链表数据结构图解 和 代码实现
項目中經常會用到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)進行源碼分析:
屬性:
| 123456789101112131415 | 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>} |
| 123 | public?LinkedList(Collection<??extends?E> c) {???//構造對象,將集合元素添加到新集合中<br>?????? this();???????addAll(c);???} |
3:方法:add(E e)
| 1234 | public?boolean?add(E e) {????????linkLast(e);????????return?true;????} |
linkedLast(e) 源碼
| 1234567891011121314 | /**?????* 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對象的構造器如下:
| 1234567891011 | 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方法:
| 1234 | public?E remove(int?index) {????//刪除指定索引的元素???????checkElementIndex(index);???//檢查是否索引越界???????return?unlink(node(index));????} |
| 1 | node(index) 的源碼如下: |
| 123456789101112131415 | 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為例
| 12345678910111213141516171819202122232425 | 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) 方法:
| 123 | public?E get(int?index) {????????checkElementIndex(index);???//檢查索引是否越界????????return?node(index).item;????//node(index) 在刪除的方法中分析過,返回索引為index的節點對象, 所以get方法 返回的是該索引節點的存儲數據對象 |
| 1 | } |
set(index,e) 方法:
| 12345 | 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指向開始進行遍歷一遍才能進行,有相應的計算復雜度;使用時應當考慮到這一點
總結
以上是生活随笔為你收集整理的链表数据结构图解 和 代码实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux下远程桌面连接windows
- 下一篇: C语言链表实现