链表是一种物理存储单元上非连续、非顺序的存储结构数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。

  每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

  相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

  链表这类数据结构,有点像生活中的火车,一节车厢连着下一节车厢,在火车里面,只有到了4号车厢你才能进入5号车厢,一般情况下,不可能直接在3号车厢绕过4号车厢进入5号车厢。不过更准确来说,火车是双向链表,也就是说在4号车厢也可以反向进入3号车厢。

二、链表种类

    1.1)单向链表:

      element:用来存放元素

      next:用来指向下一个节点元素

      通过每个结点的指针指向下一个结点从而链接起来的结构,最后一个节点的next指向null。

      

    1.2)单向循环链表

      element、next 跟前面一样

      在单向链表的最后一个节点的next会指向头节点,而不是指向null,这样存成一个环

      

    1.3)双向链表

      element:存放元素

      pre:用来指向前一个元素

      next:指向后一个元素

      双向链表是包含两个指针的,pre指向前一个节点,next指向后一个节点,但是第一个节点head的pre指向null,最后一个节点的tail指向null。

      

    1.4)双向循环链表

      element、pre、next 跟前面的一样

      第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,也形成一个“环”。

      

  时间复杂度

  (1)添加操作:O(n)

    ① addLast(e): O(n)  ②addFirst(e): O(1)  ③add(index, e): O(n/2)=O(n)

  (2)删除操作:O(n)

    ① removeLast(e): O(n)  ②removeFirst(e): O(1)  ③remove(index, e): O(n/2)=O(n)

  (3)查找操作:O(n)

    ① get(index): O(n)  ②contains(e): O(n)

  (4)修改操作:O(n)

    ① set(index,e): O(n)

三、代码实现

单链表的实现

图解如下:

插入

删除

代码

public class LinkedList<E> {
//链表节点,使用内部类
private class Node{
public E e; //当前节点内容
public Node next; //指向下一个节点
public Node(E e, Node next){
this.e = e;
this.next = next;
}
public Node(E e){
this(e,null);
}
public Node(){
this(null,null);
} @Override
public String toString(){
return e.toString();
}
} private Node dummyHead; //虚拟头节点,位于第一位置的前一个位置
private int size;//链表元素个数 public LinkedList(){
dummyHead = new Node();
size = 0;
} public int getSize(){
return size;
}
//判断是否为空
public boolean isEmpty(){
return size == 0;
} public void add(int index, E e){
if(index < 0 || index > size){
throw new IllegalArgumentException("Add failed. Illegal index.");
}
Node prev = dummyHead;
for (int i=0; i<index; i++){
prev = prev.next;
}
prev.next = new Node(e,prev.next);
size ++;
}
//在链表 表头添加新的元素e
public void addFirst(E e){
add(0,e);
}
//在链表 尾部添加新的元素e
public void addList(E e){
add(size,e);
} //获得链表的第index位置的元素. 在链表中不是一个常用的操作,练习用
public E get(int index){
if(index < 0 || index >= size){
throw new IllegalArgumentException("index Illegal");
} Node cur = dummyHead.next;
for (int i =0; i<index; i++){
cur = cur.next;
}
return cur.e;
} public E getLast(){
return get(size - 1);
}
public E getFirst(){
return get(0);
} public void set(int index,E e){
if(index < 0 || index >= size){
throw new IllegalArgumentException("index Illegal");
} Node cur = dummyHead.next;
for (int i=0; i<index; i++){
cur =cur.next;
}
cur.e = e;
} //查找链表中是否包含元素e
public boolean contains(E e){
Node cur = dummyHead.next;
while (cur != null){
if (cur.e.equals(e))
return true;
cur = cur.next;
}
return false;
}
// 从链表中删除index(0-based)位置的元素, 返回删除的元素
// 在链表中不是一个常用的操作,练习用:)
public E remove(int index){
if (index < 0 || index >= size){
throw new IllegalArgumentException("Remove failed. Index is illegal.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++){
prev = prev.next;
}
Node retNode = prev.next;
prev.next = retNode.next;
//删除retNode节点,则它的下一个节点指引就不需要了,设置null,让GC干活
retNode.next = null;
size --;
return retNode.e;
} public E removeFirst(){
return remove(0);
} public E removeLast(){
return remove(size - 1);
} public void removeElement(E e){
Node prev = dummyHead;
while (prev != null){
if (prev.next.e.equals(e)){
break;
}
prev = prev.next;
} if (prev.next != null){
Node delNode = prev.next;
prev.next = delNode.next;
delNode.next = null;
size --;
}
}
@Override
public String toString(){
StringBuilder stringBuilder = new StringBuilder();
Node cur = dummyHead.next;
while( cur != null){
stringBuilder.append(cur + "- >");
cur = cur.next;
}
stringBuilder.append("null");
return stringBuilder.toString();
}
}

