链表数据结构图解 和 代码实现

时间:2022-11-10 10:49:38

项目中经常会用到LinkedList集合来存储数据,打算写一篇LinkedList的源码解析,而LinkedList是基于链表结构存储数据的,这篇博文将解析链表数据结构,包括单向链表和双向链表;

1:单向链表:

单向链表的链表对象维护了一个 first 引用,该引用指向节点链表中的第一个节点对象,每个节点对象维护一个 next 引用,next引用指向下一个节点对象;(这里注意:是引用指向的是节点对象:节点对象包含存储的数据和next引用)

以下是单向链表的图解:

链表数据结构图解 和 代码实现

java代码实现如下:

public class LinkedListDemo1 { //表示整个链表对象

private Node first; //链表对象的第一个引用

public LinkedListDemo1(){

}


public Node getFirst() {
return first;
}


public void setFirst(Node first) {
this.first = first;
}


class Node{ //节点对象
Item item; //存储的数据对象
Node next; //下一个节点对象的引用
public Item getItem() {
return item;
}
public void setItem(Item item) {
this.item = item;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}


}
}

当需要在首位置插入元素时,图解如下:first 引用指向需要插入到链表中的节点对象,新的节点对象的next引用指向原先的首节点对象;

链表数据结构图解 和 代码实现

java代码实现如下:

//插入对象到链表首位置	public void insertFirst(Item item){		//创建链表对象		LinkedListDemo1 list=new LinkedListDemo1();		//原来的首个节点暂存在:用oldFirst引用指向		Node oldFirst=first;		//创建需要插入的节点对象		Node newNode=new Node();		newNode.item=item;		//新节点对象的next引用指向原先的首节点对象		newNode.next=oldFirst;			}

 当然这里的插入没有考虑首位置的节点对象为null的情况,插入到其他位置的节点实现原理和插入到首位置的基本差不多;

下面接收双向链表的实现原理:

链表对象中维护一个first 引用和 last引用,分别指向链表中的首末节点对象;每个节点对象维护 存储的数据对象引用,prev和next引用,用来指向前后节点对象;

双向链表的图解:

链表数据结构图解 和 代码实现

java代码实现链表对象如下:

public class LinkedListDemo2 {	private Node first;	private Node last;			public LinkedListDemo2(){			}			public Node getFirst() {		return first;	}	public void setFirst(Node first) {		this.first = first;	}			public Node getLast() {		return last;	}	public void setLast(Node last) {		this.last = last;	}	class Node{		 Item item;		 Node prev;		 Node next;		public Item getItem() {			return item;		}		public void setItem(Item item) {			this.item = item;		}				public Node getPrev() {			return prev;		}		public void setPrev(Node prev) {			this.prev = prev;		}		public Node getNext() {			return next;		}		public void setNext(Node next) {			this.next = next;		}}}

双向链表插入元素到首位:

图解:

链表数据结构图解 和 代码实现

java代码实现:

