數據結構-java鏈表【單/雙向鏈表實現,原理,跳躍表】

鏈表在物理上/邏輯上

鏈表是一種物理存儲單元非連續、非順序的存儲結構。
數據元素的邏輯順序是通過鏈表中的指針鏈接次序實現的。

鏈表每個結點的組成

鏈表由一系列結點(鏈表中每一個元素稱爲結點)組成,結點可以在運行時動態生成。
每個結點包括兩個部分:一個是存儲數據元素的數據域,另一個是存儲下一個結點地址的指針域

鏈表使用過程中的時間複雜度

鏈表在插入的時候可以達到O(1)的複雜度,比另一種線性表順序錶快得多,但是查找一個節點或者訪問特定編號的節點則需要O(n)的時間,而線性表和順序表相應的時間複雜度分別是O(logn)和O(1)。
簡單說,查詢慢,增刪快。
因爲需要加入一個指針域,所以空間開銷比較大。

單向鏈表


它存儲的數據分散在內存中,每個結點只能也只有它能知道下一個結點的存儲位置。由N各節點(Node)組成單向鏈表,每一個Node記錄本Node的數據及下一個Node。向外暴露的只有一個頭節點(Head),我們對鏈表的所有操作,都是直接或者間接地通過其頭節點來進行的。

嘗試自己寫一個簡單的

首先準備好結點(class Node)實現一個單向的,所以只需要準備當前結點的數據,和next地址就可以了。
具體的增刪改查方法就是new Node並且處理next 和data就可以了。

public class MyLink<E> {

    /**
     * 頭結點,聲明鏈表的時候,實例化一個空的頭結點
     */
    Node head = null;

    int size = 0;


    /**
     * 鏈表插入數據 插入的數據指向的對象,是否存在下一個結點,
     * @param d
     */
    private synchronized void addLastNode(E d){
        final Node<E> newNode = new Node(d);
        if(head ==null){
            head = newNode;
            size++;
            return;
        }
        //定義一個臨時的結點  判斷頭結點下面的值,且不改變實際頭結點的值
        Node<E> tmp = head;
        //當前結點的下一個結點是否有值  如果有,當前結點變爲next結點
        while(tmp.next!=null){
            tmp = tmp.next;
        }
        //直到last Node  給當前結點的下一個結點賦爲需要添加的值
        tmp.next = newNode;
        size++;
    }

    /**
     * 給頭結點添加元素
     * @param d
     */
    private synchronized  void addHeadNode(E d){
        //聲明一個頭結點 next爲null,需要將原來的頭結點 變成newNode的next
        final Node<E> newNode = new Node(d);
        newNode.next = head;
        head = newNode;
        size++;
    }

    /**
     * 給中間結點添加新結點
     * @param d
     */
    private synchronized  void addSizeNode(int index,E d){
        Node<E> newNode = null;
        //插入中間元素
        for (int i = 0; i < index-1; i++){
            //next index次
            newNode = head.next;
        }
        //得到需要插入的結點的位置,往該節點下插入新的
        Node<E> addNode = new Node<>(d);
        addNode.next = newNode.next;
        newNode.next = addNode;
        size++;
    }

    /**
     * 實例一個刪除頭部元素
     * @param index
     */
    public synchronized void deleteHeadNode(){
        //現有頭部元素作爲新的頭部
        Node newHead = head.next;

        //原有頭部data設置爲null;
        head.data = null;
        head.next = null;
        head = newHead;
        size--;

    }


    public static void main(String[] args) {
        MyLink<SystemA> myLink = new MyLink<>();
        myLink.addHeadNode(new SystemA("001","張三","true"));
        myLink.addLastNode(new SystemA("002","李四","true"));
        myLink.addLastNode(new SystemA("004","王五","true"));
        myLink.addSizeNode(2,new SystemA("003","李四","true"));
        myLink.deleteHeadNode();
        System.out.println("長度是:"+myLink.size);
        System.out.println("頭元素是:"+myLink.head.data.toString());
        System.out.println("頭.next元素是:"+myLink.head.next.data.toString());
    }
}
class Node<E>{
    Node next = null;//結點的引用,指向下一個結點
    E data;//結點的對象,內容
    public Node(E data){
        this.data = data;
    }
}

