【數據結構與算法】--JavaScript 鏈表

1、介紹

JavaScript 原生提供了數組類型,可是卻沒有鏈表,雖然日常的業務開發中,數組是能夠知足基本需求,可是鏈表在大數據集操做等特定的場景下明顯具備優點,那爲什麼 JavaScript 不提供鏈表類型呢?怎麼實現一個完整可用的鏈表呢?javascript

數組的特色前端

  1. 線性結構,順序存儲
  2. 插入慢,查找快
  3. 查找、更新、插入、刪除,的時間複雜度分別爲,O(1)、O(1)、O(n)、O(n)

鏈表的特色java

  1. 線性結構,隨機存儲(省內存)
  2. 插入快,查找慢
  3. 查找、更新、插入、刪除,的時間複雜度分別爲,O(n)、O(1)、O(1)、O(1)

2、單鏈表

talk is easy, show the code, 下面用 JavaScript 實現一個相對完整的單鏈表,並提供如下方法供調用:數組

  1. push(element) // 鏈表尾部插入節點
  2. pop() // 鏈表尾部刪除節點
  3. shift() // 刪除頭部節點、
  4. unshift(element) // 插入頭部節點
  5. find(index) // 查找指定位置節點
  6. insert(element, index) // 指定位置插入節點
  7. edit(element, index) // 修改指定位置節點
  8. delete(index) // 鏈表刪除指定位置節點
  9. cycle() // 使鏈表首尾成環
function initList() {
    class Node {
        constructor(item) {
            this.element = item
        }
    }
    class List {
        constructor() {
            this.head = null
            this.size = 0
            this.last = null
        }
        /** * 鏈表查找元素 * @param index 查找的位置 */
        find(index) {
            let current = this.head
            for (let i = 0; i < index; i++) {
                current = current.next
            }
            return current
        }
        /** * 鏈表尾部插入元素 * @param element 插入的元素 */
        push(element) {
            let newNode = new Node(element)
            if (this.size === 0) {
                this.head = newNode
                this.head.next = null
                this.last = this.head
            } else {
                this.last.next = newNode
                this.last = newNode
                newNode.next = null
            }
            this.size += 1
        }
        /** * 鏈表尾部刪除元素 * @param element 刪除的位置 */
        pop(element) {
            this.last.next = null
        }
        /** * 鏈表頭部刪除元素 */
        shift() {
            if (this.size === 0)
                return
            this.head = this.head.next
            if (this.size === 1)
                this.last = null
            this.size -= 1
        }
        /** * 鏈表頭部插入元素 * @param element 插入的元素 */
        unshift(element) {
            let newNode = new Node(element)
            newNode.next = this.head
            this.head = newNode
            if (this.size === 0)
                this.last = this.head
            this.size += 1
        }
        /** * 鏈表插入元素 * @param element 插入的位置, index 插入的位置 */
        insert(element, index) {
            if (index < 0 || index > this.size) {
                console.error('超出鏈表節點範圍')
                return
            }
            let newNode = new Node(element)
            if (this.size === 0) {
                // 空鏈表
                newNode.next = null
                this.head = newNode
                this.last = newNode
            } else if (index === 0) {
                // 插入頭部
                newNode.next = this.head
                this.head = newNode
            } else if (index == this.size) {
                //插入尾部
                newNode.next = null
                this.last.next = newNode
                this.last = newNode
            } else {
                // 中間插入
                let preNode = this.find(index - 1)
                newNode.next = preNode.next
                preNode.next = newNode
            }
            this.size += 1
        }
        /* *鏈表編輯元素 * @param element 編輯的元素,index 元素位置 */
        edit(element, index) {
            let current = this.find(index)
            current.element = element
        }
        /* *鏈表刪除元素 * @param index 刪除元素位置 */
        delete(index) {
            let current = this.find(index)
            if (index === 0) {
                // 刪除頭節點
                this.head = this.head.next
            } else if (index === ((this.size) - 1)) {
                // 刪除尾節點
                let preNode = this.find(index - 1)
                preNode.next = null
            } else {
                // 刪除中間節點
                let preNode = this.find(index - 1)
                let nextNode = preNode.next.next
                let removeNode = preNode.next
                preNode.next = nextNode
            }
            this.size -= 1
        }
        /* *鏈表使首尾成環 */
        cycle() {
            this.last.next = this.head
        }
    }
    return new List()
}

let list = initList()
複製代碼

3、雙向鏈表

雙向鏈表的特色就是添加了指向上一個節點的指針(prev),比較單鏈表來講,稍微複雜一些,也更強大,這裏把上面的單鏈表修改一下。bash

