链表数据结构图解 和 代码实现
項目中經(jīng)常會用到LinkedList集合來存儲數(shù)據(jù),打算寫一篇LinkedList的源碼解析,而LinkedList是基于鏈表結(jié)構(gòu)存儲數(shù)據(jù)的,這篇博文將解析鏈表數(shù)據(jù)結(jié)構(gòu),包括單向鏈表和雙向鏈表;
1:單向鏈表:
單向鏈表的鏈表對象維護了一個 first 引用,該引用指向節(jié)點鏈表中的第一個節(jié)點對象,每個節(jié)點對象維護一個 next 引用,next引用指向下一個節(jié)點對象;(這里注意:是引用指向的是節(jié)點對象:節(jié)點對象包含存儲的數(shù)據(jù)和next引用)
以下是單向鏈表的圖解:
java代碼實現(xiàn)如下:
public class LinkedListDemo1 { //表示整個鏈表對象private Node first; //鏈表對象的第一個引用public LinkedListDemo1(){}public Node getFirst() {return first;}public void setFirst(Node first) {this.first = first;}class Node{ //節(jié)點對象Item item; //存儲的數(shù)據(jù)對象Node next; //下一個節(jié)點對象的引用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 引用指向需要插入到鏈表中的節(jié)點對象,新的節(jié)點對象的next引用指向原先的首節(jié)點對象;
java代碼實現(xiàn)如下:
//插入對象到鏈表首位置public void insertFirst(Item item){//創(chuàng)建鏈表對象LinkedListDemo1 list=new LinkedListDemo1();//原來的首個節(jié)點暫存在:用oldFirst引用指向Node oldFirst=first;//創(chuàng)建需要插入的節(jié)點對象Node newNode=new Node();newNode.item=item;//新節(jié)點對象的next引用指向原先的首節(jié)點對象newNode.next=oldFirst;}當然這里的插入沒有考慮首位置的節(jié)點對象為null的情況,插入到其他位置的節(jié)點實現(xiàn)原理和插入到首位置的基本差不多;
下面接收雙向鏈表的實現(xiàn)原理:
鏈表對象中維護一個first 引用和 last引用,分別指向鏈表中的首末節(jié)點對象;每個節(jié)點對象維護 存儲的數(shù)據(jù)對象引用,prev和next引用,用來指向前后節(jié)點對象;
雙向鏈表的圖解:
java代碼實現(xiàn)鏈表對象如下:
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代碼實現(xiàn):
public void insertFirst(Item item){//暫存原先首節(jié)點對象Node oldFirst=first;//創(chuàng)建新的節(jié)點對象Node newNode=new Node();newNode.item=item;newNode.next=first;//first引用指向新節(jié)點對象first=newNode;//原先的節(jié)點對象的prev引用指向新節(jié)點對象oldFirst.prev=newNode;}到此,單向鏈表結(jié)構(gòu)和雙向鏈表結(jié)構(gòu)就解析完了,下面將解析 LinkedList 的源碼:
LinkedList是基于雙向鏈表數(shù)據(jù)結(jié)構(gòu)來存儲數(shù)據(jù)的,以下是對LinkedList ?的 屬性,構(gòu)造器 ,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;??//指向首節(jié)點對象????/**?????* Pointer to last node.?????* Invariant: (first == null && last == null) ||?????*??????????? (last.next == null && last.item != null)?????*/????transient?Node<E> last;????//指向末節(jié)點對象 |
2構(gòu)造器:
| 1 | public?LinkedList() {???//構(gòu)造空的LinkedList對象<br>} |
| 123 | public?LinkedList(Collection<??extends?E> c) {???//構(gòu)造對象,將集合元素添加到新集合中<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;??????//將原來的最末節(jié)點對象暫存 l 引用????????final?Node<E> newNode =?new?Node<>(l, e,?null);? /構(gòu)建新的Node對象????????last = newNode;??????????????//將鏈表對象的last引用指向新增的節(jié)點元素????????if?(l ==?null)??????????????????????????first = newNode;?????????//如果不存在之前指向的節(jié)點,則first引用指向新創(chuàng)建的節(jié)點對象????????else????????????l.next = newNode;????????//存在前一個節(jié)點,之前最后節(jié)點對象的next指向新建的節(jié)點對象????????size++;??????????????????????//結(jié)合的長度加1????????modCount++;????} |
Node對象的構(gòu)造器如下:
| 1234567891011 | private?static?class?Node<E> {????????E item;????????Node<E> next;????????Node<E> prev;????????Node(Node<E> prev, E element, Node<E> next) {??//參數(shù)為 l:之前的最后一個節(jié)點, element:需要新增的元素, next null????????????this.item = element;???//要增加的元素????????????this.next = next;??????//新增節(jié)點的next指向為null????????????this.prev = prev;??????//新增節(jié)點的prev指向之前的節(jié)點????????}????} |
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;????????//暫存鏈表中首節(jié)點對象????????????for?(int?i =?0; i < index; i++)??//遍歷前半段集合節(jié)點????????????????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;的節(jié)點對象,假設結(jié)合的長度為20
則調(diào)用 node(5) 方法后返回的是什么呢?假設Node(0) 為起始位置
此時:初始:x=Node(0),當i=0 ? x=Node(1) ? ?i=1 ? x=Node(2)…… 當i=5-1 ?x=Node(5) ? 此時就定位到了需要刪除的節(jié)點對象 即 Node(index)
接下來調(diào)用: ? unlink(node(index)) ?繼續(xù)以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 ?完成了刪除動作 ?刪除相應的索引的節(jié)點
刪除第一個節(jié)點和刪除最后一個節(jié)點的原理類似;
Get(int index) 方法:
| 123 | public?E get(int?index) {????????checkElementIndex(index);???//檢查索引是否越界????????return?node(index).item;????//node(index) 在刪除的方法中分析過,返回索引為index的節(jié)點對象, 所以get方法 返回的是該索引節(jié)點的存儲數(shù)據(jù)對象 |
| 1 | } |
set(index,e) 方法:
| 12345 | public?E set(int?index, E element) {????????checkElementIndex(index);????????Node<E> x = node(index);?????//調(diào)用node(index)放回Node(index)????????E oldVal = x.item;??????????????????x.item = element;????????????//將 Node(index)的引用指向新的對象 |
| 1 | return?oldVal; } |
到此LinkedList的源碼分析結(jié)束了:
mark:使用LinkedList 時,使用的是鏈表結(jié)構(gòu),當調(diào)用add()方法時,默認添加到最后一個,集合不需要擴充,減少內(nèi)存消耗;
但是當LinkedList 進行指定索引的查詢,元素替換,刪除,需要對集合從first指向開始進行遍歷一遍才能進行,有相應的計算復雜度;使用時應當考慮到這一點
總結(jié)
以上是生活随笔為你收集整理的链表数据结构图解 和 代码实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux下远程桌面连接windows
- 下一篇: C语言链表实现