本篇文章是在二叉排序樹的基礎上進行遍歷、查找、與刪除結點。javascript
那麼首先來看一下什麼是二叉排序樹?java
二叉排序樹,又稱二叉查找樹、二叉搜索樹。node
咱們知道了什麼是二叉排序樹,如今來看下它的具體算法實現。算法
// 構建二叉樹 function BinaryTree() { // 定義結點 let Node = function(key) { this.key = key this.left = left this.right = right } // 定義根結點 let root = null // 得到整棵樹 this.getRoot = function() { return this.root } // 定義插入結點算法 let insertNode = function(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) } } } // 定義二叉排序樹插入算法 this.insert = function(key) { let newNode = new Node(key) if(root === null) { root = newNode } else { insertNode(root, newNode) } } } let nodes = [8,3,30,1,6,14,4,7,13] // 建立樹實例 let tree = new BinaryTree() nodes.forEach(function(key) { tree.insert(key) }) console.log("建立的二叉樹是:", tree.getRoot())
至此,一棵二叉排序樹就構造完啦。接下來咱們根據構造的這顆二叉樹進行相應遍歷、查找與刪除操做。數組
二叉樹的遍歷分爲深度優先遍歷和廣度優先遍歷。ide
深度優先遍歷(Depth First Search)是指沿着樹的深度進行遍歷樹的結點。其中深度優先遍歷又分爲三種:前序遍歷、中序遍歷、後序遍歷。函數
這裏前序、中序、後序是根據根結點的順序命名的。
前序遍歷也叫作先根遍歷、先序遍歷、前序周遊,記作 根左右。post
前序遍歷的做用是能夠複製已有的二叉樹,且比從新構造的二叉樹的效率高。this
下面咱們來看它的算法實現。分爲遞歸與非遞歸兩種。code
function BinaryTree() { // 這裏省略了二叉排序樹的構建方法 // 定義前序遍歷算法 let preOrderTraverseNode = function(node, callback) { if(node !== null) { callback(node.key) // 先訪問當前根結點 preOrderTraverseNode(node.left, callback) // 訪問左子樹 preOrderTraverseNode(node.right, callback) // 訪問右子樹 } } // 定義前序遍歷方法 this.preOrderTraverse = function(callback) { preOrderTraverseNode(root, callback) } } let nodes = [8,3,10,1,6,14,4,7,13] let tree = new BinaryTree() nodes.forEach(function(key) { tree.insert(key) // 構造二叉樹 }) // 定義回調函數 let callback = function(key) { console.log(key) } tree.preOrderTraverse(callback) // 8 3 1 6 4 7 10 14 13
function BinaryTree() { // ... // 定義前序遍歷算法 let preOrderTraverseNode = function(node, callback) { let stack = [] if(node !== null) { stack.push(node) } while(stack.length) { let temp = stack.pop() callback(temp.key) // 這裏先放右邊再放左邊是由於取出來的順序相反 if(temp.right !== null) { stack.push(temp.right) } if(temp.left !== null) { stack.push(temp.left) } } } // 定義前序遍歷方法 this.preOrderTraverse = function(callback) { preOrderTraverseNode(root, callback) } } let nodes = [8,3,10,1,6,14,4,7,13] let tree = new BinaryTree() nodes.forEach(function(key) { tree.insert(key) // 構造二叉樹 }) // 定義回調函數 let callback = function(key) { console.log(key) } tree.preOrderTraverse(callback) //8 3 1 6 4 7 10 14 13
中序遍歷也叫作中根遍歷、中序周遊,記作 左根右。
中序遍歷二叉排序樹,獲得的數組是有序的且是升序的。
下面咱們來看中序遍歷算法的實現。分爲遞歸和非遞歸兩種。
function BinaryTree() { // 省略二叉排序樹的建立 // 定義中序遍歷算法 let inOrderTraverseNode = function(node, callback) { if(node !== null) { inOrderTraverseNode(node.left, callback) // 先訪問左子樹 callback(node.key) // 再訪問當前根結點 inOrderTraverseNode(node.right, callback) // 訪問右子樹 } } // 定義中序遍歷方法 this.inOrderTraverse = function(callback) { inOrderTraverseNode(root, callback) } } let nodes = [8,3,10,1,6,14,4,7,13] let tree = new BinaryTree() nodes.forEach(function(key) { tree.insert(key) // 構造二叉樹 }) // 定義回調函數 let callback = function(key) { console.log(key) } tree.inOrderTraverse(callback) // 1 3 4 6 7 8 10 13 14
藉助於棧,先將左子樹所有放進棧中,以後輸出,最後處理右子樹。
function BinaryTree() { // 省略二叉排序樹的構建方法 // 定義中序遍歷算法 let inOrderTraverseNode = function(node, callback) { let stack = [] while(true) { // 將當前結點的左子樹推入棧 while(node !== null) { stack.push(node) node = node.left } // 定義終止條件 if(stack.length === 0) { break } let temp = stack.pop() callback(temp.key) node = temp.right } } this.inOrderTraverse = function(callback) { inOrderTraverseNode(root, callback) } } let nodes = [8,3,10,1,6,14,4,7,13] let tree = new BinaryTree() nodes.forEach(function(key) { tree.insert(key) // 構造二叉樹 }) // 定義回調函數 let callback = function(key) { console.log(key) } tree.inOrderTraverse(callback) // 1 3 4 6 7 8 10 13 14
後序遍歷也叫作後根遍歷、後序周遊,記作 左右根。
後序遍歷的做用用於文件系統路徑中,或將正常表達式變成逆波蘭表達式。
下面咱們來看後序遍歷算法的實現。分爲遞歸和非遞歸兩種。
// 先構造一棵二叉樹 function BinaryTree() { // 省略二叉排序樹的構建方法 // 定義後序遍歷算法 let postOrderTraverseNode = function(node, callback) { if(node !== null) { postOrderTraverseNode(node.left, callback) // 遍歷左子樹 postOrderTraverseNode(node.right, callback) // 再遍歷右子樹 callback(node.key) // 訪問根結點 } } // 定義後序遍歷方法 this.postOrderTraverse = function(callback) { postOrderTraverseNode(root, callback) } } let nodes = [8,3,10,1,6,14,4,7,13] let tree = new BinaryTree() nodes.forEach(function(key){ tree.insert(key) }) // 定義回調函數 let callback = function(key) { console.log(key) } tree.postOrderTraverse(callback) // 1 4 7 6 3 13 14 10 8
// 先構造一棵二叉樹 function BinaryTree() { // 省略二叉排序樹的構建方法 // 定義後序遍歷算法 let postOrderTraverseNode = function(node, callback) { let stack = [] let res = [] stack.push(node) while(stack.length) { let temp = stack.pop() res.push(temp.key) if(temp.left !== null) { stack.push(temp.left) } if(temp.right !== null) { stack.push(temp.right) } } callback(res.reverse()) } // 定義後序遍歷方法 this.postOrderTraverse = function(callback) { postOrderTraverseNode(root, callback) } } let nodes = [8,3,10,1,6,14,4,7,13] let tree = new BinaryTree() nodes.forEach(function(key){ tree.insert(key) }) // 定義回調函數 let callback = function(key) { console.log(key) } tree.postOrderTraverse(callback) // 1 4 7 6 3 13 14 10 8
廣度優先遍歷(Breadth First Search),又叫作寬度優先遍歷、層次遍歷,是指從根結點沿着樹的寬度搜索遍歷。
下面來看它的實現原理
function BinaryTree() { // 省略二叉排序樹的構建 let wideOrderTraverseNode = function(root) { let stack = [root] // 先將要遍歷的樹壓入棧 return function bfs(callback) { let node = stack.shift() if(node) { callback(node.key) if(node.left) stack.push(node.left); if(node.right) stack.push(node.right); bfs(callback) } } } this.wideOrderTraverse = function(callback) { wideOrderTraverseNode(root)(callback) } } let nodes = [8,3,10,1,6,14,4,7,13] let tree = new BinaryTree() nodes.forEach(function(key) { tree.insert(key) }) // 定義回調函數 let callback = function(key) { console.log(key) } tree.wideOrderTraverse(callback) // 8,3,10,1,6,14,4,7,13
使用棧實現,未訪問的元素入棧,訪問後則出棧,並將其leve左右元素入棧,直到葉子元素結束。
function BinaryTree() { // 省略二叉排序樹的構建 let wideOrderTraverseNode = function(node, callback) { let stack = [] if(node === null) { return [] } stack.push(node) while(stack.length) { // 每一層的結點數 let level = stack.length // 遍歷每一層元素 for(let i = 0; i < level; i++) { // 當前訪問的結點出棧 let temp = stack.shift() // 出棧結點的孩子入棧 temp.left ? queue.push(temp.left) : '' temp.right ? queue.push(temp.right) : '' callback(temp.key) } } } this.wideOrderTraverse = function(callback) { wideOrderTraverseNode(root, callback) } } let nodes = [8,3,10,1,6,14,4,7,13] let tree = new BinaryTree() nodes.forEach(function(key) { tree.insert(key) }) // 定義回調函數 let callback = function(key) { console.log(key) } tree.wideOrderTraverse(callback) // 8,3,10,1,6,14,4,7,13
function BinaryTree() { // 省略二叉排序樹的構建 let wideOrderTraverseNode = function(node, callback) { let stack = [] if(node === null) { return [] } stack.push(node) while(stack.length) { let temp = stack.shift() callback(temp.key) if(temp.left) { stack.push(temp.left) } if(temp.right) { stack.push(temp.right) } } } this.wideOrderTraverse = function(callback) { wideOrderTraverseNode(root, callback) } } let nodes = [8,3,10,1,6,14,4,7,13] let tree = new BinaryTree() nodes.forEach(function(key) { tree.insert(key) }) // 定義回調函數 let callback = function(key) { console.log(key) } tree.wideOrderTraverse(callback) // 8,3,10,1,6,14,4,7,13
鑑於篇幅過長,二叉樹結點的查找和刪除會在下一篇文章內~