1. 程式人生 > >大話資料結構(四)——雙向連結串列的java實現

大話資料結構(四)——雙向連結串列的java實現

在實現了單向連結串列後,我們在使用單向連結串列中會發現一個問題:在單向連結串列中查詢某一個結點的下一個結點的時間複雜度是O(1),但是查詢這個結點的上一個結點的時候,時間複雜度的最大值就變成了O(n),因為在查詢這個指定結點的上一個結點時又需要從頭開始遍歷。

那麼該如何解決這個困難呢?我們可以在單鏈表的每個結點中,再設定一個指向前驅結點的指標域,這就構成了雙向連結串列。在雙向連結串列中含有兩個指標域,一個指向直接前驅,一個指向直接後繼。
這裡寫圖片描述

雙向連結串列是單向連結串列的拓展,所以程式大致與與單向連結串列的程式相似。
這裡需要注意的是結點的插入和刪除步驟:
將結點s插入到結點p和p->next之間時
這裡寫圖片描述


(1)將s前驅指向結點p,如圖中①
(2)將s的後繼指向結點p->next,如圖中②
(3)將p->next的前驅指向結點s,如圖中③
(4)將p的後繼指向結點s,如圖中④
將結點p刪除
(1)將p->prior結點的後驅指標指向p->next結點
(2)將p->next結點的前驅指標指向p->prior結點
(3)解除p結點和其前驅後繼的關係
上程式碼

package likend;

/**
 * Created by yxf on 2018/4/5.
 * 雙向連結串列
 */