测试代码

public class LinkedListTest{
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
for (int i=0; i< 10; i++){
linkedList.add(i,i*10);
System.out.println(linkedList);
}
linkedList.remove(1);
System.out.println(linkedList);
linkedList.removeElement(50);
System.out.println(linkedList);
}
}
//测试结果
0- >null
0- >10- >null
0- >10- >20- >null
0- >10- >20- >30- >null
0- >10- >20- >30- >40- >null
0- >10- >20- >30- >40- >50- >null
0- >10- >20- >30- >40- >50- >60- >null
0- >10- >20- >30- >40- >50- >60- >70- >null
0- >10- >20- >30- >40- >50- >60- >70- >80- >null
0- >10- >20- >30- >40- >50- >60- >70- >80- >90- >null
0- >20- >30- >40- >50- >60- >70- >80- >90- >null //删除第二个位置的10
0- >20- >30- >40- >60- >70- >80- >90- >null //删除指定元素50

  链表实现队列

import com.wj.queue.Queue; //Queue连接:https://www.cnblogs.com/FondWang/p/11808221.html
public class LinkedListQueue<E> implements Queue<E> {
private class Node{
public E e;
public Node next; public Node(E e, Node next){
this.e=e;
this.next = next;
} public Node(){
this(null,null);
} public Node(E e){
this(e,null);
} @Override
public String toString() {
return e.toString();
}
} private Node head, tail; //指针队首和队尾
private int size; //数据个数 public LinkedListQueue(){
head = null;
tail = null;
size = 0;
} @Override
public int getSize() {
return size;
} @Override
public boolean isEmpty() {
return size == 0;
} //入队
@Override
public void enqueue(Object o) {
if (tail == null){
tail = new Node((E) o);
head = tail;
}else {
tail.next = new Node((E) o);
tail = tail.next;
}
size++;
}
//出队
@Override
public E dequeue() {
if (isEmpty()){
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
}
Node retNode = head;
head = head.next;
retNode.next = null;
if (head == null){
tail = null;
}
size--;
return retNode.e;
}
//返回队首元素
@Override
public E getFront() {
if (isEmpty()){
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
}
return head.e;
} @Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Queue: front ");
Node cur = head;
while (cur != null){
stringBuilder.append(cur + "- >");
cur = cur.next;
}
stringBuilder.append("NULL tail");
return stringBuilder.toString();
}
}

测试类

public class LinkedListQueueTest{
public static void main(String[] args) {
LinkedListQueue linkedListQueue = new LinkedListQueue();
for (int i=0; i<10; i++){
linkedListQueue.enqueue(i); //入队
System.out.println(linkedListQueue);
if (i % 3 ==0){
linkedListQueue.dequeue(); //符合条件,出队
}
}
}
}
//测试结果
Queue: front 0- >NULL tail
Queue: front 1- >NULL tail
Queue: front 1- >2- >NULL tail
Queue: front 1- >2- >3- >NULL tail
Queue: front 2- >3- >4- >NULL tail
Queue: front 2- >3- >4- >5- >NULL tail
Queue: front 2- >3- >4- >5- >6- >NULL tail
Queue: front 3- >4- >5- >6- >7- >NULL tail
Queue: front 3- >4- >5- >6- >7- >8- >NULL tail
Queue: front 3- >4- >5- >6- >7- >8- >9- >NULL tail

  链表实现栈 

import com.wj.stack.Stack; //详情连接:https://www.cnblogs.com/FondWang/p/11809042.html
public class LinkedListStack<E> implements Stack<E> { private LinkedList<E> linkedList; public LinkedListStack(){
linkedList = new LinkedList<>();
}
@Override
public int getSize() {
return linkedList.getSize();
} @Override
public boolean isEmpty() {
return linkedList.isEmpty();
} //压入
public void push(Object o) {
linkedList.addFirst((E) o);
}
//移除
@Override
public E pop() {
return linkedList.removeFirst();
}
//返回栈首元素
@Override
public E peek() {
return linkedList.getFirst();
} public String toString(){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Stack: top ");
stringBuilder.append(linkedList);
return stringBuilder.toString();
}
}

测试类

