一種聽從先進後出原則的有序集合javascript
聽從先進先出原則的有序項java
修改版的隊列,設置優先級node
基於隊列,克服‘假溢出’想象的隊列。例如隊列長度爲5,取第6個數據時,會取第一個數據git
要存儲多個元素,數組(或列表)多是最經常使用的數據結構github
每種語言都實現了數組。這種數據結構很是方便,提供了一個便利的[]語法來訪問它的元素。數組
而後,這種數據結構有一個缺點:在大多數語言中,數組的大小是固定的,從數組的起點或中間插入或移除項的成本很高,因須要移動元素。數據結構
儘管javascript中的Array類方法能夠幫咱們作這些事,但背後的處理機制一樣如此。閉包
鏈表儲存有序的元素集合,但不一樣於數組,鏈表中的元素在內存中不是連續放置的。每一個元素由一個儲存元素自己的節點和一個指向下一個元素的引用(也稱指針或連接)組成app
相對於傳統的數組,鏈表的一個好處在於,添加或移除元素的時候不須要移動其餘元素。然而,鏈表須要使用指針,所以實現鏈表時須要額外注意。函數
數組的另外一個細節是能夠直接訪問任意位置的任何元素,而要想訪問鏈表中間的一個元素,須要從起點(表頭)開始迭代列表直到找到所需的元素
// 鏈表節點 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.indexOf(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 } }
與普通鏈表的區別在於,雙向鏈表的連接是雙向的,一個鏈向下一個元素,一個鏈向上一個元素。
雙向鏈表提供了兩種迭代列表的方法,從頭至尾或反過來。在單向鏈表中,若是迭代列表時錯過了要找的元素,就須要回到列表起點,從新開始迭代。
循環鏈表能夠是單向鏈表同樣只有單向引用,也能夠向雙向鏈表有雙向引用。循環鏈表和鏈表之間惟一的區別在於,最後元素指向下一個元素的指針(tail.next)不是引用null,而是指向第一個元素(head)
鏈表相比數組最重要的優勢,就是無需移動鏈表中的元素,就能輕鬆地添加移除元素。所以,當你須要添加移除不少元素時,最好的選擇就是鏈表,而不是數組
集合是由一組無序且惟一(不能重複)的項組成的。這個數據結構使用了與優先集合相同的數學概念,但應用在計算機科學的數據結構中
class Set { constructor() { this.items = {} } has(value) { return this.items.hasOwnProperty(value) } add(value) { if (!this.has(value)) { this.items[value] = value return true } return false } remove(value) { if (this.has(value)) { delete this.items[value] return true } return false } get size() { return Object.keys(this.items).length } get values() { return Object.keys(this.items) } }
集合,字典,散列表均可以存儲不重複的數據。字典和集合很像,集合是以{ value: value }的形式存儲數據,而字典是以{ key: value}的形式存儲數據,字典也稱爲映射。
object對象即是字典在javascript中的實現
一個樹結構包含一系列存在父子關係的節點。每一個節點都有一個父節點(除頂部的第一個節點)以及零個或者多個子節點
二叉樹中的節點最多隻能有兩個子節點:一個是左側子節點,另外一個是右側子節點。二叉樹在計算機科學中應用很是普遍
二叉搜索樹(BST)是二叉樹的一種,可是它只容許你在左側節點存儲比父節點小的值,右側節點存儲比父節點大的值
下圖展現二叉搜索樹的組織結構方式
代碼實現二叉搜索樹
class Node { constructor(key) { this.key = key this.left = null this.right = null } } class BinarySearchTree { constructor() { this.root = null } insert(key) { const newNode = new Node(key) const insertNode = (node, newNode) => { if (newNode.key < node.key) { if (node.left === null) { node.left = newNode } else { insertNode(node.left, newNode) } } else { if (node.right === null) { node.right = newNode } else { insertNode(node.right, newNode) } } } if (!this.root) { this.root = newNode } else { insertNode(this.root, newNode) } } }
使用二叉搜索樹
const tree = new BinarySearchTree() tree.insert(11) tree.insert(7) tree.insert(5) tree.insert(3) tree.insert(9) tree.insert(8) tree.insert(10) tree.insert(13) tree.insert(12) tree.insert(14) tree.insert(20) tree.insert(18) tree.insert(25)
構建的樹以下圖:
遍歷一顆樹是指訪問樹的每個節點並對它們進行某種操做的過程。
訪問樹的全部節點有三種方式:中序、先序、後序
中序遍歷是一種以上行順序訪問BST全部節點的遍歷方式,也就是以從小到最大的順序訪問全部的節點。中序遍歷的一種應用就是對樹進行排序操做。實現以下:
inOrderTraverse(callback) { const inOrderTraverseNode = (node, callback) => { if (node !== null) { inOrderTraverseNode(node.left, callback) callback(node.key) inOrderTraverseNode(node.right, callback) } } inOrderTraverseNode(this.root, callback) }
inOrderTraverse方法接受一個回調函數做爲參數,回掉函數用來定義咱們對便利到的每一個節點進行的操做,這也叫作訪問者模式
在以前展現的樹上執行下面的方法:
tree.inOrderTraverse(value => { console.log(value) })
控制檯將會按照順序輸出:3 5 6 7 8 9 10 11 12 13 14 15 18 20 25
下圖描述了inOrderTraverse方法的訪問路徑
先序遍歷是以優先於後代節點的順序訪問每一個節點的,先序遍歷的一種應用是打印一個結構化的文檔。
preOrderTraverse(callback) { const preOrderTraverseNode = (node, callback) => { if (node !== null) { callback(node.key) preOrderTraverseNode(node.left, callback) preOrderTraverseNode(node.right, callback) } } preOrderTraverseNode(this.root, callback) }
下面的圖描繪了 preOrderTraverse 方法的訪問路徑:
後序遍歷是先訪問節點的後代節點,再訪問節點自己。後續遍歷的一種應用是計算一個目錄和它的子目錄全部文件所佔空間的大小
postOrderTraverse(callback) { const postOrderTraverseNode = (node, callback) => { if (node !== null) { postOrderTraverseNode(node.left, callback) postOrderTraverseNode(node.right, callback) callback(node.key) } } postOrderTraverseNode(this.root, callback) }
這個例子中,後續遍歷會先訪問左側節點,而後是右側節點,最後是父節點自己。
中序遍歷、先序遍歷、後續遍歷的實現方式類似的,惟一不一樣是三行代碼的執行順序。
下圖描繪postOrderTraverse方法的訪問路徑
在樹中,有三種常常執行的搜索順序:
用下圖的樹做爲示例
實現方法:
min(node) { const minNode = node => { return node ? (node.left ? minNode(node.left) : node) : null } return minNode(node || this.root) } max(node) { const maxNode = node => { return node ? (node.right ? maxNode(node.right) : node) : null } return maxNode(node || this.root) }
搜索一個特定的值
search(key) { const searchNode = (node, key) => { if (node === null) return false if (node.key === key) return node return searchNode((key < node.key) ? node.left : node.right, key) } return searchNode(root, key) }
移除一個節點
remove(key) { const removeNode = (node, key) => { if (node === null) return false if (node.key === key) { console.log(node) if (node.left === null && node.right === null) { let _node = node node = null return _node } else { console.log('key', key) } } else if (node.left !== null && node.key > key) { if (node.left.key === key) { node.left.key = this.min(node.left.right).key removeNode(node.left.right, node.left.key) return node.left } else { return removeNode(node.left, key) } } else if (node.right !== null && node.key < key) { if (node.right.key === key) { node.right.key = this.min(node.right.right).key removeNode(node.right.right, node.right.key) return node.right } else { return removeNode(node.right, key) } } else { return false } return removeNode((key < node.key) ? node.left : node.right, key) } return removeNode(this.root, key) }
完整寫法:
var removeNode = function(node, key){ if (node === null){ //{2} return null; } if (key < node.key){ //{3} node.left = removeNode(node.left, key); //{4} return node; //{5} } else if (key > node.key){ //{6} node.right = removeNode(node.right, key); //{7} return node; //{8} } else { //鍵等於node.key //第一種狀況——一個葉節點 if (node.left === null && node.right === null){ //{9} node = null; //{10} return node; //{11} } //第二種狀況——一個只有一個子節點的節點 if (node.left === null){ //{12} node = node.right; //{13} return node; //{14} } else if (node.right === null){ //{15} node = node.left; //{16} return node; //{17} } //第三種狀況——一個有兩個子節點的節點 var aux = findMinNode(node.right); //{18} node.key = aux.key; //{19} node.right = removeNode(node.right, aux.key); //{20} return node; //{21} } }; var findMinNode = function(node){ while (node && node.left !== null) { node = node.left; } return node; };
TODO