function initList() {
    class Node {
        constructor(item) {
            this.element = item
            this.next = null
            this.prev = null
        }
    }
    class List {
        constructor() {
            this.head = null
            this.size = 0
            this.last = null
        }
        /**
        * 鏈表查找元素
        * @param index 查找的位置
        */
        find(index) {
            let current = this.head
            for (let i = 0; i < index; i++) {
                current = current.next
            }
            return current
        }
        /**
        * 鏈表尾部插入元素
        * @param element 插入的元素
        */
        push(element) {
            let newNode = new Node(element)
            if (this.size === 0) {
                this.head = newNode
                this.head.next = null
                this.last = this.head
            } else {
                this.last.next = newNode
                newNode.next = null
                newNode.prev = this.last
                this.last = newNode
            }
            this.size += 1
        }
        /**
        * 鏈表尾部刪除元素
        * @param element 刪除的位置
        */
        pop() {
            if (this.size === 0)
                return
            if (this.size === 1) {
                this.head = null
                this.last = null
            } else {
                this.last.prev.next = null
                this.last = this.last.prev
            }
            this.size -= 1
        }
        /**
        * 鏈表頭部刪除元素
        */
        shift() {
            if (this.size === 0)
                return
            if (this.size === 1) {
                this.head = null
                this.last = null
            } else {
                this.head = this.head.next
                this.head.prev = null
            }
            this.size -= 1
        }
        /**
        * 鏈表頭部插入元素
        * @param element 插入的元素
        */
        unshift(element) {
            let newNode = new Node(element)
            if (this.size === 0) {
                this.head = newNode
                this.head.next = null
                this.last = this.head
            } else {
                this.head.prev = newNode
                newNode.next = this.head
                this.head = newNode
            }
            this.size += 1
        }
        /**
        * 鏈表插入元素
        * @param element 插入的位置, index 插入的位置
        */
        insert(element, index) {
            if (index < 0 || index > this.size) {
                console.error('超出鏈表節點範圍')
                return
            }
            let newNode = new Node(element)
            if (this.size === 0) {
                // 空鏈表
                this.head = newNode
                this.head.next = null
                this.last = this.head
            } else if (index === 0) {
                // 插入頭部
                this.head.pre = newNode
                newNode.next = this.head
                this.head = newNode
            } else if (index == this.size - 1) {
                //插入尾部
                newNode.next = null
                newNode.prev = this.last
                this.last.next = newNode
                this.last = newNode
            } else {
                // 中間插入
                let prevNode = this.find(index - 1)
                newNode.next = prevNode.next
                prevNode.next = newNode
                newNode.prev = prevNode
                newNode.next.prev = newNode
            }
            this.size += 1
        }
        /*
        *鏈表編輯元素
        * @param element 編輯的元素,index 元素位置
        */
        edit(element, index) {
            let current = this.find(index)
            current.element = element
        }
        /*
        *鏈表刪除元素
        * @param index 刪除元素位置
        */
        delete(index) {
            let current = this.find(index)
            if (index === 0) {
                // 刪除頭節點
                this.head = this.head.next
                this.prev = null
            } else if (index === ((this.size) - 1)) {
                // 刪除尾節點
                let preNode = this.find(index - 1)
                preNode.next = null
            } else {
                // 刪除中間節點
                let preNode = this.find(index - 1)
                let nextNode = preNode.next.next
                let removeNode = preNode.next
                preNode.next = nextNode
                nextNode.prev = preNode
            }
            this.size -= 1
        }
        /*
        *鏈表使首尾成環
        */
        cycle() {
            this.last.next = this.head
            this.head.prev = this.last
        }
    }
    return new List()
}
let list = new initList()
複製代碼

3、循環鏈表

循環鏈表能夠是單鏈表也能夠是雙向鏈表,它的特色是最後一個節點的 next 指針指向的是 head 節點 而不是 null,上面代碼已經提供了 cycle 方法來實現。框架

4、判斷鏈表有環

主要有這些方法:大數據

  1. 遍歷鏈表,使每個節點與以前節點比較,如有重複則爲有環鏈表優化

  2. 定義一個 Map 對象,遍歷鏈表到每個節點,若 Map 中沒有次節點 ID,則將節點 ID 爲 key, 存入 Map ,每一個節點判斷一次,若是某個節點的 ID存在,證實鏈表成環ui

  3. 雙指針法,舉個例子來講,兩我的在操場跑步,速度不一樣時,總會在某些時刻相遇,就是由於跑到是圓的(環),利用這個原理,定義一個循環和兩個指向頭節點的指針,一個每次移動一個節點,一個移動兩個節點,若是是成環的鏈表,某個時刻必然會遇到同一個節點。this

5、鏈表在前端開發中的應用

  1. 鏈表的特性代表其擅長修改,不擅查找,因此對於須要大量修改的操做,能夠考慮用鏈表實現,可是前端每每處理的數據量不會大,因此這種場景的實際意義不是很大,我的感受在框架的底層優化上,使用較多,業務開發中,數組夠用。

  2. 鏈表由於是隨機存儲的,因此比較省內存,可是對動態語言 JavaScript 解釋器來講,有自動的垃圾回收機制來管理內存,因此鏈表的這個優點就不明顯了。

  3. 鏈表特殊的結構,感受適合作輪播圖(雙向循環鏈表)、雙向導航列表等

相關文章
相關標籤/搜索