Java數據結構之鏈表的原理及LinkedList的部分源碼剖析

1、爲何要學習數據結構?

  • 作爲一名程序員,無論你是用什麼編程語言,數據結構是取底層的東西。就至關於蓋樓的地基同樣,地基作很差,上邊再好也沒有用。
  • 在高級語言中,通常會對這些基礎的數據結構進行封裝,咱們學要學習這些基礎的東西嗎?
    固然是的,只有知道這些基礎的東西,咱們才能更好地使用語言封裝好的api。舉個最簡單的例子,在Java中,List的實現類有ArrayList,LinkedList,Vector,你知道在什麼狀況下用哪一個效率最高嗎?只有知道其底層源才能更好地利用。
  • 如何學習數據結構?
    能夠看書,看視頻,看博客...可是最重要的一點,必定要本身用手去敲,好比本身去寫一個鏈表,本身去模擬一個棧,一個隊列等。可能你寫的沒有在語言中封裝的那麼用好,可是你必定會收穫頗豐的。
  • 視頻書籍哪裏找?
    微信關注公衆號「小魚與Java」,後臺回覆數據結構,有我已經整理好的資料。
    小魚與Javajava

    2、什麼是數據和數據結構?

    數據就是一些或某部分有關係的內容的組合。
    數據結構是數據的存儲方式。從不一樣的角度來討論,分類以下:
    |按邏輯結構分|按物理結構分|
    |-|-|
    |線性結構|順序存儲結構|
    |集合結構|鏈式存儲結構|
    |樹形結構||
    |圖形結構||
  • 邏輯結構,即按照人們的思惟邏輯對其分類。
  • 物理結構就是數據在磁盤上的存儲方式,能夠是一整塊存儲區域,也能夠是不一樣的存儲塊(可是它們以前有關係,因此就劃分爲一組數據)。node

2、物理結構分法的分類

順序存儲

就是在磁盤上連續存儲的,在Java中就是數組,它從第一個索引開始,全部的數據都是緊跟其後的。程序員

鏈式存儲

這些數據(稱爲數據元素,是不可再分隔的)在磁盤上是分開存儲的,只是由於它們之間有一些關係,因此咱們就將其聯繫到了一塊兒,組成了一種數據結構————鏈表。編程

代碼實現

對於順序存儲的來講,在Java中就是數組,因此不作代碼實現。

對於鏈式存儲,就是在其中任何一個元素,除了存儲它自己的數據外,還存儲另外一個或多個數據元素的存儲位置。主要分類有下:

單向鏈表:

就是在這個元素中,除了存儲它自身的數據還存儲了它的下一個數據
單向鏈表api

class LinkedNode<T> {
        private T data;
        private LinkedNode<T> next;

        public LinkedNode(T data) {
            this.data = data;
            //在構建這個時,咱們就讓它的下一個指針爲null
            next = null;
        }
    }

接下來寫一個管理它的類,咱們寫一個add(T t)方法,就是插入到最後數組

public class MyLinked<T> {
    /**
     * 用一個頭指針來表示這個鏈表的頭
     */
    private LinkedNode<T> first;

    public MyLinked() {
    }

    /**
     * 提供一個有參構造方法
     *
     * @param t
     */
    public MyLinked(T t) {
        this.first = new LinkedNode<>(t);
        first.next=null;
    }

    /**
     * 往鏈表中添加元素,默認是添加到最後的
     *
     * @param t
     */
    public void add(T t) {
        if (first == null) {
            first = new LinkedNode<>(t);
        } else {
            LinkedNode herd = first;
            while (herd.next != null) {
                herd = herd.next;
            }
            //當從這個循環中出來的時候,這個herd.next就是null,也就是說這個herd就是這個鏈表的最後一個元素
            herd.next = new LinkedNode(t);
        }
    }
}

咱們寫的這個add方法,是要遍歷整個鏈表來作此操做。
以上就是最簡單的一個鏈表類了,咱們使用了泛型爲了讓其更加通用。微信

雙向鏈表

雙向鏈表
雙向鏈表就是在這個鏈表中存儲了自身的數據,還存儲了它的前一個和後一個數據的地址。數據結構

class LinkedNode<T> {
        private T data;
        private LinkedNode<T> prev;
        private LinkedNode<T> next;

        public LinkedNode(T data) {
            this.data = data;
            this.prev = null;
            this.next = null;
        }
    }

循環鏈表表

  • 它也分爲單向循環鏈表和雙向循環鏈表
  • 在單向鏈表中,最後一個元素的next不是null,而是指向第一個元素
  • 在雙向鏈表中它的第一個元素的prev是最後一個元素,最後一個元素的next是第一個元素。
  • 由於它是雙向循環的,因此在效率上要比單向的下快一些。
    好比,這個鏈表的長度是50,咱們要找第48個元素。若是是單身的話,它只能從0->1->2.....->48,這樣要遍歷前48個元素;若是是雙向的話,咱們只須要50->49->48,三次就夠了。
  • Java中的LinkedList就是底層就是雙向循環鏈表。咱們來瞅一下它的源碼:
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方法,來體驗一個它的魅力編程語言

  • 它有add(E e)和add(int index, E e)兩個方法

add(E e)就是添加到鏈表的最後,最終調用的方法以下:學習

void linkLast(E e) {
    //將當前的最後一個節點保存下來
        final Node<E> l = last;
    //構造一個新的節點對象
        final Node<E> newNode = new Node<>(l, e, null);
    //將這個鏈表的last指向這個新元素
        last = newNode;
        if (l == null){
    //這個條件就是說,此時鏈表爲空。由於l是在添加以前的last,若是這個鏈表爲空,last確定是空的
            first = newNode;
        }else{
            l.next = newNode;
    }
        size++;//當前的鏈表大小++
        modCount++;//這個是用來記錄這個鏈表的操做次數,對這個鏈表進行的任何操做,這個都會++
    }

上邊的構造方法

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

這個插入到時最後的鏈表並無去遍歷一個整個鏈表,而是將last.next指向了這個新的節點

add(int index, E e)插入到指定位置
這個最重要的就是利用循環列表來找到這個index是在前半邊仍是後半邊,主要尋找的代碼以下:

Node<E> node(int index) {
        if (index < (size >> 1)) {
    //上邊的>>就是取半,除以2
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

能夠看到,它判斷了所在的部分進行了不一樣的遍歷方式,就是對二分法的一次簡利用

  • 固然,它內部還寫了迭代等其餘的方法,感興趣的能夠本身看一下。
相關文章
相關標籤/搜索