public class LinkedListStackTest{
public static void main(String[] args) {
LinkedListStack stack = new LinkedListStack();
for (int i =0; i< 10; i++){
stack.push(i); //压入
System.out.println(stack);
} System.out.println("=============");
stack.pop(); //移除
System.out.println(stack);
}
}
//测试结果
Stack: top 0- >null
Stack: top 1- >0- >null
Stack: top 2- >1- >0- >null
Stack: top 3- >2- >1- >0- >null
Stack: top 4- >3- >2- >1- >0- >null
Stack: top 5- >4- >3- >2- >1- >0- >null
Stack: top 6- >5- >4- >3- >2- >1- >0- >null
Stack: top 7- >6- >5- >4- >3- >2- >1- >0- >null
Stack: top 8- >7- >6- >5- >4- >3- >2- >1- >0- >null
Stack: top 9- >8- >7- >6- >5- >4- >3- >2- >1- >0- >null //将第一个移除
=============
Stack: top 8- >7- >6- >5- >4- >3- >2- >1- >0- >null

四、效率对比

栈的对比

import com.wj.stack.ArrayStack; //详情连接:https://www.cnblogs.com/FondWang/p/11809042.html
import com.wj.stack.Stack;
import java.util.Random;
public class StackEfficiency {
private static double testStack(Stack<Integer> stack, int opCount){
long startTime = System.nanoTime();
Random random = new Random();
for (int i=0; i < opCount; i ++){
stack.push(random.nextInt(Integer.MAX_VALUE));
}
for (int i=0; i < opCount; i ++){
stack.pop();
} long endTime = System.nanoTime();
return (endTime - startTime) / 1000000000.0;
}
public static void main(String[] args) {
int opCount = 100000;
ArrayStack<Integer> arrayStack = new ArrayStack();
double time1 = testStack(arrayStack,opCount);
System.out.println("ArrayStack - time1: " + time1); LinkedListStack<Integer> linkedListStack = new LinkedListStack();
double time2 = testStack(linkedListStack,opCount);
System.out.println("LinkedListStack - time2: " + time2); }
}
//测试结果,添加和删除
ArrayStack - time1: 7.071363002
LinkedListStack - time2: 0.006395025
//说明数组的增删 效率要低于 链表。
//对于增删数组的时间复杂度是O(n),而链表是O(1)

队列的对比

import com.wj.queue.ArrayQueue;//详情:https://www.cnblogs.com/FondWang/p/11808221.html
import com.wj.queue.LoopQueue;
import com.wj.queue.Queue;
import java.util.Random;
public class MainQueue {
private static double testStack(Queue<Integer> queue, int opCount){
long startTime = System.nanoTime();
Random random = new Random();
for (int i=0; i < opCount; i ++){
queue.enqueue(random.nextInt(Integer.MAX_VALUE));
}
for (int i=0; i < opCount; i ++){
queue.dequeue();
} long endTime = System.nanoTime();
return (endTime - startTime) / 1000000000.0;
}
public static void main(String[] args) {
int opCount = 1000000;
      
ArrayQueue<Integer> arrayQueue = new ArrayQueue();
double time1 = testStack(arrayQueue,opCount);
System.out.println("ArrayStack - time1: " + time1); LinkedListQueue<Integer> linkedListQueue = new LinkedListQueue();
double time2 = testStack(linkedListQueue,opCount);
System.out.println("LinkedListStack - time2: " + time2); LoopQueue<Integer> loopQueue = new LoopQueue();
double time3 = testStack(loopQueue,opCount);
System.out.println("LoopQueue - time3: " + time3);
}
}
//测试结果
ArrayQueue - time1: 368.014648575
LinkedListQueue - time2: 0.395344446
LoopQueue - time3: 0.045756219
//对于增删操作,数组(O(n))的时间复杂度高于链表队列和循环队列,所以数组最慢。
//链表和队列。队列增删只在队首和队尾,时间复杂度低于链表,所以循环队列要高于链表队列。

