最近在回顧數據結構與算法,有部分的算法題用到了棧的思想,提及棧又不得不說鏈表了。數組和鏈表都是線性存儲結構的基礎,棧和隊列都是線性存儲結構的應用~html
本文主要講解單鏈表的基礎知識點,作一個簡單的入門~若是有錯的地方請指正java
提及鏈表,咱們先提一下數組吧,跟數組比較一下就很理解鏈表這種存儲結構了。算法
數組咱們不管是C、Java都會學過:數組
數組的優勢:微信
數組的缺點:數據結構
看完了數組,回到咱們的鏈表:this
n個節點離散分配,彼此經過指針相連,每一個節點只有一個前驅節點,每一個節點只有一個後續節點,首節點沒有前驅節點,尾節點沒有後續節點。spa
鏈表優勢:.net
鏈表缺點:指針
鏈表相關術語介紹,我仍是經過上面那個圖來講明吧:
肯定一個鏈表咱們只須要頭指針,經過頭指針就能夠把整個鏈表都能推導出來了~
鏈表又分了好幾類:
操做鏈表要時刻記住的是:
算法:
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;
}
}
複製代碼
向鏈表中插入數據:
/** * 向鏈表添加數據 * * @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;
}
複製代碼
上面咱們已經編寫了增長方法,如今遍歷一下看一下是否正確~~~
從首節點開始,不斷日後面找,直到後面的節點沒有數據:
/** * 遍歷鏈表 * * @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;
}
}
複製代碼
結果:
/** * 插入節點 * * @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;
}
}
複製代碼
獲取鏈表的長度就很簡單了,遍歷一下,每獲得一個節點+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;
}
複製代碼
刪除某個位置上的節點實際上是和插入節點很像的, 一樣都要找到上一個節點。將上一個節點的指針域改變一下,就能夠刪除了~
/** * 根據位置刪除節點 * * @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;
}
}
複製代碼
前面已經講過了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;
}
}
}
}
複製代碼
這個算法挺有趣的:設置兩個指針p一、p2,讓p2比p1快k個節點,同時向後遍歷,當p2爲空,則p1爲倒數第k個節點
/** * 找到鏈表中倒數第k個節點(設置兩個指針p一、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;
}
複製代碼
這裏以前有問題,你們能夠到:blog.csdn.net/ifollowrive…
進行參考
這個算法也挺有趣的:一個每次走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;
}
複製代碼
/** * 經過遞歸從尾到頭輸出單鏈表 * * @param head 頭節點 */
public static void printListReversely(Node head) {
if (head != null) {
printListReversely(head.next);
if (head.data != null) {
System.out.println(head.data);
}
}
}
複製代碼
/** * 實現鏈表的反轉 * * @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)
,退出循環就會找到尾節點)PS:每一個人的實現都會不同,因此一些小細節不免會有些變更,也沒有固定的寫法,可以實現對應的功能便可~
參考資料:
若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同窗,能夠關注微信公衆號:Java3y