public class DoubleLink<T> {
private Node header; //連結串列頭結點 private Node tail; //連結串列尾結點 private int size; //儲存已經有的結點 public class Node<T> { private T data; //資料 private Node prev; //指向上一個節點的引用 private Node next; //指向下一個節點的引用 public Node() { } public Node(T data, Node prev, Node next) { this
.data = data; this.prev = prev; this.next = next; } } public DoubleLink() { } /** * 在尾部新增 * * @param element * @return */ public boolean add(T element) { linkLast(element); return true; } /** * 獲取指定索引處的元素 * * @param index * @return */ public T getElement(int index) { return (T) getNodeByIndex(index).data; } /** * 獲取指定位置的結點 * * @param index * @return */ public Node getNodeByIndex(int index) { if (index < 0 || index > size) throw new IndexOutOfBoundsException("獲取位置超過了連結串列長度範圍"); Node currentNode = header; for (int i = 0; i < index; i++) { currentNode = currentNode.next; } return currentNode; } /** * 獲取指定位置前驅的結點 * * @param index * @return */ public Node getNodeByIndexBefore(int index) { Node preNode = header; for (int i = 0; i < index - 1; i++) { preNode = preNode.next; //獲得前驅結點 } return preNode; } /** * 獲取指定元素的前驅 * * @param currentElem * @return */ public T priorElement(T currentElem) { int index = getIndex(currentElem); if (index == -1) return null; else { if (index == 0) { return null; } else { return (T) getNodeByIndex(index - 1).data; } } } /** * 獲取指定元素的後驅 * * @param currentElem * @return */ public T nextElement(T currentElem) { int index = getIndex(currentElem); if (index == -1) return null; else { if (index == size - 1) { return null; } else { return (T) getNodeByIndex(index + 1).data; } } } //查詢連結串列中指定元素所在的位置 public int getIndex(T element) { Node current = header; for (int i = 0; i < size && current != null; i++, current = current.next) { if (current.data.equals(element)) return i; } return -1; } /** * 在頭部插入 * * @param element * @return */ public boolean addFirst(T element) { linkFirst(element); return true; } /** * 在指定位置插入元素 * * @param index * @param element * @return */ public boolean insert(int index, T element) { if (index < 0 || index > size) throw new IndexOutOfBoundsException("插入位置超出連結串列範圍"); if (index == 0) linkFirst(element); else { //獲取插入位置的前驅結點 Node preNode = getNodeByIndexBefore(index); //獲取插入位置的後繼結點 Node nextNode = preNode.next; Node newNode = new Node(element, preNode, nextNode); //讓前驅結點的後繼指向插入結點 preNode.next = newNode; //讓後驅結點的前繼指向插入結點 nextNode.prev = newNode; size++; } return true; } /** * 刪除元素 * * @param index * @return */ public T delete(int index) { if (index < 0 || index > size) throw new IndexOutOfBoundsException("刪除位置超出連結串列範圍"); Node currentNode = header; //刪除的是頭結點 if (index == 0) { header = header.next; currentNode.next = null; header.prev = null; } else { Node currentNodeBefore = null; for (int i = 0; i < index; i++) { currentNodeBefore = currentNode;//前置結點 currentNode = currentNode.next; //要刪除的當前結點 } //刪除的是尾結點 if (index == size - 1) { tail = currentNodeBefore; //尾結點變為刪除結點的前置結點 tail.next = null; } else { //將前置結點的後繼指標指向當前結點的後繼指標 currentNodeBefore.next = currentNode.next; //將當前刪除結點的後繼結點的前驅指標指向刪除結點的前驅結點 currentNode.next.prev = currentNodeBefore; } currentNode.prev = null; currentNode.next = null; } size--; return (T) currentNode.data; } //刪除最後一個元素 public T remove() { return delete(size - 1); } /** * 尾部插入 * * @param e */ private void linkLast(T e) { final Node<T> l = tail; final Node<T> newNode = new Node<>(e, null, null); if (l == null) { header = newNode; tail = header; } else { tail.next = newNode; //尾結點指向新結點 newNode.prev = tail; tail = newNode; //新結點作為尾結點 } size++; } /** * 在頭部插入 * * @param e */ private void linkFirst(T e) { final Node<T> l = header; Node<T> newNode = new Node<>(e, null, null); if (l == null) { header = newNode; tail = header; } else { newNode.next = header; header.prev = newNode; header = newNode; } size++; } public boolean isEmpty() { return size == 0; } //清空線性表 public void clear() { //將頭結點和尾結點設為空 header = null; tail = null; size = 0; } @Override public String toString() { if (isEmpty()) return "[]"; else { StringBuilder sb = new StringBuilder("["); for (Node current = header; current != null; current = current.next) sb.append(current.data.toString() + "->"); sb.append("]"); int len = sb.length(); return sb.delete(len - 3, len - 1).toString(); } } }

測試程式碼

package likend;

/**
 * Created by yxf on 2018/4/5.
 */
public class DoubleLinkTest {
    public static void main(String[] args) {
        DoubleLink ls = new DoubleLink();

        ls.add(2);
        ls.add(4);
        ls.add(5);
        ls.addFirst(1);
        System.out.println("新增元素後的連結串列為: " + ls);
        ls.insert(2, 3);
        System.out.println("在連結串列位置2插入元素: " + ls);
        ls.delete(2);
        System.out.println("在連結串列位置2刪除元素: " + ls);
        ls.remove();
        System.out.println("刪除連結串列中的一個元素: " + ls);
        System.out.println("獲得連結串列位置為2處的元素:   " + ls.getElement(2));
        System.out.println("獲取元素2的前驅元素:    " + ls.priorElement(2));
        System.out.println("獲取元素2的後驅元素:    " + ls.nextElement(2));
        ls.clear();
        System.out.println(ls);
    }
}
新增元素後的連結串列為: [1->2->4->5]
在連結串列位置2插入元素: [1->2->3->4->5]
在連結串列位置2刪除元素: [1->2->4->5]
刪除連結串列中的一個元素: [1->2->4]
獲得連結串列位置為2處的元素:   4
獲取元素2的前驅元素:    1
獲取元素2的後驅元素:    4
[]

Process finished with exit code 0

借鑑 https://blog.csdn.net/u013393958/article/details/72650496 程式碼有所改變。