揭開連結串列的真面目

Java旅途發表於2020-09-01

連結串列是一種常見的資料結構,連結串列是由一連串的結點組成,這個節點就是鏈結點,每個鏈結點都由資料域指標域兩部分組成。

使用連結串列結構可以克服陣列結構需要預先知道資料大小的缺點,連結串列結構可以充分利用計算機記憶體空間,實現靈活的記憶體動態管理。但是連結串列失去了陣列隨機讀取的優點,同時連結串列由於增加了結點的指標域,空間開銷比較大。

連結串列比較好的一種理解是:將連結串列看成一個火車,每個車廂之間都是相互連線的,只要找到火車頭,就可以找到具體的車身。連結串列也是,我們只關心它的頭。

一 單向連結串列

1.1 單向連結串列原理圖

單向連結串列的一個鏈結點包含資料域和下一個鏈結點的指標。頭結點也包含資料域和指標域,但是一般為了方便查詢,頭節點不寫資料,最後一個結點的指標指向空。

1.2 實現單向連結串列的儲存等操作

建立一個鏈結點的實體類

public class Node {

    // 資料域
    public long data;
    // 指標域
    public Node next;

    public Node(long value){
        this.data = value;
    }
}

1.2.1 插入一個節點

在頭節點後插入一個結點,第一步需要將新插入的結點指向頭結點指向的結點,第二部將頭結點指向新插入的結點。插入結點只需要改變一個引用,所以複雜度為O(1)。

public class LinkList {

    private Node head;
    /**
     * 在頭節點之後插入一個節點
     */
    public void insertFirst(long value){
        Node node = new Node(value);
        node.next = head;
        head = node;
    }
}

1.2.2 頭結點後刪除一個結點

在頭結點後刪除一個結點,就是讓頭結點指向這個結點的下一個結點。複雜度也是O(1)。

public Node deleteFirst(){
    Node tmp = head;
    head = tmp.next;
    return tmp;
}

1.2.3 根據資料域查詢結點

查詢需要比對每個結點的資料,理論上查詢一個結點平均需要N/2次,所以複雜度為O(N)。

public Node find(long value){

    Node current = head;
    while (current.data != value){
        if(current.next == null){
            return null;
        }
        current = current.next;
    }
    return current;
}

1.2.4 根據資料與刪除結點

查詢需要比對每個結點的資料,理論上刪除一個結點平均需要N/2次,所以複雜度為O(N)。

public Node delete(int value){
    Node current = head;
    // 當前結點的前一個結點
    Node pre = head;
    while (current.data != value){
        if(current.next == null){
            return null;
        }
        pre = current;
        current = current.next;
    }
    if(current == head){
        head = head.next;
    }else{
        pre.next = current.next;
    }
    return current;
}

二 雙端連結串列

2.1 雙端連結串列原理圖

雙端連結串列是在單向連結串列的基礎上,頭結點增加了一個尾結點的引用。

2.2 實現雙端連結串列的儲存等操作

2.2.1 從頭部插入結點

如果連結串列為空,則設定尾結點就是新新增的結點。複雜度為O(1)。

public class FirstLastLinkList {

    private Node first;
    private Node last;
    /**
     * 在頭結點之後插入一個節點
     */
    public void insertFirst(long value){
        Node node = new Node(value);
        if(first == null){
            last = node;
        }
        node.next = first;
        first = node;
    }
}

2.2.2 從尾部插入結點

如果連結串列為空,則設定頭結點為新新增的結點,否則設定尾結點的後一個結點為新新增的結點。複雜度為O(1)。

public void insertLast(long value){
    Node node = new Node(value);
    if(first == null){
        first = node;
    }else{
        last.next = node;
    }
    last = node;
}

2.2.3 從頭部進行刪除

判斷頭結點是否有下一個結點,如果沒有則設定尾結點為null,複雜度為O(1)。

public Node deleteFirst(){

    Node tmp = first;
    if(first.next == null){
        last = null;
    }
    first = tmp.next;
    return tmp;
}

三 雙向連結串列

3.1 雙向連結串列原理圖

每個結點除了儲存對後一個結點的引用外,還儲存著對前一個結點的引用。

3.2 實現雙向連結串列的儲存等操作

鏈結點實體類

public class Node {

    // 資料域
    public long data;
    // 後一個結點指標域
    public Node1 next;
    // 前一個結點指標域
    public Node prev;
    
    public Node(long value){
        this.data = value;
    }
}

3.2.1 從頭部插入結點

如果連結串列為空,則設定尾結點為新新增的結點,如果不為空,還需要設定頭結點的前一個結點為新新增的結點。插入結點只需要改變兩個結點的引用,所以複雜度為O(1)。

public class DoubleLinkList {

    private Node first;
    private Node last;

    /**
     * 在頭結點之後插入一個節點
     */
    public void insertFirst(long value){
        Node node = new Node(value);
        if(first == null){
            last = node;
        } else{
            first.prev = node;
        }
        node.next = first;
        first = node;
    }
}

3.2.2 從尾部插入結點

如果連結串列為空,則設定頭結點為新新增的結點,否則設定尾結點的後一個結點為新新增的結點。同時設定新新增的結點的前一個結點為尾結點。插入結點只需要改變1個結點的引用,所以複雜度為O(1)。

public void insertLast(long value){
    Node node = new Node(value);
    if(first == null){
        first = node;
    }else{
        last.next = node;
        node.prev = last;
    }
    last = node;
}

3.2.3 從頭部刪除結點

判斷頭結點是否有下一個結點,如果沒有則設定尾結點為null,否則設定頭結點的下一個結點的prev為null。複雜度也為O(1)。

public Node deleteFirst(){

    Node tmp = first;
    if(first.next == null){
        last = null;
    }else{
        first.next.prev = null;
    }
    first = tmp.next;
    return tmp;
}

3.2.4 從尾部刪除結點

如果頭結點後沒有其他結點,則設定頭結點為null,否則設定尾結點的前一個結點的next為null,設定尾結點為前一個結點。複雜度為O(1)。

public Node deleteLast(){

    Node tmp = last; 
    if(first.next == null){
        first = null;
    }else{
        last.prev.next = null;  
    }
    last = last.prev;
    return last;
}

四 總結

連結串列包含一個頭結點和多個結點,頭結點包含一個引用,這個引用通常叫做first,它指向連結串列的第一個鏈結點。結點的next為null,則意味著這個結點時尾結點。與陣列相比,連結串列更適合做插入、刪除操作,而查詢操作的複雜度更高。還有一個優勢就是連結串列不需要初始化記憶體大小,不會造成記憶體溢位(陣列中插入元素個數超過陣列長度)或記憶體浪費(宣告的陣列長度比實際放的元素長)。


點關注、不迷路

如果覺得文章不錯,歡迎關注點贊收藏,你們的支援是我創作的動力,感謝大家。

如果文章寫的有問題,請不要吝嗇,歡迎留言指出,我會及時核查修改。

如果你還想更加深入的瞭解我,可以微信搜尋「Java旅途」進行關注。回覆「1024」即可獲得學習視訊及精美電子書。每天7:30準時推送技術文章,讓你的上班路不在孤獨,而且每月還有送書活動,助你提升硬實力!

相關文章