javascript
javascript实现二叉搜索树
在使用javascript實(shí)現(xiàn)基本的數(shù)據(jù)結(jié)構(gòu)中,練習(xí)了好幾周,對(duì)基本的數(shù)據(jù)結(jié)構(gòu)如 棧、隊(duì)列、鏈表、集合、哈希表、樹(shù)、圖等內(nèi)容進(jìn)行了總結(jié)并且寫(xiě)了筆記和代碼。
在 github中可以看到? 點(diǎn)擊查看,可以關(guān)注一下我哈。
?
樹(shù)的基本術(shù)語(yǔ)
二叉樹(shù)節(jié)點(diǎn)的存儲(chǔ)結(jié)構(gòu)
創(chuàng)建一個(gè)二叉搜索樹(shù)
二叉樹(shù)的先序、中序、后續(xù)遍歷算法
二叉樹(shù)的非遞歸先序、中序、后續(xù)遍歷算法。
?
?
?
文章對(duì)樹(shù)了解的不多的人有點(diǎn)不友好,這里簡(jiǎn)單介紹(從書(shū)上抄下來(lái))那些基本的一點(diǎn)概念吧。
?
看下面這個(gè)示意圖
?
?
?
樹(shù)的基本術(shù)語(yǔ):
結(jié)點(diǎn):A、B、C等都是結(jié)點(diǎn),結(jié)點(diǎn)不僅包含數(shù)據(jù)元素,而且包含指向子樹(shù)的分支。例如,A結(jié)點(diǎn)不僅包含數(shù)據(jù)元素A、還包含3個(gè)指向子樹(shù)的指針。
結(jié)點(diǎn)的度:結(jié)點(diǎn)擁有的子樹(shù)個(gè)數(shù)或者分支的個(gè)數(shù),例如A結(jié)點(diǎn)有3棵子樹(shù),所以A結(jié)點(diǎn)的度為3.
樹(shù)的度:樹(shù)中各結(jié)點(diǎn)度的最大值。如例子中結(jié)點(diǎn)度最大為3(A、D結(jié)點(diǎn))。最小為0(F、G、I、J、K、L、M),所以樹(shù)的度為3。
葉子節(jié)點(diǎn):又叫做終端節(jié)點(diǎn),指度為0的節(jié)點(diǎn),F、G、I、J、K、L、M節(jié)點(diǎn)都是葉子節(jié)點(diǎn)。
孩子:結(jié)點(diǎn)的子樹(shù)的根,如A節(jié)點(diǎn)的孩子為B、C、D。
雙親:與孩子的定義對(duì)應(yīng),如B C D結(jié)點(diǎn)的雙親都是A。
兄弟:同一個(gè)雙親的孩子之間互為兄弟。如B、C、D互為兄弟,因?yàn)樗鼈兌际茿節(jié)點(diǎn)的孩子。
祖先:從根到某節(jié)點(diǎn)的路徑上的所有結(jié)點(diǎn),都是這個(gè)節(jié)點(diǎn)的祖先。如K的祖先是A B E,因?yàn)閺腁到K的路徑為 A---B---E--K。
子孫: 以某節(jié)點(diǎn)為根的子樹(shù)中的所有結(jié)點(diǎn),都是該結(jié)點(diǎn)的子孫。如D的子孫為H I J M。
層次:從根開(kāi)始,根為第一層,根的孩子為第二層,根的孩子的孩子為第三層,以此類(lèi)推。
樹(shù)的高度(或者深度):樹(shù)中結(jié)點(diǎn)的最大層次,如例子中的樹(shù)共有4層,所以高度為4.
?
理解了上面的樹(shù)一些基本一些的概念后,我們來(lái)看一下什么是二叉樹(shù)。
1)每個(gè)結(jié)點(diǎn)最多只有兩棵子樹(shù),即二叉樹(shù)中的節(jié)點(diǎn)的度只能為0、1、2
2) 子樹(shù)有左右之分,不能顛倒。
?
以下二叉樹(shù)的5中基本狀態(tài)
?
?
構(gòu)造一個(gè)二叉樹(shù)的節(jié)點(diǎn)存儲(chǔ)結(jié)構(gòu)
我們會(huì)發(fā)現(xiàn),二叉樹(shù)中的存儲(chǔ)結(jié)構(gòu)一個(gè)是值,一個(gè)是左邊有一個(gè),右邊有一個(gè)。他們分別叫左孩子/左子樹(shù)? 右孩子/右子樹(shù)。
所以我們會(huì)很容易的寫(xiě)出來(lái)一個(gè)節(jié)點(diǎn)的構(gòu)造函數(shù)。
1 // 樹(shù)的結(jié)構(gòu) 2 class BTNode { 3 constructor() { 4 this.key = key; 5 this.lchild = null; 6 this.rchild = null; 7 } 8 }?
實(shí)現(xiàn)一個(gè)二叉搜索樹(shù) / 二叉排序樹(shù)
看一下定義
二叉排序樹(shù)或者是空樹(shù),或者是滿(mǎn)足以下性質(zhì)的二叉樹(shù)。
1) 若它的左子樹(shù)不空,則左子樹(shù)上的所有關(guān)鍵字的值均小于根關(guān)鍵字的值。
2)若它的右子樹(shù)不空,則右子樹(shù)上所有關(guān)鍵字的值均大于根關(guān)鍵字的值。
3)左右子樹(shù)又是一棵二叉排序樹(shù)。
?
舉個(gè)例子
假如要插入一堆數(shù)字,數(shù)字為 20 10 5 15 13 18 17 30
?
?
?
?
?那么用代碼如何實(shí)現(xiàn)呢?
1 let BST = (function () { 2 3 let ROOT = Symbol(); 4 5 // 節(jié)點(diǎn)結(jié)構(gòu) 6 let BTNode = class { 7 constructor(key) { 8 this.key = key; 9 this.lchild = null; 10 this.rchild = null; 11 } 12 } 13 14 // 遞歸插入節(jié)點(diǎn) 15 let recursionInsert = function (root, node) { 16 if (node.key < root.key) { 17 if (root.lchild) { 18 recursionInsert(root.lchild, node); 19 } else { 20 root.lchild = node; 21 } 22 } else { 23 if (root.rchild) { 24 recursionInsert(root.rchild, node); 25 } else { 26 root.rchild = node; 27 } 28 } 29 } 30 31 // 二叉搜索樹(shù)類(lèi) 32 return class { 33 constructor() { 34 this[ROOT] = null; 35 } 36 37 // 插入 38 insert(key) { 39 let node = new BTNode(key); 40 let root = this[ROOT]; 41 if (!root) { 42 this[ROOT] = node; 43 return; 44 } 45 // 遞歸插入 46 recursionInsert(root, node); 47 } 48 } 49 50 })(); 51 52 53 let bst = new BST(); 54 55 56 bst.insert(20); 57 bst.insert(10); 58 bst.insert(5); 59 bst.insert(15); 60 bst.insert(13); 61 bst.insert(18); 62 bst.insert(17); 63 bst.insert(30); 64 65 console.log(bst);?
?在瀏覽器中一層一層的展開(kāi)看看是否和我們的一樣。
?
二叉樹(shù)的遍歷算法?
二叉樹(shù)的遍歷算法,二叉樹(shù)的遍歷就是按照某種規(guī)則將二叉樹(shù)中的所有數(shù)據(jù)都訪(fǎng)問(wèn)一遍。
1 let BST = (function () { 2 3 let ROOT = Symbol(); 4 5 // 節(jié)點(diǎn)結(jié)構(gòu) 6 let BTNode = class { 7 constructor(key) { 8 this.key = key; 9 this.lchild = null; 10 this.rchild = null; 11 } 12 } 13 14 // 遞歸插入節(jié)點(diǎn) 15 let recursionInsert = function (root, node) { 16 if (node.key < root.key) { 17 if (root.lchild) { 18 recursionInsert(root.lchild, node); 19 } else { 20 root.lchild = node; 21 } 22 } else { 23 if (root.rchild) { 24 recursionInsert(root.rchild, node); 25 } else { 26 root.rchild = node; 27 } 28 } 29 }; 30 31 // 用于中序遍歷二叉樹(shù)的方法 32 let inorderTraversal = function (root, arr) { 33 if (!root) return; 34 inorderTraversal(root.lchild, arr); 35 arr.push(root.key); 36 inorderTraversal(root.rchild, arr); 37 }; 38 39 // 用于先序遍歷的遞歸函數(shù) 40 let preOrderTraversal = function (root, arr) { 41 if (!root) return; 42 arr.push(root.key); 43 preOrderTraversal(root.lchild, arr); 44 preOrderTraversal(root.rchild, arr); 45 }; 46 47 // 用于后續(xù)遍歷的遞歸函數(shù) 48 let lastOrderTraversal = function (root, arr) { 49 if (!root) return; 50 lastOrderTraversal(root.lchild, arr); 51 lastOrderTraversal(root.rchild, arr); 52 arr.push(root.key); 53 }; 54 55 // 二叉搜索樹(shù)類(lèi) 56 return class { 57 constructor() { 58 this[ROOT] = null; 59 } 60 61 // 插入 62 insert(key) { 63 let node = new BTNode(key); 64 let root = this[ROOT]; 65 if (!root) { 66 this[ROOT] = node; 67 return; 68 } 69 // 遞歸插入 70 recursionInsert(root, node); 71 } 72 73 74 // 中序遍歷二叉樹(shù) 75 inorderTraversal() { 76 let arr = []; 77 inorderTraversal(this[ROOT], arr); 78 return arr; 79 } 80 81 // 先序遍歷二叉樹(shù) 82 preOrderTraversal() { 83 let arr = []; 84 preOrderTraversal(this[ROOT], arr); 85 return arr; 86 } 87 88 // 后續(xù)遍歷 89 lastOrderTraversal() { 90 let arr = []; 91 lastOrderTraversal(this[ROOT], arr); 92 return arr; 93 } 94 } 95 96 })(); 97 98 99 let bst = new BST(); 100 101 bst.insert(20); 102 bst.insert(15); 103 bst.insert(7); 104 bst.insert(40); 105 bst.insert(30); 106 bst.insert(45); 107 bst.insert(50); 108 109 110 console.log(bst); 111 112 113 let a = bst.inorderTraversal(); 114 let b = bst.preOrderTraversal(); 115 let c = bst.lastOrderTraversal(); 116 117 console.log(a); 118 console.log(b); 119 console.log(c);
?
廣度優(yōu)先遍歷
// 廣度優(yōu)先遍歷 breadthRirstSearch() {// 初始化用于廣度優(yōu)先遍歷的隊(duì)列let queue = new Queue();console.log('根節(jié)點(diǎn)', this[ROOT]);let arr = [];let root = this[ROOT];if (!root) return;queue.enqueue(root);while (queue.size()) {let queueFirst = queue.dequeue();arr.push(queueFirst.key);queueFirst.lchild && queue.enqueue(queueFirst.lchild);queueFirst.rchild && queue.enqueue(queueFirst.rchild);}return arr; }?
?
有的人可能會(huì)想到,關(guān)于二叉樹(shù)深度優(yōu)先遍歷算法的非遞歸實(shí)現(xiàn)和遞歸實(shí)現(xiàn),一個(gè)是用戶(hù)自己定義棧,一個(gè)是系統(tǒng)棧,為什么用戶(hù)自己定義的棧要比系統(tǒng)棧執(zhí)行高效?
一個(gè)較為通俗的解釋是:遞歸函數(shù)所申請(qǐng)的系統(tǒng)棧,是一個(gè)所有遞歸函數(shù)都通用的棧,對(duì)于二叉樹(shù)深度優(yōu)先遍歷算法,系統(tǒng)棧除了記錄訪(fǎng)問(wèn)過(guò)的節(jié)點(diǎn)信息之外,還有其他信息需要記錄,以實(shí)現(xiàn)函數(shù)的遞歸調(diào)用,用戶(hù)自己定義的棧僅保存了遍歷所需的節(jié)點(diǎn)信息,是對(duì)遍歷算法的一個(gè)針對(duì)性的設(shè)計(jì),對(duì)于遍歷算法來(lái)說(shuō),顯然要比遞歸函數(shù)通用的系統(tǒng)棧更高,也就是一般情況下,專(zhuān)業(yè)的要比通用的要好一些。
然而在實(shí)際應(yīng)用中不是這樣的,如尾遞歸在很多機(jī)器上都會(huì)被優(yōu)化為循環(huán),因此遞歸函數(shù)不一定就比非遞歸函數(shù)執(zhí)行效率低。
?
棧數(shù)據(jù)結(jié)構(gòu),滿(mǎn)足后進(jìn)先出的規(guī)則用來(lái)輔佐我們遍歷
// 棧結(jié)構(gòu) 用來(lái)輔助非遞歸遍歷 class Stack {constructor() {this.items = [];}push(data) {this.items.push(data);}pop() {return this.items.pop();}peek() {return this.items[this.items.length - 1];}size() {return this.items.length;} }非遞歸的先序遍歷
preOrderTraversal() {console.log('先序遍歷');let root = this[ROOT];// 初始化輔助遍歷存儲(chǔ)的棧let stack = new Stack();let arr = []; // 用于存儲(chǔ)先序遍歷的順序 stack.push(root);// 如果棧不為空 則一直走while (stack.size()) {let stackTop = stack.pop();// 訪(fǎng)問(wèn)棧頂元素 arr.push(stackTop.key);stackTop.rchild && stack.push(stackTop.rchild);stackTop.lchild && stack.push(stackTop.lchild);}return arr; }?
?非遞歸的中序排序
// 中序遍歷二叉樹(shù) inorderTraversal() {// 初始化用于輔助排序的棧let stack = new Stack;let p = null; // 用于指向當(dāng)前遍歷到的元素let arr = []; // 用戶(hù)記錄排序的順序p = this[ROOT];while (stack.size() || p !== null) {while (p !== null) {stack.push(p);p = p.lchild;}// 如果棧不為空 出棧if (stack.size()) {p = stack.pop();arr.push(p.key);p = p.rchild;}}return arr; }非遞歸的后序排序
// 中序遍歷二叉樹(shù) inorderTraversal() {// 初始化用于輔助排序的棧let stack = new Stack;let p = null; // 用于指向當(dāng)前遍歷到的元素let arr = []; // 用戶(hù)記錄排序的順序p = this[ROOT];while (stack.size() || p !== null) {while (p !== null) {stack.push(p);p = p.lchild;}// 如果棧不為空 出棧if (stack.size()) {p = stack.pop();arr.push(p.key);p = p.rchild;}}return arr; }?
?
?想一下,如果我們的插入順序第一個(gè)數(shù)非常大,然后后面的數(shù)字都是越來(lái)越小的會(huì)有什么問(wèn)題產(chǎn)生呢?
?
下一篇文章講述這種問(wèn)題的一個(gè)解決方案,平衡二叉樹(shù)。
?
轉(zhuǎn)載于:https://www.cnblogs.com/z937741304/p/11544054.html
總結(jié)
以上是生活随笔為你收集整理的javascript实现二叉搜索树的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 一种算法的实现,几个相同大小的div组合
- 下一篇: tomcat启动完成执行 某个方法 定时