java數據結構——3鏈表(LinkedList)

三、鏈表(LinkedList)

下面將有一種新的數據存儲結構,它可以解決上面的一些問題。這種數據存儲結構就是鏈表。鏈表可能是繼數組之後第二種使用最廣泛的通用存儲結構。

  • 單鏈表
  • 雙端鏈表
  • 有序鏈表
  • 雙向列表
  • 有迭代器的列表

鏈表與數組一樣,都作爲數據的基本存儲結構,但是在存儲原理上二者是不同的。在數組中,數據是存儲在一段連續的內存空間中,我們可以通過下標來訪問數組中的元素;而在鏈表中,元素是存儲在不同的內存空間中,前一個元素的位置維護了後一個元素在內存中的地址,在Java中,就是前一個元素維護了後一個元素的引用。在本教程我們,我們將鏈表中的每個元素稱之爲一個節點(Node)。對比數組, 鏈表的數據結構可以用下圖表示:

這裏寫圖片描述

這張圖顯示了一個鏈表的數據結構,鏈表中的每個Node都維護2個信息:一個是這個Node自身存儲的數據Data,另一個是下一個Node的引用,圖中用Next表示。對於最後一個Node,因爲沒有下一個元素了,所以其並沒有引用其他元素,在圖中用紫色框來表示。

這張圖主要顯示的是鏈表中Node的內部結構和Node之間的關係。一般情況下,我們在鏈表中還要維護第一個Node的引用,原因是在鏈表中訪問數據必須通過前一個元素才能訪問下一個元素,如果不知道第一個Node的話,後面的Node都不可以訪問。事實上,對鏈表中元素的訪問,都是從第一個Node中開始的,第一個Node是整個鏈表的入口;而在數組中,我們可以通過下標進行訪問元素。

public class Node {
    //Node中維護的數據
    private Object data;
    //下一個元素的引用
    private Node next;
    // setters and getters
}

1、單鏈表Java實現

1)、SingleLinkList中要維護的信息:維護第一個節點(firstNode)的引用,作爲整個鏈表的入口;

2)、插入操作分析:基於鏈表的特性,插入到鏈表的第一個位置是非常快的,因爲只要改變fisrtNode的引用即可。因此對於單鏈表,我們會提供addFirst方法。

3)、查找操作分析:從鏈表的fisrtNode開始進行查找,如果確定Node中維護的data就是我們要查找的數據,即返回,如果不是,根據next獲取下一個節點,重複這些步驟,直到找到最後一個元素,如果最後一個都沒找到,返回null。

4)、刪除操作分析 : 首先查找到要刪除的元素節點,同時將這個節點的上一個節點和下一個節點也要記錄下來,只要將上一個節點的next引用直接指向下一個節點即可,這就相當於 刪除了這個節點。如果要刪除的是第一個節點,直接將LinkList的firstNode指向第二個節點即可。如果刪除的是最後一個節點,只要將上一個節 點的next引用置爲null即可。上述分析,可以刪除任意節點,具有通用性但是效率較低。通常情況下,我們還會提供一個removeFirst方法,因爲這個方法效率較高,同樣只要改變fisrtNode的引用即可。

此外,根據情況而定,可以選擇是否要維護鏈表中元素的數量size,不過這不是實現一個鏈表必須的核心特性。

下面是代碼實現:

public class SingleLinkList<V> {
    protected Node firstNode = null;// 鏈表的第一個節點
    protected int size;// 鏈表中維護的節點總數

    /**
     * 添加到鏈表最前面
     * @return 
     */
    public Node addFirst(V v) {
        Node node = new Node();
        node.setData(v);
        Node currentFirst = firstNode;
        node.setNext(currentFirst);
        firstNode = node;
        size++;
        return node;
    }