自己實現的過程中,發現刪除元素,新增元素很容易實現,但是需要先找到元素之後再做這些操作才簡單。數據的二分法就比較的好。
邊界值問題需要考慮很多,比如當前鏈表的長度,新增的時候是否越界、刪除的時候是否越界、長度爲0的時候等等等。
那些問題大佬們早就考慮到了
一般實現好的鏈表會有限定好的值,而且有跳躍表來簡化鏈表的查找數據。

單向鏈表反轉

單向鏈表反轉的方式有比較多的,很粗暴的拆了重組。
從頭結點一直next,直到node.next==null,取當前結點爲新的頭結點【head】並且切斷該結點。
繼續切已經被切過尾巴的鏈表,得到的數據添加到新的鏈表中…直到老的鏈表爲null,返回新的鏈表表頭就可以了。時間複雜度O(n)。

雙向鏈表

雙向鏈表也是同樣的準備Node

public class MyLinkDou<E> {

    int size = 0;

    Node head = null;
    Node last = null;

    class Node {
        //實際存儲的數據
        public E e;
        //下一個結點
        public Node next;
        //上一個結點
        public Node pre;

        /**
         *
         * @param e
         */
        public Node(E e){
            this.e = e;
            next = null;
            pre = null;
        }
    }
}

其他的增刪改查與單向的挺像的。

/**
     * 根據下標找到當前結點的上一個結點
     * @param index
     * @return
     */
    public Node findpreNode(int index){
        Node presend = head;
        int nowindex = -1;

        while(presend.next != null){
            if( nowindex== index - 1){
                return presend;
            }
            presend = presend.next;
            nowindex++;
        }
        return null;
    }

    /**
     * 根據下標插入一個數據
     * @param index
     * @param e
     */
    public void addIndex(int index,E e){
        if(index <0||index>=size)
            return;
        Node node = new Node(e);
        Node preNode = this.findpreNode(index);
        node.next = preNode.next;
        preNode.next.pre = node;
        preNode.next = node;
        node.pre = preNode;
        size++;
    }

閱讀LinkedList源碼文檔註釋 哇哦,看着好複雜,但是能看到官方的鏈表寫的有多麼的強大。
首先多線程下不安全

 *<p><strong>Note that this implementation is not synchronized.</strong>
 * If multiple threads access a linked list concurrently, and at least
 * one of the threads modifies the list structurally, it <i>must</i> be
 * synchronized externally.  (A structural modification is any operation
 * that adds or deletes one or more elements; merely setting the value of
 * an element is not a structural modification.)  This is typically
 * accomplished by synchronizing on some object that naturally
 * encapsulates the list.

其次是雙線鏈表

 * Doubly-linked list implementation of the {@code List} and {@code Deque}
 * interfaces.  Implements all optional list operations, and permits all
 * elements (including {@code null}).

源碼裏寫到的結點類,只有一個全參構造器

    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

add方法有六個、remove方法有9個、返回迭代器、clone、等方法。原來學習Java數據結構的時候對鏈表不是很瞭解,自己手寫後,對這種數據結構也有了自己的認識。

跳躍表

上面說到,鏈表尋找數據很麻煩,比如ABCDEFG,這種順序如果要得到F,需要遍歷ABCDE結點,纔可以到達F結點,如果可以ACEF的跳躍式的訪問結點,那麼鏈表查找某個值就快很多了;如果可以AEF的,甚至直接AF,那麼取F的值,也是比較簡單的。
跳躍表:

https://www.cnblogs.com/acfox/p/3688607.html

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