摘要:二叉查找樹二叉查找樹也叫二叉搜索樹它只允許我們?cè)谧蠊?jié)點(diǎn)存儲(chǔ)比父節(jié)點(diǎn)更小的值,右節(jié)點(diǎn)存儲(chǔ)比父節(jié)點(diǎn)更大的值,上圖展示的就是一顆二叉查找樹。
前兩天接到了螞蟻金服的面試電話,面試官很直接,上來(lái)就拋出了三道算法題。。。
其中有一道關(guān)于二叉樹實(shí)現(xiàn)中序遍歷的,當(dāng)時(shí)沒(méi)回答好,所以特意學(xué)習(xí)了一把二叉樹的知識(shí),行文記錄總結(jié)。
二叉樹&二叉查找樹 樹相關(guān)術(shù)語(yǔ):節(jié)點(diǎn): 樹中的每個(gè)元素稱為一個(gè)節(jié)點(diǎn),
根節(jié)點(diǎn): 位于整棵樹頂點(diǎn)的節(jié)點(diǎn),它沒(méi)有父節(jié)點(diǎn), 如上圖 5
子節(jié)點(diǎn): 其他節(jié)點(diǎn)的后代
葉子節(jié)點(diǎn): 沒(méi)有子節(jié)點(diǎn)的元素稱為葉子節(jié)點(diǎn), 如上圖 3 8 24
二叉樹:二叉樹就是一種數(shù)據(jù)結(jié)構(gòu), 它的組織關(guān)系就像是自然界中的樹一樣。官方語(yǔ)言的定義是:是一個(gè)有限元素的集合,該集合或者為空、或者由一個(gè)稱為根的元素及兩個(gè)不相交的、被分別稱為左子樹和右子樹的二叉樹組成。
二叉查找樹:
二叉查找樹也叫二叉搜索樹(BST),它只允許我們?cè)谧蠊?jié)點(diǎn)存儲(chǔ)比父節(jié)點(diǎn)更小的值,右節(jié)點(diǎn)存儲(chǔ)比父節(jié)點(diǎn)更大的值,上圖展示的就是一顆二叉查找樹。
首先創(chuàng)建一個(gè)類來(lái)表示二叉查找樹,它的內(nèi)部應(yīng)該有一個(gè)Node類,用來(lái)創(chuàng)建節(jié)點(diǎn)
function BinarySearchTree () { var Node = function(key) { this.key = key, this.left = null, this.right = null } var root = null }
它還應(yīng)該有一些方法:
insert(key) 插入一個(gè)新的鍵
inOrderTraverse() 對(duì)樹進(jìn)行中序遍歷,并打印結(jié)果
preOrderTraverse() 對(duì)樹進(jìn)行先序遍歷,并打印結(jié)果
postOrderTraverse() 對(duì)樹進(jìn)行后序遍歷,并打印結(jié)果
search(key) 查找樹中的鍵,如果存在返回true,不存在返回fasle
findMin() 返回樹中的最小值
findMax() 返回樹中的最大值
remove(key) 刪除樹中的某個(gè)鍵
向樹中插入一個(gè)鍵向樹中插入一個(gè)新的鍵,首頁(yè)應(yīng)該創(chuàng)建一個(gè)用來(lái)表示新節(jié)點(diǎn)的Node類實(shí)例,因此需要new一下Node類并傳入需要插入的key值,它會(huì)自動(dòng)初始化為左右節(jié)點(diǎn)為null的一個(gè)新節(jié)點(diǎn)
然后,需要做一些判斷,先判斷樹是否為空,若為空,新插入的節(jié)點(diǎn)就作為根節(jié)點(diǎn),如不為空,調(diào)用一個(gè)輔助方法insertNode()方法,將根節(jié)點(diǎn)和新節(jié)點(diǎn)傳入
this.insert = function(key) { var newNode = new Node(key) if(root === null) { root = newNode } else { insertNode(root, newNode) } }
定義一下insertNode() 方法,這個(gè)方法會(huì)通過(guò)遞歸得調(diào)用自身,來(lái)找到新添加節(jié)點(diǎn)的合適位置
var 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) } } }完成中序遍歷方法
要實(shí)現(xiàn)中序遍歷,我們需要一個(gè)inOrderTraverseNode(node)方法,它可以遞歸調(diào)用自身來(lái)遍歷每個(gè)節(jié)點(diǎn)
this.inOrderTraverse = function() { inOrderTraverseNode(root) }
這個(gè)方法會(huì)打印每個(gè)節(jié)點(diǎn)的key值,它需要一個(gè)遞歸終止條件————檢查傳入的node是否為null,如果不為空,就繼續(xù)遞歸調(diào)用自身檢查node的left、right節(jié)點(diǎn)
實(shí)現(xiàn)起來(lái)也很簡(jiǎn)單:
var inOrderTraverseNode = function(node) { if (node !== null) { inOrderTraverseNode(node.left) console.log(node.key) inOrderTraverseNode(node.right) } }先序遍歷、后序遍歷
有了中序遍歷的方法,只需要稍作改動(dòng),就可以實(shí)現(xiàn)先序遍歷和后序遍歷了
上代碼:
這樣就可以對(duì)整棵樹進(jìn)行中序遍歷了
// 實(shí)現(xiàn)先序遍歷 this.preOrderTraverse = function() { preOrderTraverseNode(root) } var preOrderTraverseNode = function(node) { if (node !== null) { console.log(node.key) preOrderTraverseNode(node.left) preOrderTraverseNode(node.right) } } // 實(shí)現(xiàn)后序遍歷 this.postOrderTraverse = function() { postOrderTraverseNode(root) } var postOrderTraverseNode = function(node) { if (node !== null) { postOrderTraverseNode(node.left) postOrderTraverseNode(node.right) console.log(node.key) } }
發(fā)現(xiàn)了吧,其實(shí)就是內(nèi)部語(yǔ)句更換了前后位置,這也剛好符合三種遍歷規(guī)則:先序遍歷(根-左-右)、中序遍歷(左-根-右)、中序遍歷(左-右-根)
先來(lái)做個(gè)測(cè)試吧現(xiàn)在的完整代碼如下:
function BinarySearchTree () { var Node = function(key) { this.key = key, this.left = null, this.right = null } var root = null //插入節(jié)點(diǎn) this.insert = function(key) { var newNode = new Node(key) if(root === null) { root = newNode } else { insertNode(root, newNode) } } var 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) } } } //實(shí)現(xiàn)中序遍歷 this.inOrderTraverse = function() { inOrderTraverseNode(root) } var inOrderTraverseNode = function(node) { if (node !== null) { inOrderTraverseNode(node.left) console.log(node.key) inOrderTraverseNode(node.right) } } // 實(shí)現(xiàn)先序遍歷 this.preOrderTraverse = function() { preOrderTraverseNode(root) } var preOrderTraverseNode = function(node) { if (node !== null) { console.log(node.key) preOrderTraverseNode(node.left) preOrderTraverseNode(node.right) } } // 實(shí)現(xiàn)后序遍歷 this.postOrderTraverse = function() { postOrderTraverseNode(root) } var postOrderTraverseNode = function(node) { if (node !== null) { postOrderTraverseNode(node.left) postOrderTraverseNode(node.right) console.log(node.key) } } }
竟然已經(jīng)完成了添加新節(jié)點(diǎn)和遍歷的方式,我們來(lái)測(cè)試一下吧:
定義一個(gè)數(shù)組,里面有一些元素
var arr = [9,6,3,8,12,15]
我們將arr中的每個(gè)元素依此插入到二叉搜索樹中,然后打印結(jié)果
var tree = new BinarySearchTree() arr.map(item => { tree.insert(item) }) tree.inOrderTraverse() tree.preOrderTraverse() tree.postOrderTraverse()
運(yùn)行代碼后,我們先來(lái)看看插入節(jié)點(diǎn)后整顆樹的情況:
輸出結(jié)果
中序遍歷:
3
6
8
9
12
15
先序遍歷:
9
6
3
8
12
15
后序遍歷:
3
8
6
15
12
9
很明顯,結(jié)果是符合預(yù)期的,所以,我們用上面的JavaScript代碼,實(shí)現(xiàn)了對(duì)樹的節(jié)點(diǎn)插入,和三種遍歷方法,同時(shí),很明顯可以看到,在二叉查找樹樹種,最左側(cè)的節(jié)點(diǎn)的值是最小的,而最右側(cè)的節(jié)點(diǎn)的值是最大的,所以二叉查找樹可以很方便的拿到其中的最大值和最小值
查找最小、最大值怎么做呢?其實(shí)只需要將根節(jié)點(diǎn)傳入minNode/或maxNode方法,然后通過(guò)循環(huán)判斷node為左側(cè)(minNode)/右側(cè)(maxNode)的節(jié)點(diǎn)為null
實(shí)現(xiàn)代碼:
// 查找最小值 this.findMin = function() { return minNode(root) } var minNode = function(node) { if (node) { while (node && node.left !== null) { node = node.left } return node.key } return null } // 查找最大值 this.findMax = function() { return maxNode(root) } var maxNode = function (node) { if(node) { while (node && node.right !== null) { node =node.right } return node.key } return null }所搜特定值
this.search = function(key) { return searchNode(root, key) }
同樣,實(shí)現(xiàn)它需要定義一個(gè)輔助方法,這個(gè)方法首先會(huì)檢驗(yàn)node的合法性,如果為null,直接退出,并返回fasle。如果傳入的key比當(dāng)前傳入node的key值小,它會(huì)繼續(xù)遞歸查找node的左側(cè)節(jié)點(diǎn),反之,查找右側(cè)節(jié)點(diǎn)。如果找到相等節(jié)點(diǎn),直接退出,并返回true
var searchNode = function(node, key) { if (node === null) { return false } if (key < node.key) { return searchNode(node.left, key) }else if (key > node.key) { return searchNode(node.right, key) }else { return true } }移除節(jié)點(diǎn)
移除節(jié)點(diǎn)的實(shí)現(xiàn)情況比較復(fù)雜,它會(huì)有三種不同的情況:
需要移除的節(jié)點(diǎn)是一個(gè)葉子節(jié)點(diǎn)
需要移除的節(jié)點(diǎn)包含一個(gè)子節(jié)點(diǎn)
需要移除的節(jié)點(diǎn)包含兩個(gè)子節(jié)點(diǎn)
和實(shí)現(xiàn)搜索指定節(jié)點(diǎn)一元,要移除某個(gè)節(jié)點(diǎn),必須先找到它所在的位置,因此移除方法的實(shí)現(xiàn)中部分代碼和上面相同:
// 移除節(jié)點(diǎn) this.remove = function(key) { removeNode(root,key) } var removeNode = function(node, key) { if (node === null) { return null } if (key < node.key) { node.left = removeNode(node.left, key) return node }else if(key > node.key) { node.right = removeNode(node.right,key) return node }else{ //需要移除的節(jié)點(diǎn)是一個(gè)葉子節(jié)點(diǎn) if (node.left === null && node.right === null) { node = null return node } //需要移除的節(jié)點(diǎn)包含一個(gè)子節(jié)點(diǎn) if (node.letf === null) { node = node.right return node }else if (node.right === null) { node = node.left return node } //需要移除的節(jié)點(diǎn)包含兩個(gè)子節(jié)點(diǎn) var aux = findMinNode(node.right) node.key = aux.key node.right = removeNode(node.right, axu.key) return node } } var findMinNode = function(node) { if (node) { while (node && node.left !== null) { node = node.left } return node } return null }
其中,移除包含兩個(gè)子節(jié)點(diǎn)的節(jié)點(diǎn)是最復(fù)雜的情況,它包含左側(cè)節(jié)點(diǎn)和右側(cè)節(jié)點(diǎn),對(duì)它進(jìn)行移除主要需要三個(gè)步驟:
需要找到它右側(cè)子樹中的最小節(jié)點(diǎn)來(lái)代替它的位置
將它右側(cè)子樹中的最小節(jié)點(diǎn)移除
將更新后的節(jié)點(diǎn)的引用指向原節(jié)點(diǎn)的父節(jié)點(diǎn)
有點(diǎn)繞兒,但必須這樣,因?yàn)閯h除元素后的二叉搜索樹必須保持它的排序性質(zhì)
測(cè)試刪除節(jié)點(diǎn)tree.remove(8) tree.inOrderTraverse()
打印結(jié)果:
3
6
9
12
15
8 這個(gè)節(jié)點(diǎn)被成功刪除了,但是對(duì)二叉查找樹進(jìn)行中序遍歷依然是保持排序性質(zhì)的
到這里,一個(gè)簡(jiǎn)單的二叉查找樹就基本上完成了,我們?yōu)樗鼘?shí)現(xiàn)了,添加、查找、刪除以及先中后三種遍歷方法
存在的問(wèn)題但是實(shí)際上這樣的二叉查找樹是存在一些問(wèn)題的,當(dāng)我們不斷的添加更大/更小的元素的時(shí)候,會(huì)出現(xiàn)如下情況:
tree.insert(16) tree.insert(17) tree.insert(18)
來(lái)看看現(xiàn)在整顆樹的情況:
很容易發(fā)現(xiàn),它是不平衡的,這又會(huì)引出平衡樹的概念,要解決這個(gè)問(wèn)題,還需要更復(fù)雜的實(shí)現(xiàn),例如:AVL樹,紅黑樹 哎,之后再慢慢去學(xué)習(xí)吧
關(guān)于實(shí)現(xiàn)二叉排序樹,我也找到慕課網(wǎng)的一系列的視頻:Javascript實(shí)現(xiàn)二叉樹算法,
內(nèi)容和上述實(shí)現(xiàn)基本一致
原文鏈接:行無(wú)忌的成長(zhǎng)小屋:JavaScript實(shí)現(xiàn)簡(jiǎn)單二叉查找樹
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/96548.html
摘要:原文博客地址學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)四樹知乎專欄簡(jiǎn)書專題前端進(jìn)擊者知乎前端進(jìn)擊者簡(jiǎn)書博主博客地址的個(gè)人博客人之所能,不能兼?zhèn)?,棄其所短,取其所長(zhǎng)。通常子樹被稱作左子樹和右子樹。敬請(qǐng)期待數(shù)據(jù)結(jié)構(gòu)篇最后一篇文章學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)五圖參考文章樹數(shù)據(jù)結(jié)構(gòu)二叉樹 前言 總括: 本文講解了數(shù)據(jù)結(jié)構(gòu)中的[樹]的概念,盡可能通俗易懂的解釋樹這種數(shù)據(jù)結(jié)構(gòu)的概念,使用javascript實(shí)現(xiàn)了樹,如有紕漏,歡迎批評(píng)指正。 ...
摘要:筆者寫的數(shù)據(jù)結(jié)構(gòu)與算法之美系列用的語(yǔ)言是,旨在入門數(shù)據(jù)結(jié)構(gòu)與算法和方便以后復(fù)習(xí)。非線性表中的樹堆是干嘛用的其數(shù)據(jù)結(jié)構(gòu)是怎樣的希望大家?guī)е@兩個(gè)問(wèn)題閱讀下文。其中,前中后序,表示的是節(jié)點(diǎn)與它的左右子樹節(jié)點(diǎn)遍歷訪問(wèn)的先后順序。 showImg(https://segmentfault.com/img/bVbuvnj?w=900&h=250); 1. 前言 想學(xué)好前端,先練好內(nèi)功,內(nèi)功不行,...
摘要:因此,根據(jù)題目給出的先序遍歷和中序遍歷,可以畫出二叉樹選參考數(shù)據(jù)結(jié)構(gòu)與算法描述實(shí)現(xiàn)二叉樹算法淺談數(shù)據(jù)結(jié)構(gòu)二叉樹慕課網(wǎng)實(shí)現(xiàn)二叉樹算法前端樹控件騰訊軟件開發(fā)面試題 內(nèi)容銜接上一章 數(shù)據(jù)結(jié)構(gòu)與算法:常見(jiàn)排序算法 內(nèi)容提要 什么是樹 - 為什么使用樹 二叉樹 二叉查找樹 紅黑樹 B、B+樹 堆 伸展樹 樹 可以點(diǎn)擊鏈接感受下筆者用d3.js畫的tree https://codepen...
摘要:二叉搜索樹的特性二叉搜索樹由于其獨(dú)特的數(shù)據(jù)結(jié)構(gòu),使得其無(wú)論在增刪,還是查找,時(shí)間復(fù)雜度都是,為二叉樹的高度。二叉搜索樹的查找查找很簡(jiǎn)單,根據(jù)左子節(jié)點(diǎn)比該節(jié)點(diǎn)小,右子節(jié)點(diǎn)比該節(jié)點(diǎn)大的原則進(jìn)行循環(huán)判斷即可。 什么是二叉樹 二叉樹就是樹的每個(gè)節(jié)點(diǎn)最多只能有兩個(gè)子節(jié)點(diǎn) 什么是二叉搜索樹 二叉搜索樹在二叉樹的基礎(chǔ)上,多了一個(gè)條件,就是二叉樹在插入值時(shí),若插入值比當(dāng)前節(jié)點(diǎn)小,就插入到左節(jié)點(diǎn),否則插...
摘要:定義樹以及相關(guān)概念二叉搜索樹的定義首先是二叉樹最多有兩個(gè)節(jié)點(diǎn),分別是左右節(jié)點(diǎn)子節(jié)點(diǎn)的位置是確定的,變現(xiàn)為左子節(jié)點(diǎn)的值小于其父節(jié)點(diǎn)右子節(jié)點(diǎn)的值大于其父節(jié)點(diǎn)在中的描述描述的完整代碼傳送門可視化傳送門節(jié)點(diǎn)類樹是由節(jié)點(diǎn)組成的,要實(shí)現(xiàn)樹那么先要實(shí)現(xiàn)節(jié) 定義 樹以及相關(guān)概念 showImg(https://segmentfault.com/img/remote/1460000012578627?w...
閱讀 1826·2021-10-18 13:30
閱讀 2700·2021-10-09 10:02
閱讀 3049·2021-09-28 09:35
閱讀 2146·2019-08-26 13:39
閱讀 3589·2019-08-26 13:36
閱讀 2011·2019-08-26 11:46
閱讀 1193·2019-08-23 14:56
閱讀 1777·2019-08-23 10:38