連結串列及常見演算法(java實現)
連結串列及常見演算法(java實現)
注意:本文中的連結串列預設都是帶頭節點的。
1.連結串列的實現
實現很簡單,只有兩個值,一個data,一個指向下一個節點的指標。由於java語言的特點,這裡的指標其實是引用,但為了方便敘述,下文全部稱為指標。
public class LNode {
int data;
LNode next;
}
2.連結串列的逆序
就地逆序
就地逆序需要三個指標,遍歷的過程中就像是排著一排的人一個一個轉身,cur標識現在轉身的節點,pre和next則記錄轉身前前後的兩個人,以保證重建連線。
這個方法只需要對連結串列進行一次遍歷,時間複雜度為O(N)。需要常數個額外變數記錄前後節點,空間複雜度為O(1)。
總的來說,是一箇中規中矩的演算法。
//就地逆轉
public static void Reverse_still(LNode head) {
if(head == null || head.next == null) {
return ;
}
LNode pre = null;
LNode cur = null;
LNode next = null;
cur = head.next;//指標移動
next = cur.next;
cur.next = null;//第一個節點轉為第二個節點
//繼續下一個移動
pre = cur;
cur = next;
while(cur.next !=null) {
next = cur.next;
cur.next = pre;
//繼續移動
pre = cur;
cur = next;
}
cur.next = pre;
head.next = cur;
}
遞迴逆序
因為遞迴部分只能對沒有頭結點的連結串列進行處理,所以分成了兩個函式進行處理。
個人以為遞迴永遠都是相對優雅的一種方法,並且是相對難理解一些的。這個方法,我覺得就像是把頭節點以後的部分看成一個黑盒,工作已經全部完成,演算法裡面需要體現的就是完成最後一步。但實際執行中,就像是拆一個俄羅斯套娃,只要記憶體足夠大,且做好判斷條件,就可以一層一層拆下去,直到任務的完成。
我理解的時候,遇到的難點就是這個黑盒假設,但想通以後,就會感覺很明瞭,很優雅。
這個方法和就地逆序差不多,都是對連結串列進行一次遍歷,但因為不斷的呼叫自己,就要不斷的進行壓棧和彈棧操作,所以效能必然會有所欠缺。(但是程式碼寫的好看啊!!!)
//遞迴逆序(加上頭節點)
public static void RecursiveReverse_head(LNode head) {
if(head == null) {
return ;
}
LNode firstNode = head.next;
LNode newhead = RecursiveReverse(firstNode);
head.next = newhead;
}
//對沒有頭節點的連結串列進行處理
private static LNode RecursiveReverse(LNode head) {
if(head == null || head.next == null) {
return head;
}else {
LNode newhead = RecursiveReverse(head.next);
head.next.next = head;
head.next = null;
return newhead;
}
}
插入逆序
插入逆序時間複雜度同樣為O(N)。但是,和就地逆序相比,它不用儲存前驅節點,和遞迴相比,它不用頻繁的呼叫函式,所以它的效率最高。
我在後面寫一些演算法的時候發現,插入法只適合處理帶頭節點的連結串列,一旦有需求逆序沒有頭節點的連結串列時,就地逆序顯得更簡單易用。因為插入法能省下前驅節點的原因就是,它有效的利用了頭節點的作用,每次插入都是以頭節點作為標準,不斷的在頭節點,和head.next()之間進行插入操作。一旦沒有頭節點,則需要考慮程式結束後對頭節點的處理,這樣的話和就地逆序對比,就會有點得不償失。
//插入逆序
public static void Reverse_insert(LNode head) {
if(head == null || head.next == null) {
return ;
}
//初始化指標
LNode cur = null;
LNode next = null;
//指標開始移動,從第二個節點開始
cur = head.next.next;
//清空第一個節點的指標,使其成為尾節點
head.next.next = null;
while(cur != null){
next = cur.next;
//插入節點
cur.next = head.next;
head.next = cur;
cur = next;//開始下一次迴圈
}
}
測試程式碼
以下為上面程式碼的執行類,當時有點懶,直接寫了個for迴圈構造連結串列,沒有單獨封裝出一個方法出來,後續的會逐漸完善。
public static void main(String[] args) {
LNode head = new LNode();
head.next = null;
LNode tmp = null;
LNode cur = head;
for(int i=1; i<8; i++) {
tmp = new LNode();
tmp.data = i;
tmp.next = null;
cur.next = tmp;
cur = tmp;
}
System.out.print("逆序前:");
for(cur = head.next; cur != null; cur = cur.next) {
System.out.print(cur.data+" ");
}
System.out.println();
Reverse_insert(head);
//Reverse_still(head);
//RecursiveReverse_head(head);
System.out.print("逆序後:");
for(cur = head.next; cur != null; cur = cur.next) {
System.out.print(cur.data+" ");
}
}
未完待續…