Java學習筆記:資料結構之線性表(雙向連結串列)

Kallen Liu發表於2020-12-29

目錄


Java資料結構之線性表

  1. 連結串列結構

    鏈式儲存結構是基於指標實現,我們把一個資料元素和一個指標成為節點

    鏈式儲存結構是用指標把相互直接關聯的結點(即直接前驅節點或直接後繼節點)連結起來。 鏈式儲存結構的線性表成為連結串列

  2. 連結串列型別

    根據連結串列的構造方式的不同可以分為:

    ​ 1. 單向連結串列

    ​ 2. 迴圈連結串列

    ​ 3. 雙向連結串列


線性表的抽象資料型別

1、線性表的置空操作clear()
2、線性表判空操作:isEmpty()
3、求線性表的長度:size( )
4、取元素操作:get( i )
5、插入操作:insert( i, x )
6、刪除操作:delete( i)

雙向連結串列

1、基本概念

​ 雙向連結串列也叫雙連結串列,是連結串列的一種,它的每個資料結點中都有兩個指標,分別指向直接後繼和直接前驅。所以,從雙向連結串列中的任意一個結點開始,都可以很方便地訪問它的前驅結點和後繼結點。一般我們都構造雙向迴圈連結串列

2、圖形理解

相對於單連結串列而言,每個節點不僅有一個next,而且有一個指向前一個元素的pre

3、連結串列特性

每個資料結點中都有兩個指標,分別指向直接後繼和直接前驅

4、優缺點

優點

​ 可以找到前驅和後繼,可進可退

缺點

​ 增加刪除節點複雜,多需要分配一個指標儲存空間

優缺點總結

​ 適用於需要雙向查詢節點值的情況

5、程式碼展示

5.1、基礎程式碼
/** 儲存該連結串列的頭結點 */
private Node head;
/** 儲存該連結串列的尾結點 */
private Node tail;
/** 儲存該連結串列中已包含的節點數 */
private int size;

/** 內部類 */
private class Node {
    // 儲存節點資料
    private T data;
    // 指向上個節點的引用
    private Node prev;
    // 指向下個節點的引用
    private Node next;
    // 無參構造器
    public Node() {

    }
    // 初始化構造器
    public Node(T data) {
        this.data = data;
    }
    public Node(T data, Node prev) {
        this.data = data;
        this.prev = prev;
    }
    public Node(T data, Node prev, Node next) {
        this.data = data;
        this.prev = prev;
        this.next = next;
    }
}
5.2、插入資料
a、連結串列為空時
/**
 * <p>以指定資料元素來建立連結串列,Ps.(當前連結串列為空)</p>
 *
 * @param element   資料
 * @author Kallen
 * @since 2020/12/23 13:33
*/
public DoubleLink(T element) {
    head = new Node(element);
    tail = head;
    size++;
}
b、根據索引新增

新增節點

