1. 程式人生 > >連結串列及常見演算法(java實現)

連結串列及常見演算法(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+" ");
    }
}

未完待續…