起手詩
從今天起,做一個牛X的人,早起,健身,修煉演算法
從今天起,關心程式碼質量,我有一個夢想,朝九晚五,年薪百萬
從今天起,和每一個親人通訊,告訴他們我的決心
那成功的天使告訴我的
我想告訴每一個人
給每一個檔案、每一個變數取一個溫暖的名字
陌生人,我也為你祝福
願你有一個燦爛的前程
願你的頭髮不再減少
願你明天仍是公司棟樑
而我,只願朝九晚五,年薪百萬
前言
書接上文從今天起,每天詳解一道演算法題,今天實現一個二叉查詢樹。
基本概念
二叉查詢樹是二叉樹的一種特殊情況,二叉查詢樹的左邊節點的值總是比右邊的小,先看下定義:
二叉查詢樹(Binary Search Tree),(又:二叉搜尋樹,二叉排序樹)它或者是一棵空樹,或者是具有下列性質的二叉樹: 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值; 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值; 它的左、右子樹也分別為二叉查詢樹。
節點:包含一個資料元素及若干指向子樹的分支;
根節點:第一層的節點(圖中的8);
葉子節點:結點的子樹的根稱為該結點的葉子節點,這是一個相對概念;
結點層:根結點的層定義為1;根的孩子為第二層結點,依此類推;
樹的深度:樹中最大的結點層;
結點的度:結點子樹的個數(二叉樹為2);
樹的度:樹中最大的結點度(二叉樹為2);
程式碼實現
實現二叉樹,先要實現上面最重要的一個概念--節點:
它有三個屬性,值、左節點的指標、右節點的指標:
class Node {
constructor(data, left, right) {
this.value = data
this.left = left
this.right = right
}
show(){
return this.data
}
}
複製程式碼
下面我們實現二叉查詢樹的類:
我們可以想向到它應該有一個根節點,還應該有一些操作該類的方法,我會分別解釋,先看程式碼(為了避免程式碼太長,我把各個方法分開寫了,他們應該都在BST類裡的):
insert方法:
該方法負責向該二叉樹中插入一條資料,實現的思路是:
1、通過Node類建立包含該條資料的一個節點;
2、判斷如果該例項的根節點root為null,則把這個節點設定為根節點;
3、如果根節點不為null,則建立一個指向當前節點的指標(第一次指向根節點),然後比較data是否大於當前節點的data;
4、如果小於當前節點的data,則說明這個節點應該往左邊放,否則說明應該往右邊放;
5、通過迴圈比較,最終當前節點會找到null,這時候把新節點插入到這裡(也就是讓當parentNode的子節點指向新節點);
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)的時機。也就是一個在最先,一個在最後,一個在中間。
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方法查詢制定節點的最小值
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
}
複製程式碼
刪除給定值的節點
刪除方法相對較複雜,思路為:
1、定義remove方法,方法中把根節點更改為一個遞迴方法removeNode的返回值
2、removeNode是一個被刪除了制定節點的新樹,它的實現需要考慮以下幾種情況:
a:如果輸入節點為null,則返回null
b:如果data與node的data相等,說明這個節點就是需要被刪除的節點(這種情況又分為幾種情況,下面單獨說)
c:如果data比node的data小,則說明需要刪除的節點在node的左邊,那麼就需要遞迴它左邊這個節點,直到找到相等的或者null
d:如果data比node的data大,思路同c
接下來說b(命中相等節點)的情況:
1、如果這個節點沒有子節點,那麼直接刪除即可
2、如果這個節點只有一個節點,那麼它的這個子節點應該取代它的位置(程式碼中第二、三行註釋下的程式碼)
3、如果這個節點有左右兩個節點,那麼我們找到它的右子樹的最小值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資料結構與演算法》