    /**
     * 如果鏈表中包含要刪除的元素,刪除第一個匹配上的要刪除的元素
     */
    public void remove(V v) {
        if (size == 0) {
            return;
        }
        if (size == 1) {
            firstNode = null;
            size--;
            return;
        }
        if (Objects.equals(firstNode.getData(), v)) {
            firstNode = firstNode.getNext();
            size--;
        }
        Node pre = firstNode;
        Node next = pre.getNext();
        while (next != null) {
            if (Objects.equals(next.getData(), v)) {
                pre.setNext(next.getNext());
                size--;
                next = pre.getNext();
            }else {
                pre = pre.getNext();
                next = pre.getNext();

            }
        }
    }
    /**
     * 是否包含,包含返回true,不包含返回false
     */
    public boolean contains(V v){
        if (size == 0) {
            return false;
        }
        Node current = firstNode;
        while (current != null) {
            if (Objects.equals(v, current.getData())) {
                return true;
            }
            current = current.getNext();
        }
        return false;
    }
    /**
     * 獲取第一個元素
     */
    public V getFirst(){
        if (size == 0) {
            return null;
        }
        return (V)firstNode.getData();
    }
    /**
     * 刪除第一個元素
     */
    public V removeFirst(){
        if (size == 0) {
            return null;
        }
        Node temp = firstNode.getNext();
        firstNode = temp;
        if (temp == null) {
            return null;
        }
        return (V)temp.getData();
    }
    /**
     * 打印鏈表的所有元素
     */
    public void showAll(){
        if (size != 0) {
            Node current = firstNode;
            while (current != null){
                System.out.print(current.getData() + "/");
                current = current.getNext();
            }
        }
    }
    /**
     * 獲取元素個數
     */
    public int getSize(){
        return size;
    }
}

2、雙端鏈表Java實現

雙端鏈表與傳統的鏈表非常類似,但是它有一個新增的特性:即對鏈表中最後一個節點的引用lastNode。我們可以像在單鏈表中在表頭插入一個元素一樣,在鏈表的尾端插入元素。如果不維護對最後一個節點的引用,我們必須要迭代整個鏈表才能得到最後一個節點,然後再插入,效率很低。因此我們在雙鏈表中添加一個addLast方法,用於添加節點到末尾。

addLast方法分析:直接將鏈表中維護的lastNode的next引用指向新的節點,再將lastNode的引用指向新的節點即可。

因爲單鏈表中,大部分的代碼在雙端鏈表中都可以重用,所以此處我們編寫的DoubleLinkList只要繼承SingleLinkList,添加必要的屬性和方法支持從尾部操作即可。

下面是代碼實現:

public class DoubleLinkList<V> extends SingleLinkList<V> {
    protected Node lastNode = null;

    /**
     * 添加到鏈表最後
     */
    public void addLast(V v) {
        Node node = new Node();
        node.setData(v);
        if (size == 0) {// 說明沒有任何元素,說明第一個元素
            firstNode = node;
        } else {// 如果有元素,將最後一個節點的next指向新的節點即可
            /*
             * 這裏有一個要注意的地方: 當size=1的時候,firstNode和lastNode指向同一個引用
             * 因此lastNode.setNext時,fisrtNode的next引用也會改變;
             * 當size!=1的時候,lastNode的next的改變與firstNode無關
             */
            lastNode.setNext(node);
        }

        // 將lastNode引用指向新node
        lastNode = node;
        size++;
    }

    /**
     * 當鏈表中沒有元素時,清空lastNode引用
     */
    @Override
    public void remove(V v) {
        super.remove(v);
        if (size == 0) {
            lastNode = null;
        }
    }
    /**
     * 因爲在SingleLinkList中並沒有維護lastNode的信息,我們要自己維護
     */
    @Override
    public Node addFirst(V v) {
        Node node = super.addFirst(v);
        if (size == 1) {// 如果鏈表爲size爲1,將lastNode指向當前節點
            lastNode = node;
        }
        return node;
    }

}

3、有序鏈表

所謂有序鏈表,就是鏈表中Node節點之間的引用關係是根據Node中維護的數據data的某個字段爲key值進行排序的。爲了在一個有序鏈表中插入,算法必須首先搜索鏈表,直到找到合適的位置:它恰好在第一個比它大的數據項前面。

當算法找到了要插入的數據項的位置,用通常的方式插入數據項:把新的節點Node指向下一個節點,然後把前一個節點Node的next字段改爲指向新的節點。然而,需要考慮一些特殊情況,連接點有可能插入在表頭或者表尾。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章