/**
 * <p>根據索引插入元素</p>
 *
 * @param element   元素
 * @param index     索引
 * @author Kallen
 * @since 2020/12/28 16:05
*/
public void insert(T element, int index) {
    
    if (index < 0 || index > size - 1) {
        throw new IndexOutOfBoundsException("索引越界");
    }
    // 判斷當前連結串列是否為空
    if (head == null) {
        DoubleLink(element);
    }else {
        if (index == 0) {
            // 當index為0時,即為在連結串列頭插入
            addAtHeader(element);
        }else {
            // 獲取插入點的前一個節點
            Node prev = getNodeByIndex(index - 1);
            // 獲取插入點的節點
            Node next = prev.next;
            // 讓新節點的next引用指向next節點,prev引用指向prev節點
            Node newNode = new Node(element , prev , next);
            // 讓prev的next指向新節點。
            prev.next = newNode;
            // 讓prev的下一個節點的prev指向新節點
            next.prev = newNode;
            size++;
        }
    }
}
c、尾插法
/**
 * <p>尾插法</p>
 *
 * @param element       元素
 * @author Kallen
 * @since 2020/12/28 16:25
*/
public void add(T element) {
    // 如果該連結串列為空
    if (head == null) {
        DoubleLink(element);
    }else {
        // 建立新節點,新節點的pre引用指向原tail節點
        Node newNode = new Node(element , tail , null);
        // 讓尾節點的next指向新增的節點
        tail.next = newNode;
        // 以新節點作為新的尾節點
        tail = newNode;
    }
    size++;
}
d、頭插法
/**
 * <p>頭插法</p>
 *
 * @param element       元素
 * @author Kallen
 * @since 2020/12/28 16:10
*/
public void addAtHeader(T element) {
    // 建立新節點,讓新節點的next指向原來的head,並以新節點作為新的head
    head = new Node(element, head);
    // 如果插入之前是空連結串列
    if (tail == null)
    {
        tail = head;
    }
    size++;
}
5.3、查詢資料
a、根據索引查詢
/**
 * <p>獲取指定索引處的元素</p>
 *
 * @param index         指定索引
 * @return {@link T}    元素
 * @author Kallen
 * @since 2020/12/23 13:35
*/
public T get(int index) {
    return getNodeByIndex(index).data;
}
b、根據指定元素查詢索引
/**
 * <p>查詢指定元素的索引</p>
 *
 * @param element       元素
 * @return {@link int}  索引
 * @author Kallen
 * @since 2020/12/25 15:48
*/
public int locate(T element) {
    Node current = head;
    for (int i = 0; i < size && current != null; i++, current = current.next) {
        if (current.data.equals(element)) {
            return i;
        }
    }
    return -1;
}
c、獲取當前連結串列長度
/**
 * <p>獲取連結串列長度</p>
 *
 * @return {@link int}  連結串列長度
 * @author Kallen
 * @since 2020/12/23 13:34
*/
public int size() {
    return size;
}
5.4、刪除資料
a、根據索引刪除

刪除資料

/**
 * <p>根據索引刪除</p>
 *
 * @param index     索引
 * @author Kallen
 * @since 2020/12/28 17:08
*/
public void delete(int index) {
    if (index < 0 || index > size - 1) {
        throw new IndexOutOfBoundsException("索引越界");
    }

    if (index == 0) {
        head = head.next;
        head.prev = null;
    }else {
        // 獲取刪除節點的前一個節點
        Node prev = getNodeByIndex(index-1);
        // 讓prev節點的next指向被刪除節點的next
        prev.next = prev.next.next;
        // 讓被刪除節點的next節點的prev指向prev節點 Ps(被刪除節點的next節點不為尾節點)
        if (prev.next != null) {
            prev.next.prev = prev;
        }
        // 將被刪除節點的prev、next引用賦為null
        prev.next.prev = null;
        prev.next.next = null;
    }
    size--;
}
b、刪除最後一個元素
/**
 * <p>刪除連結串列的最後一個元素</p>
 *
 * @author Kallen
 * @since 2020/12/28 17:15
*/
public void remove() {
    delete(size-1);
}
5.5、判斷是否為空
/**
 * <p>判斷連結串列是否為空連結串列</p>
 *
 * @author Kallen
 * @since 2020/12/28 17:16
*/
public boolean isEmpty() {
    return size == 0;
}
5.6、清空連結串列
/**
 * <p>清空連結串列</p>
 *
 * @author Kallen
 * @since 2020/12/28 17:17
*/
public void clear() {
    head = null;
    tail = null;
    size = 0;
}
5.7、私有方法
/**
 * <p>根據指定索引獲取結點</p>
 *
 * @param index     					索引
 * @return {@link DoubleLink<T>.Node}   節點
 * @author Kallen
 * @since 2020/12/23 13:36
*/
private Node getNodeByIndex(int index) {
    if (index < 0 || index > size - 1) {
        throw new IndexOutOfBoundsException("索引越界");
    }

    // 當索引值小於該連結串列長度的一半時,應從頭結點開始搜尋,PS(值相同從頭結點開始搜尋)
    if (index <= size / 2) {
        Node current = head;
        for (int i = 0; i <= size / 2 && current != null; i++, current = current.next) {
            if (i == index) {
                return current;
            }
        }
    }else {
        // 從tail節點開始搜尋
        Node current = tail;
        for (int i = size - 1; i > size / 2 && current != null; i++, current = current.prev) {
            if (i == index) {
                return current;
            }
        }
    }
    return null;
}

相關文章