從今天起,作一個牛X的人,早起,健身,修煉算法
從今天起,關心代碼質量,我有一個夢想,朝九晚五,年薪百萬
從今天起,和每個親人通訊,告訴他們個人決心
那成功的天使告訴個人
我想告訴每個人
給每個文件、每個變量取一個溫暖的名字
陌生人,我也爲你祝福
願你有一個燦爛的前程
願你的頭髮再也不減小
願你明天還是公司棟樑
而我,只願朝九晚五,年薪百萬
javascript
書接上文從今天起,天天詳解一道算法題,今天實現一個二叉查找樹。java
二叉查找樹是二叉樹的一種特殊狀況,二叉查找樹的左邊節點的值老是比右邊的小,先看下定義:node
二叉查找樹(Binary Search Tree),(又:二叉搜索樹,二叉排序樹)它或者是一棵空樹,或者是具備下列性質的二叉樹: 若它的左子樹不空,則左子樹上全部結點的值均小於它的根結點的值; 若它的右子樹不空,則右子樹上全部結點的值均大於它的根結點的值; 它的左、右子樹也分別爲二叉查找樹。算法
節點:包含一個數據元素及若干指向子樹的分支;
根節點:第一層的節點(圖中的8);
葉子節點:結點的子樹的根稱爲該結點的葉子節點,這是一個相對概念;
結點層:根結點的層定義爲1;根的孩子爲第二層結點,依此類推;
樹的深度:樹中最大的結點層;
結點的度:結點子樹的個數(二叉樹爲2);
樹的度:樹中最大的結點度(二叉樹爲2);
bash
實現二叉樹,先要實現上面最重要的一個概念--節點:
它有三個屬性,值、左節點的指針、右節點的指針:數據結構
class Node {
constructor(data, left, right) {
this.value = data
this.left = left
this.right = right
}
show(){
return this.data
}
}
複製代碼
下面咱們實現二叉查找樹的類:
咱們能夠想向到它應該有一個根節點,還應該有一些操做該類的方法,我會分別解釋,先看代碼(爲了不代碼太長,我把各個方法分開寫了,他們應該都在BST類裏的):
學習
該方法負責向該二叉樹中插入一條數據,實現的思路是:
一、經過Node類建立包含該條數據的一個節點;
二、判斷若是該實例的根節點root爲null,則把這個節點設置爲根節點;
三、若是根節點不爲null,則建立一個指向當前節點的指針(第一次指向根節點),而後比較data是否大於當前節點的data;
四、若是小於當前節點的data,則說明這個節點應該往左邊放,不然說明應該往右邊放;
五、經過循環比較,最終當前節點會找到null,這時候把新節點插入到這裏(也就是讓當parentNode的子節點指向新節點);
ui
class BST {
constructor() {
this.root = null
}
insert(data) {
let newNode = new Node(data, null, null)
if (root == null) {
this.root = newNode
} else {
let currentNode = this.root
let parentNode
while (true) {
parentNode = currentNode
if (data < currentNode.data) {
currentNode = currentNode.left
if (currentNode == null) parentNode.left = newNode
break
} else {
currentNode = currentNode.right
if (currentNode == null) parentNode.right = newNode
break
}
}
}
}
}
複製代碼
樹的遍歷分爲3種類型:中序、先序、後序,其中「中」、「先」、「後」指的是訪問根節點的時機。
下面代碼中的each方法中有三個console,從上至下他們分別對應先序、後序、中序。他們分別表明訪問根節點(2)的時機。也就是一個在最早,一個在最後,一個在中間。this
each(node) {
node = node || this.root
//console.log(node.data) // 2,1,3
if (node.left) this.each(node.left)
//console.log(node.data) // 1,2,3
if (node.right) this.each(node.right)
//console.log(node.data) // 1,3,2
}
複製代碼
這兩個方法比較簡單,最大值就是遍歷右子樹,找到最後一個;最小值就是遍歷左子樹找到最後一個
注:max和min方法有參數,這是爲了查找制定節點的最大最小值,下面的remove方法中用到了min方法查找制定節點的最小值spa
max(node) {
let currentNode = node || this.root
while (currentNode.right) {
currentNode = currentNode.right
}
return currentNode.data
}
min(node) {
let currentNode = node || this.root
while (currentNode.left) {
currentNode = currentNode.left
}
return currentNode.data
}
複製代碼
has方法和上面的insert方法的思路相似,都是建立一個指針指向根節點,比較傳入的data值,若是比根節點大就把指針向右移,不然向左。
一旦找到相等的值就返回ture,若是遍歷到最後一個節點都沒有找到,返回false。
has(data) {
let currentNode = this.root
while (currentNode) {
if (data == currentNode.data) {
return true
} else if (data < currentNode.data) {
currentNode = currentNode.left
} else {
currentNode = currentNode.right
}
}
return false
}
複製代碼
刪除方法相對較複雜,思路爲:
一、定義remove方法,方法中把根節點更改成一個遞歸方法removeNode的返回值
二、removeNode是一個被刪除了制定節點的新樹,它的實現須要考慮如下幾種狀況:
a:若是輸入節點爲null,則返回null
b:若是data與node的data相等,說明這個節點就是須要被刪除的節點(這種狀況又分爲幾種狀況,下面單獨說)
c:若是data比node的data小,則說明須要刪除的節點在node的左邊,那麼就須要遞歸它左邊這個節點,直到找到相等的或者null
d:若是data比node的data大,思路同c
接下來講b(命中相等節點)的狀況:
一、若是這個節點沒有子節點,那麼直接刪除便可
二、若是這個節點只有一個節點,那麼它的這個子節點應該取代它的位置(代碼中第2、三行註釋下的代碼)
三、若是這個節點有左右兩個節點,那麼咱們找到它的右子樹的最小值minData,這個節點的值就應該爲minData,同時讓它的右子樹爲它以前的右子樹(刪除最小值以後的)。
remove(data) {
this.root = removeNode(this.root,data)
}
removeNode(node, data) {
if (node == null) {
return null
}
if (data == node.data) {
//沒有節點,返回null
if (node.left == null && node.right == null) {
return null
}
//沒有左節點,右節點替換當前節點
if (node.left == null) {
return node.right
}
//沒有右節點,左節點替換當前節點
if (node.right == null) {
return node.left
}
//左右節點都存在
let minData = this.min(node.right)
node.data = minData
node.right = this.removeNode(node.right, minData)
return node
} else if (data < node.data) {
node.left = this.removeNode(node.left, data)
return node
} else if (data > node.data) {
node.right = this.removeNode(node.right, data)
return node
}
}
複製代碼
《數據結構與算法-javascript描述》 《學習javascript數據結構與算法》