1. 程式人生 > >從jdk原始碼的角度重溫連結串列

從jdk原始碼的角度重溫連結串列

連結串列

  • 由一系列節點組成的有序集合。
  • 分為單向連結串列, 雙向連結串列,迴圈連結串列
  • 單向連結串列: 每一個節點都有一個指標指向下一個節點,最後一個節點的指標指向null
  • 雙向連結串列: 每一個節點都有兩個指標(這裡用p, n代表兩個指標),p指向前一個節點,n指向下一個節點。但第一個節點的p指向null, 最後一個節點的n指向null
  • 迴圈連結串列: 每一個節點都有兩個指標(這裡用p, n代表兩個指標), 第一個節點的p指向最後一個節點, 最後一個節點的n指向第一個節點。這樣就形成了迴圈
java的LinkedList - 雙向連結串列

這裡以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;
    }
}

上面程式碼定義了一個類,代表一個節點的實體類,裡面有3個成員變數,next用於指向下一個節點,prev用於指向前一個節點,item則是該節點承載的元素。

接下來,看一下它的一些新增元素的方法


public boolean add(E e) {
    linkLast(e);
    return true;
}

public void addFirst(E e) {
    linkFirst(e);
}

public void addLast(E e) {
    linkLast(e);
}

public void add(int index, E element) {
    checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
linkBefore(element, node(index)); }

新增元素主要有4個方法,
- add和addLast其實是同一方法,都是在後面新增一個元素,所有都呼叫了linkLast(e);
- addFirst則呼叫linkFirst(e);
- add(index, element)這個方法是把元素新增某個位置,先呼叫checkPositionIndex(index)檢查index是否在[0,size]之間, 接著判斷想新增到index是否等於size,
因為位置是0開始,如果index == size,直接呼叫linkLast()放到最後一個位置即可。否則呼叫linkBefore方法

繼續看linkLast,linkFirst,linkBefore這三個方法 ….

private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}

這個方法目的是把元素放到最前面,first和last是兩個Node的成員變數,可以理解為用來記錄第一個和最後一個的來臨時變數。
先建立一個節點new Node<>(null, e, f),要把元素放到最前面,此時newNode就是第一個節點,之前的第一個節點就成為newNode的下一個節點。
接著判斷 f == null, 這裡的f是指沒新增之前的第一個節點,如果f為null, 那麼當前新增就是第一個節點,也是最後一個節點,所以newNode賦值為last;
如果f不為null, 那麼f的p指標就應該指向newNode。最後記錄節點的變數+1。

void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

linkLast方法目的是把元素放到最後面,思路跟linkFirst差不多, 臨時變數l記錄最後一個節點,然後建立一個新的節點,該節點p指向l, n指向null,
如果l為null, 沒新增元素前為空集合,所以當前也是第一個節點,賦值為first;如果l不為null, 那麼 l 的n應該指向新的節點。最後節點數+1.

void linkBefore(E e, Node<E> succ) {
    final Node<E> pred = succ.prev;
    final Node<E> newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}

linkBefore方法的目的是將元素e放到一個指定的節點之前。 succ是這個指定的節點。首先用臨時變數pred記錄succ的前一個節點,
接著建立一個新的節點,p指向pred, n指向succ, succ的p指標就指向這個新的節點newNode。如果pred 為null, succ的前一個節點為null,
那麼newNode就是第一個節點;如果pred不為null, pred的n指標應該指向newNode。最後節點數+1

新增分析完了,接下來看看移除

public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}


public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}


public boolean remove(Object o) {
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

removeFirst, removeLast 分別呼叫unlinkFirst(f), unlinkLast(l);

看remove方法,遍歷連結串列,從first開始, 找到和o相等的元素,呼叫unlink方法完成移除。

private E unlinkFirst(Node<E> f) {
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    first = next;
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
}

f是第一個節點,f.item和f.next分別臨時賦值給element和next,接著f.item和f.next都設為null,就意味著移除了第一個節點,
那麼next這個節點就是第一節點了,賦值給first; 如果next為null, 那麼last為null,說明現在連結串列整個為空啦;如果next不為null,將next的p指標指向null。

private E unlinkLast(Node<E> l) {
    final E element = l.item;
    final Node<E> prev = l.prev;
    l.item = null;
    l.prev = null; // help GC
    last = prev;
    if (prev == null)
        first = null;
    else
        prev.next = null;
    size--;
    modCount++;
    return element;
}

l是最後一個節點,l.item和l.prev分別臨時賦值給element和prev,接著l.item和l.prev都設為null,就意味著移除了最後一個節點了,
那麼prev這個節點就是最後一個節點了,賦值給last; 如果prev為null, 那麼first也為null,說明現在連結串列整個為空啦;如果prev不為null,將prev的n指標指向null。

E unlink(Node<E> x) {
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;

    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }

    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }

    x.item = null;
    size--;
    modCount++;
    return element;
}

移除某個節點: x.item, x.next, x.prev分別臨時賦值給element, next和prev; 重新調整next 和prev節點,
如果prev為null, 那麼next節點就是第一個節點;如果prev不為null, prev的n指向next, x的p設為null;
如果next為null, 那麼prev節點就是最後一個節點; 如果next不為null, next的p指向prev, x的next設為null;
最後把x.item設為null,移除當前, size–代表節點數減1, modCount記錄的是連結串列的操作次數,所以怎麼都是加1。

到此, 雙向連結串列的實現就基本完成啦。如果要實現迴圈連結串列,在雙向連結串列的基礎上,把第一個節點的p指向最後一個, 把最後一個節點的n指向第一個節點。