Java實(shí)現(xiàn)單向鏈表的基本功能詳解
一、前言
最近在回顧數(shù)據(jù)結(jié)構(gòu)與算法,有部分的算法題用到了棧的思想,說起棧又不得不說鏈表了。數(shù)組和鏈表都是線性存儲結(jié)構(gòu)的基礎(chǔ),棧和隊(duì)列都是線性存儲結(jié)構(gòu)的應(yīng)用~
本文主要講解單鏈表的基礎(chǔ)知識點(diǎn),做一個(gè)簡單的入門~如果有錯的地方請指正
二、回顧與知新
說起鏈表,我們先提一下數(shù)組吧,跟數(shù)組比較一下就很理解鏈表這種存儲結(jié)構(gòu)了。
2.1回顧數(shù)組
數(shù)組我們無論是C、Java都會學(xué)過:
- 數(shù)組是一種連續(xù)存儲線性結(jié)構(gòu),元素類型相同,大小相等
數(shù)組的優(yōu)點(diǎn):
- 存取速度快
數(shù)組的缺點(diǎn):
- 事先必須知道數(shù)組的長度
- 插入刪除元素很慢
- 空間通常是有限制的
- 需要大塊連續(xù)的內(nèi)存塊
- 插入刪除元素的效率很低
2.2鏈表說明
看完了數(shù)組,回到我們的鏈表:
- 鏈表是離散存儲線性結(jié)構(gòu)
n個(gè)節(jié)點(diǎn)離散分配,彼此通過指針相連,每個(gè)節(jié)點(diǎn)只有一個(gè)前驅(qū)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)只有一個(gè)后續(xù)節(jié)點(diǎn),首節(jié)點(diǎn)沒有前驅(qū)節(jié)點(diǎn),尾節(jié)點(diǎn)沒有后續(xù)節(jié)點(diǎn)。
鏈表優(yōu)點(diǎn):
- 空間沒有限制
- 插入刪除元素很快
鏈表缺點(diǎn):
- 存取速度很慢
鏈表相關(guān)術(shù)語介紹,我還是通過上面那個(gè)圖來說明吧:
確定一個(gè)鏈表我們只需要頭指針,通過頭指針就可以把整個(gè)鏈表都能推導(dǎo)出來了~
鏈表又分了好幾類:
1、單向鏈表
- 一個(gè)節(jié)點(diǎn)指向下一個(gè)節(jié)點(diǎn)
2、雙向鏈表
- 一個(gè)節(jié)點(diǎn)有兩個(gè)指針域
3、循環(huán)鏈表
- 能通過任何一個(gè)節(jié)點(diǎn)找到其他所有的節(jié)點(diǎn),將兩種(雙向/單向)鏈表的最后一個(gè)結(jié)點(diǎn)指向第一個(gè)結(jié)點(diǎn)從而實(shí)現(xiàn)循環(huán)
操作鏈表要時(shí)刻記住的是:
節(jié)點(diǎn)中指針域指向的就是一個(gè)節(jié)點(diǎn)了!
三、Java實(shí)現(xiàn)鏈表
算法:
- 遍歷
- 查找
- 清空
- 銷毀
- 求長度
- 排序
- 刪除節(jié)點(diǎn)
- 插入節(jié)點(diǎn)
首先,我們定義一個(gè)類作為節(jié)點(diǎn)
- 數(shù)據(jù)域
- 指針域
為了操作方便我就直接定義成public了。
public class Node { //數(shù)據(jù)域 public int data; //指針域,指向下一個(gè)節(jié)點(diǎn) public Node next; public Node() { } public Node(int data) { this.data = data; } public Node(int data, Node next) { this.data = data; this.next = next; } }
3.1創(chuàng)建鏈表(增加節(jié)點(diǎn))
向鏈表中插入數(shù)據(jù):
- 找到尾節(jié)點(diǎn)進(jìn)行插入
- 即使頭節(jié)點(diǎn).next為null,不走while循環(huán),也是將頭節(jié)點(diǎn)與新節(jié)點(diǎn)連接的(我已經(jīng)將head節(jié)點(diǎn)初始化過了,因此沒必要判斷頭節(jié)點(diǎn)是否為null)~
/** * 向鏈表添加數(shù)據(jù) * * @param value 要添加的數(shù)據(jù) */ public static void addData(int value) { //初始化要加入的節(jié)點(diǎn) Node newNode = new Node(value); //臨時(shí)節(jié)點(diǎn) Node temp = head; // 找到尾節(jié)點(diǎn) while (temp.next != null) { temp = temp.next; } // 已經(jīng)包括了頭節(jié)點(diǎn).next為null的情況了~ temp.next = newNode; }
3.2遍歷鏈表
上面我們已經(jīng)編寫了增加方法,現(xiàn)在遍歷一下看一下是否正確~~~
從首節(jié)點(diǎn)開始,不斷往后面找,直到后面的節(jié)點(diǎn)沒有數(shù)據(jù):
/** * 遍歷鏈表 * * @param head 頭節(jié)點(diǎn) */ public static void traverse(Node head) { //臨時(shí)節(jié)點(diǎn),從首節(jié)點(diǎn)開始 Node temp = head.next; while (temp != null) { System.out.println("關(guān)注公眾號Java3y:" + temp.data); //繼續(xù)下一個(gè) temp = temp.next; } }
結(jié)果:
3.3插入節(jié)點(diǎn)
- 插入一個(gè)節(jié)點(diǎn)到鏈表中,首先得判斷這個(gè)位置是否是合法的,才能進(jìn)行插入~
- 找到想要插入的位置的上一個(gè)節(jié)點(diǎn)就可以了~
/** * 插入節(jié)點(diǎn) * * @param head 頭指針 * @param index 要插入的位置 * @param value 要插入的值 */ public static void insertNode(Node head, int index, int value) { //首先需要判斷指定位置是否合法, if (index < 1 || index > linkListLength(head) + 1) { System.out.println("插入位置不合法。"); return; } //臨時(shí)節(jié)點(diǎn),從頭節(jié)點(diǎn)開始 Node temp = head; //記錄遍歷的當(dāng)前位置 int currentPos = 0; //初始化要插入的節(jié)點(diǎn) Node insertNode = new Node(value); while (temp.next != null) { //找到上一個(gè)節(jié)點(diǎn)的位置了 if ((index - 1) == currentPos) { //temp表示的是上一個(gè)節(jié)點(diǎn) //將原本由上一個(gè)節(jié)點(diǎn)的指向交由插入的節(jié)點(diǎn)來指向 insertNode.next = temp.next; //將上一個(gè)節(jié)點(diǎn)的指針域指向要插入的節(jié)點(diǎn) temp.next = insertNode; return; } currentPos++; temp = temp.next; } }
3.4獲取鏈表的長度
獲取鏈表的長度就很簡單了,遍歷一下,每得到一個(gè)節(jié)點(diǎn)+1即可~
/** * 獲取鏈表的長度 * @param head 頭指針 */ public static int linkListLength(Node head) { int length = 0; //臨時(shí)節(jié)點(diǎn),從首節(jié)點(diǎn)開始 Node temp = head.next; // 找到尾節(jié)點(diǎn) while (temp != null) { length++; temp = temp.next; } return length; }
3.5刪除節(jié)點(diǎn)
刪除某個(gè)位置上的節(jié)點(diǎn)其實(shí)是和插入節(jié)點(diǎn)很像的, 同樣都要找到上一個(gè)節(jié)點(diǎn)。將上一個(gè)節(jié)點(diǎn)的指針域改變一下,就可以刪除了~
/** * 根據(jù)位置刪除節(jié)點(diǎn) * * @param head 頭指針 * @param index 要刪除的位置 */ public static void deleteNode(Node head, int index) { //首先需要判斷指定位置是否合法, if (index < 1 || index > linkListLength(head) + 1) { System.out.println("刪除位置不合法。"); return; } //臨時(shí)節(jié)點(diǎn),從頭節(jié)點(diǎn)開始 Node temp = head; //記錄遍歷的當(dāng)前位置 int currentPos = 0; while (temp.next != null) { //找到上一個(gè)節(jié)點(diǎn)的位置了 if ((index - 1) == currentPos) { //temp表示的是上一個(gè)節(jié)點(diǎn) //temp.next表示的是想要刪除的節(jié)點(diǎn) //將想要刪除的節(jié)點(diǎn)存儲一下 Node deleteNode = temp.next; //想要刪除節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)交由上一個(gè)節(jié)點(diǎn)來控制 temp.next = deleteNode.next; //Java會回收它,設(shè)置不設(shè)置為null應(yīng)該沒多大意義了(個(gè)人覺得,如果不對請指出哦~) deleteNode = null; return; } currentPos++; temp = temp.next; } }
3.6對鏈表進(jìn)行排序
前面已經(jīng)講過了8種的排序算法了【八種排序算法總結(jié)】,這次挑簡單的冒泡排序吧(其實(shí)我是想寫快速排序的,嘗試了一下感覺有點(diǎn)難.....)
/** * 對鏈表進(jìn)行排序 * * @param head * */ public static void sortLinkList(Node head) { Node currentNode; Node nextNode; for (currentNode = head.next; currentNode.next != null; currentNode = currentNode.next) { for (nextNode = head.next; nextNode.next != null; nextNode = nextNode.next) { if (nextNode.data > nextNode.next.data) { int temp = nextNode.data; nextNode.data = nextNode.next.data; nextNode.next.data = temp; } } } }
3.7找到鏈表中倒數(shù)第k個(gè)節(jié)點(diǎn)
這個(gè)算法挺有趣的:設(shè)置兩個(gè)指針p1、p2,讓p2比p1快k個(gè)節(jié)點(diǎn),同時(shí)向后遍歷,當(dāng)p2為空,則p1為倒數(shù)第k個(gè)節(jié)點(diǎn)
/** * 找到鏈表中倒數(shù)第k個(gè)節(jié)點(diǎn)(設(shè)置兩個(gè)指針p1、p2,讓p2比p1快k個(gè)節(jié)點(diǎn),同時(shí)向后遍歷,當(dāng)p2為空,則p1為倒數(shù)第k個(gè)節(jié)點(diǎn) * * @param head * @param k 倒數(shù)第k個(gè)節(jié)點(diǎn) */ public static Node findKNode(Node head, int k) { if (k < 1 || k > linkListLength(head)) return null; Node p1 = head; Node p2 = head; // p2比怕p1快k個(gè)節(jié)點(diǎn) for (int i = 0; i < k - 1; i++) p2 = p2.next; // 只要p2為null,那么p1就是倒數(shù)第k個(gè)節(jié)點(diǎn)了 while (p2.next != null) { p2 = p2.next; p1 = p1.next; } return p1; }
3.8刪除鏈表重復(fù)數(shù)據(jù)
跟冒泡排序差不多,只要它相等,就能刪除了~
/** * 刪除鏈表重復(fù)數(shù)據(jù)(跟冒泡差不多,等于刪除就是了) * * @param head 頭節(jié)點(diǎn) */ public static void deleteDuplecate(Node head) { //臨時(shí)節(jié)點(diǎn),(從首節(jié)點(diǎn)開始-->真正有數(shù)據(jù)的節(jié)點(diǎn)) Node temp = head.next; //當(dāng)前節(jié)點(diǎn)(首節(jié)點(diǎn))的下一個(gè)節(jié)點(diǎn) Node nextNode = temp.next; while (temp.next != null) { while (nextNode.next != null) { if (nextNode.next.data == nextNode.data) { //將下一個(gè)節(jié)點(diǎn)刪除(當(dāng)前節(jié)點(diǎn)指向下下個(gè)節(jié)點(diǎn)) nextNode.next = nextNode.next.next; } else { //繼續(xù)下一個(gè) nextNode = nextNode.next; } } //下一輪比較 temp = temp.next; } }
3.9查詢鏈表的中間節(jié)點(diǎn)
這個(gè)算法也挺有趣的:一個(gè)每次走1步,一個(gè)每次走兩步,走兩步的遍歷完,然后走一步的指針,那就是中間節(jié)點(diǎn)
/** * 查詢單鏈表的中間節(jié)點(diǎn) */ public static Node searchMid(Node head) { Node p1 = head; Node p2 = head; // 一個(gè)走一步,一個(gè)走兩步,直到為null,走一步的到達(dá)的就是中間節(jié)點(diǎn) while (p2 != null && p2.next != null && p2.next.next != null) { p1 = p1.next; p2 = p2.next.next; } return p1; }
3.10通過遞歸從尾到頭輸出單鏈表
/** * 通過遞歸從尾到頭輸出單鏈表 * * @param head 頭節(jié)點(diǎn) */ public static void printListReversely(Node head) { if (head != null) { printListReversely(head.next); System.out.println(head.data); } }
3.11反轉(zhuǎn)鏈表
/** * 實(shí)現(xiàn)鏈表的反轉(zhuǎn) * * @param node 鏈表的頭節(jié)點(diǎn) */ public static Node reverseLinkList(Node node) { Node prev ; if (node == null || node.next == null) { prev = node; } else { Node tmp = reverseLinkList(node.next); node.next.next = node; node.next = null; prev = tmp; } return prev; }
反轉(zhuǎn)鏈表參考自:http://chabaoo.cn/article/136185.htm
四、最后
理解鏈表本身并不難,但做相關(guān)的操作就弄得頭疼,head.next next next next ....(算法這方面我還是薄弱啊..腦子不夠用了.....)寫了兩天就寫了這么點(diǎn)東西...
操作一個(gè)鏈表只需要知道它的頭指針就可以做任何操作了
1、添加數(shù)據(jù)到鏈表中
- 遍歷找到尾節(jié)點(diǎn),插入即可(只要while(temp.next != null),退出循環(huán)就會找到尾節(jié)點(diǎn))
2、遍歷鏈表
- 從首節(jié)點(diǎn)(有效節(jié)點(diǎn))開始,只要不為null,就輸出
3、給定位置插入節(jié)點(diǎn)到鏈表中
- 首先判斷該位置是否有效(在鏈表長度的范圍內(nèi))
- 找到想要插入位置的上一個(gè)節(jié)點(diǎn)
將原本由上一個(gè)節(jié)點(diǎn)的指向交由插入的節(jié)點(diǎn)來指向
上一個(gè)節(jié)點(diǎn)指針域指向想要插入的節(jié)點(diǎn)
4、獲取鏈表的長度
- 每訪問一次節(jié)點(diǎn),變量++操作即可
5、刪除給定位置的節(jié)點(diǎn)
- 首先判斷該位置是否有效(在鏈表長度的范圍內(nèi))
- 找到想要插入位置的上一個(gè)節(jié)點(diǎn)
將原本由上一個(gè)節(jié)點(diǎn)的指向后面一個(gè)節(jié)點(diǎn)
6、對鏈表進(jìn)行排序
- 使用冒泡算法對其進(jìn)行排序
7、找到鏈表中倒數(shù)第k個(gè)節(jié)點(diǎn)
- 設(shè)置兩個(gè)指針p1、p2,讓p2比p1快k個(gè)節(jié)點(diǎn),同時(shí)向后遍歷,當(dāng)p2為空,則p1為倒數(shù)第k個(gè)節(jié)點(diǎn)
8、刪除鏈表重復(fù)數(shù)據(jù)
- 操作跟冒泡排序差不多,只要它相等,就能刪除了~
9、查詢鏈表的中間節(jié)點(diǎn)
- 這個(gè)算法也挺有趣的:一個(gè)每次走1步,一個(gè)每次走兩步,走兩步的遍歷完,然后走一步的指針,那就是中間節(jié)點(diǎn)
10、遞歸從尾到頭輸出單鏈表
- 只要下面還有數(shù)據(jù),那就往下找,遞歸是從最后往前翻。
11、反轉(zhuǎn)鏈表
- 有遞歸和非遞歸兩種方式,我覺得是挺難的。可以到我給出的鏈接上查閱~
PS:每個(gè)人的實(shí)現(xiàn)都會不一樣,所以一些小細(xì)節(jié)難免會有些變動,也沒有固定的寫法,能夠?qū)崿F(xiàn)對應(yīng)的功能即可~
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
參考資料:
- http://www.cnblogs.com/whgk/p/6589920.html
- https://www.cnblogs.com/bywallance/p/6726251.html
相關(guān)文章
spring boot項(xiàng)目沒有mainClass如何實(shí)現(xiàn)打包運(yùn)行
這篇文章主要介紹了spring boot項(xiàng)目沒有mainClass如何實(shí)現(xiàn)打包運(yùn)行,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01使用springboot開發(fā)的第一個(gè)web入門程序的實(shí)現(xiàn)
這篇文章主要介紹了使用springboot開發(fā)的第一個(gè)web入門程序的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04詳解SpringBoot如何創(chuàng)建自定義Starter
Spring Boot的自動配置機(jī)制為開發(fā)人員提供了一種輕松集成和配置各種功能的便捷方式,本文將深入探討在Spring Boot中如何創(chuàng)建自定義Starter,為構(gòu)建模塊化且易維護(hù)的應(yīng)用提供有力的支持,需要的朋友可以參考下2024-02-02詳解java JDK 動態(tài)代理類分析(java.lang.reflect.Proxy)
這篇文章主要介紹了詳解java JDK 動態(tài)代理類分析(java.lang.reflect.Proxy)的相關(guān)資料,需要的朋友可以參考下2017-06-06