public void insertFirst(Item item){		//暂存原先首节点对象		Node oldFirst=first;		//创建新的节点对象		Node newNode=new Node();		newNode.item=item;		newNode.next=first;		//first引用指向新节点对象		first=newNode;		//原先的节点对象的prev引用指向新节点对象		oldFirst.prev=newNode;	}

到此,单向链表结构和双向链表结构就解析完了,下面将解析 LinkedList 的源码:

LinkedList是基于双向链表数据结构来存储数据的,以下是对LinkedList  的 属性,构造器 ,add(E e),remove(index),get(Index),set(inde,e)进行源码分析:

属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
transient  int  size =  0 ;     //记录集合的大小
 
     /**
      * Pointer to first node.
      * Invariant: (first == null && last == null) ||
      *            (first.prev == null && first.item != null)
      */
     transient  Node<E> first;   //指向首节点对象
 
     /**
      * Pointer to last node.
      * Invariant: (first == null && last == null) ||
      *            (last.next == null && last.item != null)
      */
     transient  Node<E> last;     //指向末节点对象

2构造器:

1
public  LinkedList() {    //构造空的LinkedList对象<br>}
1
2
3
public  LinkedList(Collection<?  extends  E> c) {    //构造对象,将集合元素添加到新集合中<br>       this();
        addAll(c);
    }

3:方法:add(E e)

1
2
3
4
public  boolean  add(E e) {
         linkLast(e);
         return  true ;
     }

linkedLast(e) 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
      * Links e as last element.
      */
     void  linkLast(E e) {
         final  Node<E> l = last;       //将原来的最末节点对象暂存 l 引用
         final  Node<E> newNode =  new  Node<>(l, e,  null );  /构建新的Node对象
         last = newNode;               //将链表对象的last引用指向新增的节点元素
         if  (l ==  null )              
             first = newNode;          //如果不存在之前指向的节点,则first引用指向新创建的节点对象
         else
             l.next = newNode;         //存在前一个节点,之前最后节点对象的next指向新建的节点对象
         size++;                       //结合的长度加1
         modCount++;
     }

Node对象的构造器如下:

1
2
3
4
5
6
7
8
9
10
11
private  static  class  Node<E> {
         E item;
         Node<E> next;
         Node<E> prev;
 
         Node(Node<E> prev, E element, Node<E> next) {   //参数为 l:之前的最后一个节点, element:需要新增的元素, next null
             this .item = element;    //要增加的元素
             this .next = next;       //新增节点的next指向为null
             this .prev = prev;       //新增节点的prev指向之前的节点
         }
     }

remove方法:

1
2
3
4
public  E remove( int  index) {     //删除指定索引的元素
        checkElementIndex(index);    //检查是否索引越界
        return  unlink(node(index)); 
    }
1
node(index) 的源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Node<E> node( int  index) {
         // assert isElementIndex(index);
 
         if  (index < (size >>  1 )) {    //获取到一般长度的集合索引值 
             Node<E> x = first;         //暂存链表中首节点对象
             for  ( int  i =  0 ; i < index; i++)   //遍历前半段集合节点
                 x = x.next;
             return  x;
         else  {
             Node<E> x = last;
             for  ( int  i = size -  1 ; i > index; i--)
                 x = x.prev;
             return  x;
         }
     }

这里有点繁琐,举个具体的实例说明:比如需要删除index=5;的节点对象,假设结合的长度为20

则调用 node(5) 方法后返回的是什么呢?假设Node(0) 为起始位置  

此时:初始:x=Node(0),当i=0   x=Node(1)    i=1   x=Node(2)…… 当i=5-1  x=Node(5)   此时就定位到了需要删除的节点对象 即 Node(index)

接下来调用:   unlink(node(index))  继续以index=5为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
E unlink(Node<E> x) {
         // assert x != null;
         final  E element = x.item;      //Node(5).data
         final  Node<E> next = x.next;   //next=Node(6)
         final  Node<E> prev = x.prev;   //prev=Node(4)
 
         if  (prev ==  null ) {
             first = next;
         else  {
             prev.next = next;        //Node(4).next=Node(6)
             x.prev =  null ;           //Node(5).prev=null
         }
 
         if  (next ==  null ) {
             last = prev;
         else  {
             next.prev = prev;        // Node(6).prev=Node(4)
             x.next =  null ;           //Node(5).next=null  回收
         }
 
         x.item =  null ;              //Node(5)=null
         size--;
         modCount++;
         return  element;
     }

这样就完成了  Node(index-1).next=Node(index+1)   Node(index+1).prev=Node(index-1)   Node(index).data=null  Node(index).prev=null  Node(index).next=null  完成了删除动作  删除相应的索引的节点

删除第一个节点和删除最后一个节点的原理类似;

Get(int index) 方法:

1
2
3
public  E get( int  index) {
         checkElementIndex(index);    //检查索引是否越界
         return  node(index).item;     //node(index) 在删除的方法中分析过,返回索引为index的节点对象, 所以get方法 返回的是该索引节点的存储数据对象
1
}

set(index,e) 方法:

1
2
3
4
5
public  E set( int  index, E element) {
         checkElementIndex(index);
         Node<E> x = node(index);      //调用node(index)放回Node(index)
         E oldVal = x.item;          
         x.item = element;             //将 Node(index)的引用指向新的对象
1
return  oldVal; }

 到此LinkedList的源码分析结束了:

mark:使用LinkedList 时,使用的是链表结构,当调用add()方法时,默认添加到最后一个,集合不需要扩充,减少内存消耗;

但是当LinkedList 进行指定索引的查询,元素替换,删除,需要对集合从first指向开始进行遍历一遍才能进行,有相应的计算复杂度;使用时应当考虑到这一点