一、前言
最近在回顧資料結構與演算法,有部分的演算法題用到了棧的思想,說起棧又不得不說連結串列了。陣列和連結串列都是線性儲存結構的基礎,棧和佇列都是線性儲存結構的應用~
本文主要講解單連結串列的基礎知識點,做一個簡單的入門~如果有錯的地方請指正
二、回顧與知新
說起連結串列,我們先提一下陣列吧,跟陣列比較一下就很理解連結串列這種儲存結構了。
2.1回顧陣列
陣列我們無論是C、Java都會學過:
- 陣列是一種連續儲存線性結構,元素型別相同,大小相等
陣列的優點:
- 存取速度快
陣列的缺點:
- 事先必須知道陣列的長度
- 插入刪除元素很慢
- 空間通常是有限制的
- 需要大塊連續的記憶體塊
- 插入刪除元素的效率很低
2.2連結串列說明
看完了陣列,回到我們的連結串列:
- 連結串列是離散儲存線性結構
n個節點離散分配,彼此通過指標相連,每個節點只有一個前驅節點,每個節點只有一個後續節點,首節點沒有前驅節點,尾節點沒有後續節點。
連結串列優點:
- 空間沒有限制
- 插入刪除元素很快
連結串列缺點:
- 存取速度很慢
連結串列相關術語介紹,我還是通過上面那個圖來說明吧:
確定一個連結串列我們只需要頭指標,通過頭指標就可以把整個連結串列都能推匯出來了~
連結串列又分了好幾類:
- 單向連結串列
- 一個節點指向下一個節點
- 雙向連結串列
- 一個節點有兩個指標域
- 迴圈連結串列
- 能通過任何一個節點找到其他所有的節點,將兩種(雙向/單向)連結串列的最後一個結點指向第一個結點從而實現迴圈
操作連結串列要時刻記住的是:
- 節點中指標域指向的就是一個節點了!
三、Java實現連結串列
演算法:
- 遍歷
- 查詢
- 清空
- 銷燬
- 求長度
- 排序
- 刪除節點
- 插入節點
ps:我將head節點定義在成員變數上:
private static Node head = new Node();
複製程式碼
首先,我們定義一個類作為節點
- 資料域
- 指標域
為了操作方便我就直接定義成public了。
public class Node {
//資料域
public Integer data;
//指標域,指向下一個節點
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建立連結串列(增加節點)
向連結串列中插入資料:
- 找到尾節點進行插入
- 即使頭節點.next為null,不走while迴圈,也是將頭節點與新節點連線的(我已經將head節點初始化過了,因此沒必要判斷頭節點是否為null)~
/**
* 向連結串列新增資料
*
* @param value 要新增的資料
*/
public static void addData(int value) {
//初始化要加入的節點
Node newNode = new Node(value);
//臨時節點
Node temp = head;
// 找到尾節點
while (temp.next != null) {
temp = temp.next;
}
// 已經包括了頭節點.next為null的情況了~
temp.next = newNode;
}
複製程式碼
3.2遍歷連結串列
上面我們已經編寫了增加方法,現在遍歷一下看一下是否正確~~~
從首節點開始,不斷往後面找,直到後面的節點沒有資料:
/**
* 遍歷連結串列
*
* @param head 頭節點
*/
public static void traverse(Node head) {
//臨時節點,從首節點開始
Node temp = head.next;
while (temp != null) {
if (temp.data != null) {
System.out.println("關注公眾號Java3y:" + temp.data);
}
//繼續下一個
temp = temp.next;
}
}
複製程式碼
結果:
3.3插入節點
- 插入一個節點到連結串列中,首先得判斷這個位置是否是合法的,才能進行插入~
- 找到想要插入的位置的上一個節點就可以了~
/**
* 插入節點
*
* @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;
}
//臨時節點,從頭節點開始
Node temp = head;
//記錄遍歷的當前位置
int currentPos = 0;
//初始化要插入的節點
Node insertNode = new Node(value);
while (temp.next != null) {
//找到上一個節點的位置了
if ((index - 1) == currentPos) {
//temp表示的是上一個節點
//將原本由上一個節點的指向交由插入的節點來指向
insertNode.next = temp.next;
//將上一個節點的指標域指向要插入的節點
temp.next = insertNode;
return;
}
currentPos++;
temp = temp.next;
}
}
複製程式碼
3.4獲取連結串列的長度
獲取連結串列的長度就很簡單了,遍歷一下,每得到一個節點+1即可~
/**
* 獲取連結串列的長度
* @param head 頭指標
*/
public static int linkListLength(Node head) {
int length = 0;
//臨時節點,從首節點開始
Node temp = head.next;
// 找到尾節點
while (temp != null) {
length++;
temp = temp.next;
}
return length;
}
複製程式碼
3.5刪除節點
刪除某個位置上的節點其實是和插入節點很像的, 同樣都要找到上一個節點。將上一個節點的指標域改變一下,就可以刪除了~
/**
* 根據位置刪除節點
*
* @param head 頭指標
* @param index 要刪除的位置
*/
public static void deleteNode(Node head, int index) {
//首先需要判斷指定位置是否合法,
if (index < 1 || index > linkListLength(head) + 1) {
System.out.println("刪除位置不合法。");
return;
}
//臨時節點,從頭節點開始
Node temp = head;
//記錄遍歷的當前位置
int currentPos = 0;
while (temp.next != null) {
//找到上一個節點的位置了
if ((index - 1) == currentPos) {
//temp表示的是上一個節點
//temp.next表示的是想要刪除的節點
//將想要刪除的節點儲存一下
Node deleteNode = temp.next;
//想要刪除節點的下一個節點交由上一個節點來控制
temp.next = deleteNode.next;
//Java會回收它,設定不設定為null應該沒多大意義了(個人覺得,如果不對請指出哦~)
deleteNode = null;
return;
}
currentPos++;
temp = temp.next;
}
}
複製程式碼
3.6對連結串列進行排序
前面已經講過了8種的排序演算法了【八種排序演算法總結】,這次挑簡單的氣泡排序吧(其實我是想寫快速排序的,嘗試了一下感覺有點難.....)
/**
* 對連結串列進行排序
*
* @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找到連結串列中倒數第k個節點
這個演算法挺有趣的:設定兩個指標p1、p2,讓p2比p1快k個節點,同時向後遍歷,當p2為空,則p1為倒數第k個節點
/**
* 找到連結串列中倒數第k個節點(設定兩個指標p1、p2,讓p2比p1快k個節點,同時向後遍歷,當p2為空,則p1為倒數第k個節點
*
* @param head
* @param k 倒數第k個節點
*/
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個節點
for (int i = 0; i < k - 1; i++)
p2 = p2.next;
// 只要p2為null,那麼p1就是倒數第k個節點了
while (p2.next != null) {
p2 = p2.next;
p1 = p1.next;
}
return p1;
}
複製程式碼
3.8刪除連結串列重複資料
這裡之前有問題,大家可以到:blog.csdn.net/ifollowrive…
進行參考
3.9查詢連結串列的中間節點
這個演算法也挺有趣的:一個每次走1步,一個每次走兩步,走兩步的遍歷完,然後走一步的指標,那就是中間節點
/**
* 查詢單連結串列的中間節點
*/
public static Node searchMid(Node head) {
Node p1 = head;
Node p2 = head;
// 一個走一步,一個走兩步,直到為null,走一步的到達的就是中間節點
while (p2 != null && p2.next != null && p2.next.next != null) {
p1 = p1.next;
p2 = p2.next.next;
}
return p1;
}
複製程式碼
3.10通過遞迴從尾到頭輸出單連結串列
/**
* 通過遞迴從尾到頭輸出單連結串列
*
* @param head 頭節點
*/
public static void printListReversely(Node head) {
if (head != null) {
printListReversely(head.next);
if (head.data != null) {
System.out.println(head.data);
}
}
}
複製程式碼
3.11反轉連結串列
/**
* 實現連結串列的反轉
*
* @param head 連結串列的頭節點
*/
public static Node reverseList(Node head) {
Node pre = null;
Node cur = head;
while (cur != null) {
Node next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
// 翻轉完,使用下面的程式碼進行遍歷吧:
public static void traverse4Reverse(Node head) {
//臨時節點,從首節點開始
Node temp = head;
while (temp != null) {
if (temp.data != null) {
System.out.println("關注公眾號Java3y:" + temp.data);
}
//繼續下一個
temp = temp.next;
}
}
複製程式碼
反轉連結串列參考自:
四、最後
理解連結串列本身並不難,但做相關的操作就弄得頭疼,
head.next next next next ....
(演算法這方面我還是薄弱啊..腦子不夠用了.....)寫了兩天就寫了這麼點東西...
操作一個連結串列只需要知道它的頭指標就可以做任何操作了
- 新增資料到連結串列中
- 遍歷找到尾節點,插入即可(只要
while(temp.next != null)
,退出迴圈就會找到尾節點)
- 遍歷找到尾節點,插入即可(只要
- 遍歷連結串列
- 從首節點(有效節點)開始,只要不為null,就輸出
- 給定位置插入節點到連結串列中
- 首先判斷該位置是否有效(在連結串列長度的範圍內)
- 找到想要插入位置的上一個節點
- 將原本由上一個節點的指向交由插入的節點來指向
- 上一個節點指標域指向想要插入的節點
- 獲取連結串列的長度
- 每訪問一次節點,變數++操作即可
- 刪除給定位置的節點
- 首先判斷該位置是否有效(在連結串列長度的範圍內)
- 找到想要插入位置的上一個節點
- 將原本由上一個節點的指向後面一個節點
- 對連結串列進行排序
- 使用冒泡演算法對其進行排序
- 找到連結串列中倒數第k個節點
- 設定兩個指標p1、p2,讓p2比p1快k個節點,同時向後遍歷,當p2為空,則p1為倒數第k個節點
- 刪除連結串列重複資料
- 操作跟氣泡排序差不多,只要它相等,就能刪除了~
- 查詢連結串列的中間節點
- 這個演算法也挺有趣的:一個每次走1步,一個每次走兩步,走兩步的遍歷完,然後走一步的指標,那就是中間節點
- 遞迴從尾到頭輸出單連結串列
- 只要下面還有資料,那就往下找,遞迴是從最後往前翻。
- 反轉連結串列
- 有遞迴和非遞迴兩種方式,我覺得是挺難的。可以到我給出的連結上查閱~
PS:每個人的實現都會不一樣,所以一些小細節難免會有些變動,也沒有固定的寫法,能夠實現對應的功能即可~
參考資料:
如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關注微信公眾號:Java3y