數據結構 - 鏈表

鏈表

要存儲多個元素,數組(或列表)可能是最常用的數據結構。
每種語言都實現了數組。這種數據結構非常方便,提供了一個便利的[]語法來訪問它的元素。
然而,這種數據結構有一個缺點:在大多數語言中,數組的大小是固定的,從數組的起點或中間插入或移除項的成本很高,因爲需要移動元素;
儘管 JavaScript中的Array類方法可以幫我們做這些事,但背後的處理機制同樣如此。

鏈表存儲有序的元素集合,但不同於數組,鏈表中的元素在內存中並不是連續放置的。每個 元素由一個存儲元素本身的節點和一個指向下一個元素的引用(也稱指針或鏈接)組成。

下圖展示了鏈表的結構:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-mXR3t1jE-1584444250629)(C:\Users\lin\AppData\Roaming\Typora\typora-user-images\1584279213258.png)]

相對於傳統的數組,鏈表的一個好處在於,添加或移除元素的時候不需要移動其他元素。然而,鏈表需要使用指針,因此實現鏈表時需要額外注意。·

數組的另一個細節是可以直接訪問任何位置的任何元素,而要想訪問鏈表中間的一個元素,需要從起點(表頭)開始迭代列表直到找到所需的元素。

下面我們使用 JavaScript 創建一個鏈表類:

// 鏈表節點
class Node {
    constructor(element) {
        this.element = element
        this.next = null
    }
}

// 鏈表
class LinkedList {

    constructor() {
        this.head = null
        this.length = 0
    }

    // 追加元素
    append(element) {
        const node = new Node(element)
        let current = null
        if (this.head === null) {
            this.head = node
        } else {
            current = this.head
            while(current.next) {
                current = current.next
            }
            current.next = node
        }
        this.length++
    }

    // 任意位置插入元素
    insert(position, element) {
        if (position >= 0 && position <= this.length) {
            const node = new Node(element)
            let current = this.head
            let previous = null
            let index = 0
            if (position === 0) {
                this.head = node
            } else {
                while (index++ < position) {
                    previous = current
                    current = current.next
                }
                node.next = current
                previous.next = node
            }
            this.length++
            return true
        }
        return false
    }

    // 移除指定位置元素
    removeAt(position) {

        // 檢查越界值
        if (position > -1 && position < length) {
            let current = this.head
            let previous = null
            let index = 0
            if (position === 0) {
                this.head = current.next
            } else {
                while (index++ < position) {
                    previous = current
                    current = current.next
                }
                previous.next = current.next
            }
            this.length--
            return current.element
        }
        return null
    }

    // 尋找元素下標
    findIndex(element) {
        let current = this.head
        let index = -1
        while (current) {
            if (element === current.element) {
                return index + 1
            }
            index++
            current = current.next
        }
        return -1
    }

    // 刪除指定文檔
    remove(element) {
        const index = this. findIndex(element)
        return this.removeAt(index)
    }

    isEmpty() {
        return !this.length
    }

    size() {
        return this.length
    }

    // 轉爲字符串
    toString() {
        let current = this.head
        let string = ''
        while (current) {
            string += ` ${current.element}`
            current = current.next
        }
        return string
    }
}

鏈表類的使用:

const linkedList = new LinkedList()

console.log(linkedList)
linkedList.append(2)
linkedList.append(6)
linkedList.append(24)
linkedList.append(152)

linkedList.insert(3, 18)
console.log(linkedList)
console.log(linkedList.findIndex(24))

雙向鏈表

雙向鏈表和普通鏈表的區別在於,在鏈表中, 一個節點只有鏈向下一個節點的鏈接,而在雙向鏈表中,鏈接是雙向的:一個鏈向下一個元素, 另一個鏈向前一個元素,如下圖所示:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Z28Z8uFi-1584444250650)(C:\Users\lin\AppData\Roaming\Typora\typora-user-images\1584360838837.png)]
雙向鏈表提供了兩種迭代列表的方法:從頭到尾,或者反過來。我們也可以訪問一個特定節 點的下一個或前一個元素。在單向鏈表中,如果迭代列表時錯過了要找的元素,就需要回到列表 起點,重新開始迭代。這是雙向鏈表的一個優點。

來實現一個雙向鏈表類:

// 鏈表節點
class Node {
    constructor(element) {
        this.element = element
        this.prev = null
        this.next = null
    }
}

// 雙向鏈表
class DoublyLinkedList {

    constructor() {
        this.head = null
        this.tail = null
        this.length = 0
    }

    // 任意位置插入元素
    insert(position, element) {
        if (position >= 0 && position <= this.length){
            const node = new Node(element)
            let current = this.head
            let previous = null
            let index = 0
            // 首位
            if (position === 0) {
                if (!head){
                    this.head = node
                    this.tail = node
                } else {
                    node.next = current
                    this.head = node
                    current.prev = node
                }
            // 末位
            } else if (position === this.length) {
                current = this.tail
                current.next = node
                node.prev = current
                this.tail = node
            // 中位
            } else {
                while (index++ < position) {
                    previous = current
                    current = current.next
                }
                node.next = current
                previous.next = node
                current.prev = node
                node.prev = previous
            }
            this.length++
            return true
        }
        return false
    }

    // 移除指定位置元素
    removeAt(position) {
        if (position > -1 && position < this.length) {
            let current = this.head
            let previous = null
            let index = 0

            // 首位
            if (position === 0) {
                this.head = this.head.next
                this.head.prev = null
                if (this.length === 1) {
                    this.tail = null
                }

            // 末位
            } else if (position === this.length - 1) {
                this.tail = this.tail.prev
                this.tail.next = null

            // 中位
            } else {
                while (index++ < position) {
                     previous = current
                     current = current.next
                }
                previous.next = current.next
                current.next.prev = previous
         }
         this.length--
         return current.element
        } else {
            return null
        }
    }

    // 其他方法...
}

循環鏈表

循環鏈表可以像鏈表一樣只有單向引用,也可以像雙向鏈表一樣有雙向引用。循環鏈表和鏈表之間唯一的區別在於,最後一個元素指向下一個元素的指針(tail.next)不是引用null, 而是指向第一個元素(head),如下圖所示。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-iI9G8VSw-1584444250652)(C:\Users\lin\AppData\Roaming\Typora\typora-user-images\1584361857501.png)]
雙向循環鏈表有指向head元素的tail.next,和指向tail元素的head.prev。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-pa5XiSoT-1584444250658)(C:\Users\lin\AppData\Roaming\Typora\typora-user-images\1584361877241.png)]
鏈表相比數組最重要的優點,那就是無需移動鏈表中的元素,就能輕鬆地添加和移除元素。因此,當你需要添加和移除很多元素 時,最好的選擇就是鏈表,而非數組。

學習於:JavaScript中的數據結構和算法學習

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章