数据结构与算法—一文多图搞懂双链表
目錄
- 前言
- 雙鏈表介紹
- 與單鏈表區別
- 結構的設計
- 具體方法的解析
- 初始化
- 增加
- 空表插入:
- 頭插入:
- 尾插入:
- 編號插入:
- 刪除
- 單節點刪除:
- 頭刪除:
- 尾刪除:
- 普通刪除:
- 代碼與測試
- 總結與感悟
前言
前面講過線性表中順序表和鏈表的實現和性質。但是在數據結構與算法中,雙向鏈表無論在考察還是運用中都占有很大的比例,筆者旨在通過本文與讀者一起學習分享雙鏈表相關知識。
雙鏈表介紹
與單鏈表區別
邏輯上沒有區別。他們均是完成線性表的內容。主要的區別是結構上的構造有所區別。
對于單鏈表:
- 對于一個節點,有儲存數據的data。和next后驅節點(指針)。也就是這個單鏈表想要一些遍歷的操作都得通過前節點—>后節點。
對于雙鏈表:
- 對于一個節點,有些和單鏈表一樣有存儲數據的data,指向后方的next(指針)。它擁有單鏈表的所有操作和內容。但是他還有一個前驅節點pre(指針)。
結構的設計
- 對于雙鏈表的結構,上圖也很清楚的。以前設計的單鏈表是帶頭節點的。帶頭節點可以方面首位的插入和刪除。而這次我們抱著學習的態度搞清鏈表故該雙鏈表是不帶頭節點的.
- 同時,以前的單鏈表是不帶尾節點的,這次我們帶上尾節點tail。這樣我們就接觸了幾乎所有類型啦!遇到啥也不怕了。
所以我們構造的這個雙鏈表的的性質:
- 不帶頭節點、帶尾指針(tail)、雙向鏈表。
對于node節點:
class node<T> {T data;node<T> pre;node<T> next;public node() {}public node(T data) {this.data = data;} }對于鏈表:
public class doubleList<T> {private node<T> head;// 頭節點private node<T> tail;// 尾節點private int length;//各種方法 }具體方法的解析
- 其實對于一個鏈表主要的操作還是增刪。增閃的話都需要考慮是否帶頭節點。頭插、尾插、中間插。并且還要考慮其中的一些細節處理。指針的運算。防止鏈表崩掉。因為這些操作如果不當往往會對鏈表的結構和證悟性帶來致命的打擊。而像查找那些邏輯稍微簡單。也很容易排查錯誤。
初始化
- 我們知道一個雙鏈表在最初的時候它的數據肯定是為null的。那么對于這個不帶頭節點的雙鏈表而言。它的head始終指向第一個真實有效的數據。tail也是如此。那么在最初沒數據的時候當然要head=null,并且tail=head。(tail和head需要在一個鏈上)。
增加
空表插入:
- 對于空鏈表來說。增加第一個元素可以特殊考慮。因為在鏈表為空的時候head和tail均為null。但head和tail又需要實實在在指向鏈表中的真實數據(帶頭指針就不需要考慮)。所以這時候就新建一個node讓head、tail等于它。
頭插入:
對于頭插入來說。步驟很簡單,只需考慮head節點的變化。
尾插入:
對于尾插入來說。只需考慮尾節點tail節點的變化。
編號插入:
對于編號插入來說。要考慮查找和插入兩部,而插入既和head無關也和tail無關。
整個流程的動態圖為:
刪除
單節點刪除:
無論頭刪還是尾刪,遇到單節點刪除的需要將鏈表從新初始化!
if (length == 1)// 只有一個元素 {head = null;tail = head;length--; }頭刪除:
頭刪除需要注意的就是刪除不為空時候頭刪除只和head節點有關
大致分為:
尾刪除:
尾刪除需要注意的就是刪除不為空時候尾刪除只和tail節點有關。記得在普通鏈表中,我們刪除尾節點需要找到尾節點的前驅節點。需要遍歷整個表。而雙向鏈表可以直接從尾節點遍歷到前面。
刪除的時tail所在位置的點。也就是tail所在節點要斷絕和雙鏈表的關系。
普通刪除:
普通刪除需要重點掌握,因為前兩個刪除都是普通刪除的一個特例而已。(普通刪除要確保不是頭刪除和尾刪除)
整個流程的動態圖為:
代碼與測試
代碼:
package LinerList;/** 不帶頭節點的*/ public class doubleList<T> { class node<T> {T data;node<T> pre;node<T> next;public node() {}public node(T data) {this.data = data;} }private node<T> head;// 頭節點 private node<T> tail;// 尾節點 private int length;public doubleList() {head = null;tail = head;length = 0; }boolean isEmpty() {return length == 0 ? true : false; }void addfirst(T data) {node<T> teamNode = new node(data);if (isEmpty()) {head = teamNode;tail = teamNode;} else {teamNode.next = head;head = teamNode;}length++; }void add(T data)// 尾節點插入 {node<T> teamNode = new node(data);if (isEmpty()) {head = teamNode;tail = teamNode;} else {tail.next = teamNode;teamNode.pre=tail;tail = teamNode;}length++; }int length(){return length;} T getElum(int index)//為了簡單統一從頭找 {node<T> team=head;for(int i=0;i<index;i++)//不帶頭節點 遍歷次數-1{team=team.next;}return team.data; } void add(int index, T data)// 編號插入 {if (index == 0) {addfirst(data);} else if (index == length) {add(data);} else {// 重頭戲node teampre = head;// 為插入的前qufor (int i = 0; i < index -1; i++)// 無頭節點,index-1位置找到前驅節點{teampre = teampre.next;}node<T> team = new node(data);// a c 中插入B 找打ateam.next = teampre.next;// B.next=cteampre.next.pre = team;// c.pre=Bteam.pre = teampre;// 關聯a Bteampre.next = team;length++;} } void deletefirst()// 頭部刪除 {if (length == 1)// 只有一個元素{head = null;tail = head;length--;} else {head = head.next;length--;} }void deletelast() {if(length==1){head=null;tail=head;length--;}else {tail.pre.next=null;tail=tail.pre;length--;} }void delete(int index){if(index==0)deletefirst();else if (index==length-1) {deletelast();}else {//刪除 為了理解統一從頭找那個節點 node<T>team=head;for(int i=0;i<index-1;i++){team=team.next;}//team 此時為要刪除的前節點 a c 插入B a為teamteam.next.next.pre=team;//c的前驅變成ateam.next=team.next.next;//a的后驅變成clength--;}}void set(int index,T data){node<T>team=head;for(int i=0;i<index-1;i++){team=team.next;}team.data=data;} @Override public String toString() {node<T> team = head;String vaString = "";while (team != null) {vaString += team.data + " ";team = team.next;}return vaString; } }測試:
package LinerList;public class test {public static void main(String[] args) throws Exception { // TODO Auto-generated method stubSystem.out.println("線性表測試:");doubleList<Integer> list = new doubleList<Integer>();list.add(66);list.addfirst(55);list.add(1, 101);list.add(-22);list.add(555);list.addfirst(9999);System.out.println(list.toString() + " lenth " + list.length());// 9999 55 101 66 -22 555// System.out.println(list.getElum(0)+" "+list.getElum(2)+" "+list.getElum(4));list.deletefirst();System.out.println(list.toString() + " lenth " + list.length());// 55 101 66 -22 555 lenth 5list.delete(1);System.out.println(list.toString() + " length " + list.length());// 55 66 -22 555 length 4list.delete(1);System.out.println(list.toString() + " length " + list.length());// 55 -22 555 length 3list.deletelast();System.out.println(list.toString() + " lenth " + list.length());// 55 -22 lenth 2list.deletelast();System.out.println(list.toString() + " lenth " + list.length());// 55 lenth 1list.deletelast();System.out.println(list.toString() + " lenth " + list.length());// lenth 0System.err.println("歡迎關注公眾號:bigsai");}}結果圖
總結與感悟
插入、刪除順序問題:
- 很多人其實不清楚插入、刪除正確的順序是什么。其實這點沒有必然的順序,要根據題意所給的條件完成相同的結果即可!
- 還有就是你可能會搞不清一堆next.next這些問題。這時候建議你畫個圖。你也可以先建一個節點,用變量名完成操作,可能會更容易一些。比如刪除操作,你找到pre節點(刪除前的節點)。你可以node delete=pre.next,node next=delete.next。這樣你直接操作pre。delete。next三個節點會更簡單。
- 但是很多題目只給你一個node。你這時候要分析next(pre)。改變順序。因為只有一個節點,你改變next(pre)很可能導致你遍歷不到那個節點。所以這種情況要好好思考(可以參考筆者的代碼實現)。
- 至于有些語言需要刪除內存的。別忘記刪除。(java大法好)
其他操作問題:
- 對于其他操作,相比增刪要容易理解,可以參考代碼理解。
- 雙向鏈表可以對很多操作進行優化。這里只是突出實現并沒有寫的太多。比如查找時候可以根據長度判斷這個鏈表從頭查找還是從尾查找。
另外,代碼寫的可能不是太好,鏈表也沒考慮線程安全問題。算法效率可能不太優。如果有什么改進或者漏洞還請大佬指出!
最后(last but not least):
- 喜歡的感覺可以的煩請大家動動小手關注一下把,關注后回復: 數據結構 有精心準備的系列。個人公眾號交流:bigsai
- 歡迎交友!
總結
以上是生活随笔為你收集整理的数据结构与算法—一文多图搞懂双链表的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 哪吒票房逼近40亿,用python爬取哪
- 下一篇: 数据结构与算法—栈详解