從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指向第一個節點。