数据结构 -- 链表(LinkedList)的更多相关文章

  1. 模板 - 数据结构 - 链表/LinkedList

    一个只供删除的双向链表,为了简单不再引入head节点,而且也不进行next的套娃操作.空间使用略微多了一些,但是无伤大雅. struct LinkedList { static const int M ...

  2. 数据结构之链表(LinkedList)(三)

    数据结构之链表(LinkedList)(二) 环形链表 顾名思义 环形列表是一个首尾相连的环形链表 示意图 循环链表的特点是无须增加存储量,仅对表的链接方式稍作改变,即可使得表处理更加方便灵活. 看一 ...

  3. 数据结构之链表(LinkedList)(二)

    数据结构之链表(LinkedList)(一) 双链表 上一篇讲述了单链表是通过next 指向下一个节点,那么双链表就是指不止可以顺序指向下一个节点,还可以通过prior域逆序指向上一个节点 示意图: ...

  4. 《数据结构与算法分析——C语言描述》ADT实现(NO.00) : 链表(Linked-List)

    开始学习数据结构,使用的教材是机械工业出版社的<数据结构与算法分析——C语言描述>,计划将书中的ADT用C语言实现一遍,记录于此.下面是第一个最简单的结构——链表. 链表(Linked-L ...

  5. Python—数据结构——链表

    数据结构——链表 一.简介 链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构.由一系列节点组成的元素集合.每个节点包含两部分,数据域item和指向下一个节 ...

  6. (js描述的)数据结构[链表](4)

    (js描述的)数据结构 [链表](4) 一.基本结构 二.想比于数组,链表的一些优点 1.内存空间不是必须连续的,可以充分利用计算机的内存,事项灵活的内存动态管理. 2.链表不必再创建时就确定大小,并 ...

  7. 数据结构和算法(Golang实现)(12)常见数据结构-链表

    链表 讲数据结构就离不开讲链表.因为数据结构是用来组织数据的,如何将一个数据关联到另外一个数据呢?链表可以将数据和数据之间关联起来,从一个数据指向另外一个数据. 一.链表 定义: 链表由一个个数据节点 ...

  8. Redis数据结构—链表与字典的结构

    目录 Redis数据结构-链表与字典的结构 链表 Redis链表节点的结构 Redis链表的表示 Redis链表用在哪 字典 Redis字典结构总览 Redis字典结构分解 Redis字典的使用 Re ...

  9. Redis数据结构—链表与字典

    目录 Redis数据结构-链表与字典 链表 Redis链表节点的结构 Redis链表的表示 Redis链表用在哪 字典 Redis字典结构总览 Redis字典结构分解 哈希算法 解决键冲突 rehas ...

  10. 链表LinkedList、堆栈Stack、集合Set

    链表LinkedList LinkedList 也像 ArrayList 一样实现了基本的 List 接口,但它在 List 中间执行插入和删除操作时比 ArrayList 更高效.然而,它在随机访问 ...

随机推荐

  1. 入门cmake,窥探编译过程

    https://www.cnblogs.com/hg-love-dfc/p/10242391.html https://www.cnblogs.com/hg-love-dfc/p/10244328.h ...

  2. 2018-2019-2 网络对抗技术 20165311 Exp 9 Web安全基础

    2018-2019-2 网络对抗技术 20165311 Exp 9 Web安全基础 基础问题回答 实践过程记录 WebGoat安装 SQL注入攻击 1.命令注入(Command Injection) ...

  3. bs4 string与text的区别

    用python写爬虫时,BeautifulSoup真是解析html,快速获取所需数据的神器. 这个美味汤使唤起来,屡试不爽. 在用find()方法找到特定的tag后,想获取里面的文本,可以用.text ...

  4. SEQ!org.apache.hadoop.io.LongWritable

    [uhadoop@10-13-109-236 subdir26]$ $HADOOP_HOME/bin/hadoop fs -cat /data/flumeEvents/FlumeData.155980 ...

  5. C#中 Dictionary<>的使用及注意事项

    1,如果在主体代码中使用,直接在初始化中生成就行 2如果在其他层,比如逻辑层,要注意在事件内部定义,在外部的话,重复调用就会提示“”“已经定义了相同的KEY”,见例子 (例子是转的) Dictiona ...

  6. 【PHP】解决数据库查询出来的中文内容显示为问号“??”

    方法一:在数据库连接后执行: mysql_query('SET NAMES utf8'); 代码: $dbconn=mysql_connect("localhost", " ...

  7. python面试必备-基础篇

    一.python中and, or, and-or语法 1.and   有假先出假 在Python 中,and 和 or 执行布尔逻辑演算,如你所期待的一样,但是它们并不返回布尔值:而是,返回它们实际进 ...

  8. transition css3 渐变效果

    div { width:100px; transition: width 2s; -moz-transition: width 2s; /* Firefox 4 */ -webkit-transiti ...

  9. css 命名规范参考[转]

    命名空间 另外最好的实践就是当命名你的类名的时候,使用命名空间前缀来进行分类.这些前缀会在你的命名前添加一组字符,但是这个值能立刻标记每一个类的目的,在你看 HTML 或者样式的时候是很需要的.我使用 ...

  10. linux EXT4格式分区扩容

    1.查看现有的分区大小  2.关机增加磁盘大小为100G  3.查看磁盘扩容后状态 lsblk或dh -TH 4.进行分区扩展磁盘,保留根目录的起止位置.  5.删除根分区,不要保存  6.创建分区, ...