二叉查找樹(Binary Search Tree),(又:二叉搜索樹,二叉排序樹)它可以是一棵空樹,也可以是具有下列性質的二叉樹: 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值; 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值; 它的左、右子樹也分別為二叉排序樹。二叉搜索樹作為一種經典的數據結構,它既有鏈表的快速插入與刪除操作的特點,又有數組快速查找的優勢;所以應用十分廣泛,例如在文件系統和數據庫系統一般會采用這種數據結構進行高效率的排序與檢索操作
樹的定義
樹由一組以邊連接的節點組成。公司的組織結構圖就是一個樹的例子,參見下圖
組織結構圖是用來描述一個組織的架構。在圖 10-1 中,每個方框都是一個節點,連接方框
的線叫做邊。節點代表了該組織中的各個職位,邊描述了各職位間的關系。比如,CIO 直
接匯報給 CEO,那么兩者就用一條邊連接起來。開發經理向 CIO 匯報,也用一條邊連接
起來。銷售副總監和開發經理沒有直接的聯系,因此兩個節點間沒有用一條邊相連。
下圖的樹展示了更多有關樹的術語,在后續討論中將會提到。一棵樹最上面的節點稱為
根節點,如果一個節點下面連接多個節點,那么該節點稱為父節點,它下面的節點稱為子
節點。一個節點可以有 0 個、1 個或多個子節點。沒有任何子節點的節點稱為葉子節點。
沿着一組特定的邊,可以從一個節點走到另外一個與它不直接相連的節點。從一個節點到另一個節點的這一組邊稱為路徑,在圖中用虛線表示。以某種特定順序訪問樹中所有的節點稱為樹的遍歷。
樹可以分為幾個層次,根節點是第 0 層,它的子節點是第 1 層,子節點的子節點是第2層,以此類推。樹中任何一層的節點可以都看做是子樹的根,該子樹包含根節點的子節點,子節點的子節點等。我們定義樹的層數就是樹的深度。
這種自上而下的樹與人們的直覺相反。現實世界里,樹的根是在底下的。在計算機科學里,自上而下的樹則是個由來已久的習慣。事實上,計算機科學家高德納曾經試圖改變這個習慣,但沒幾個月他就發現,大多數計算機科學家都不願用自然的、自下而上的方式描述樹,於是,這件事也就只好不了了之。最后,每個節點都有一個與之相關的值,該值有時被稱為鍵。
二叉樹和二叉查找樹
叉排序樹的查找過程和次優二叉樹類似,通常采取二叉鏈表作為二叉排序樹的存儲結構。中序遍歷二叉排序樹可得到一個關鍵字的有序序列,一個無序序列可以通過構造一棵二叉排序樹變成一個有序序列,構造樹的過程即為對無序序列進行排序的過程。每次插入的新的結點都是二叉排序樹上新的葉子結點,在進行插入操作時,不必移動其它結點,只需改動某個結點的指針,由空變為非空即可。搜索,插入,刪除的復雜度等於樹高,O(log(n))
二叉樹每個節點的子節點不允許超過兩個。通過將子節點的個數限定為 2,可以寫出高效的程序在樹中插入、查找和刪除數據。
下圖展示了一棵二叉樹
當考慮某種特殊的二叉樹,比如二叉查找樹時,確定子節點非常重要。二叉查找樹是一種特殊的二叉樹,相對較小的值保存在左節點中,較大的值保存在右節點中。這一特性使得查找的效率很高,對於數值型和非數值型的數據,比如單詞和字符串,都是如此。
實現二叉查找樹
實現Node
二叉查找樹由節點組成,所以我們要定義的第一個對象就是 Node,該對象和鏈表類似。Node 類的定義如下:
class Node {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}
Node 對象既保存數據,也保存和其他節點的鏈接(left 和 right)。
實現BST
現在可以創建一個類,用來表示二叉查找樹(BST)。我們讓類只包含一個數據成員:一個表示二叉查找樹根節點的 Node 對象。該類的構造函數將根節點初始化為 null,以此創建一個空節點。BST 先要有一個 insert() 方法,用來向樹中加入新節點。這個方法有點復雜,需要着重講解。首先要創建一個 Node 對象,將數據傳入該對象保存。
其次檢查 BST 是否有根節點,如果沒有,那么這是棵新樹,該節點就是根節點,這個方法到此也就完成了;否則,進入下一步。
如果待插入節點不是根節點,那么就需要准備遍歷 BST,找到插入的適當位置。該過程類似於遍歷鏈表。用一個變量存儲當前節點,一層層地遍歷 BST。
進入 BST 以后,下一步就要決定將節點放在哪個地方。找到正確的插入點時,會跳出循環。查找正確插入點的算法如下。
- 設根節點為當前節點。
- 如果待插入節點保存的數據小於當前節點,則設新的當前節點為原節點的左節點;反
- 之,執行第 4 步。
- 如果當前節點的左節點為 null,就將新的節點插入這個位置,退出循環;反之,繼續
- 執行下一次循環。
- 設新的當前節點為原節點的右節點。
- 如果當前節點的右節點為 null,就將新的節點插入這個位置,退出循環;反之,繼續
- 執行下一次循環。
有了上面的算法,就可以開始實現 BST 類了。
class BinarySearchTree {
constructor() {
this.root = null;
}
insert(key) {
// 插入
const newNode = new Node(key);
if (this.root === null) {
this.root = newNode;
} else {
this.insertNode(this.root, newNode);
}
}
insertNode(node, newNode) {
if (newNode.key < node.key) {
if (node.left === null) {
node.left = newNode;
} else {
this.insertNode(node.left, newNode);
}
} else {
if (node.right === null) {
node.right = newNode;
} else {
this.insertNode(node.right, newNode);
}
}
}
}
有三種遍歷 BST 的方式:中序、先序和后序。中序遍歷按照節點上的鍵值,以升序訪問BST 上的所有節點。先序遍歷先訪問根節點,然后以同樣方式訪問左子樹和右子樹。后序遍歷先訪問葉子節點,從左子樹到右子樹,再到根節點
增加中序、先序和后序遍歷
class BinarySearchTree {
...
inOrderTraverse(callback) {
// 中序查找
this.inOrderTraverseNode(this.root, callback);
}
preOrderTraverse(callback) {
// 先序查找
this.preOrderTraverseNode(this.root, callback);
}
postOrderTraverse(callback) {
// 后序查找
this.postOrderTraverseNode(this.root, callback);
}
inOrderTraverseNode(node, callback) {
if (node !== null) {
this.inOrderTraverseNode(node.left, callback);
callback(node.key);
this.inOrderTraverseNode(node.right, callback);
}
}
preOrderTraverseNode(node, callback) {
if (node !== null) {
callback(node.key);
this.preOrderTraverseNode(node.left, callback);
this.preOrderTraverseNode(node.right, callback);
}
}
postOrderTraverseNode(node, callback) {
if (node !== null) {
this.postOrderTraverseNode(node.left, callback);
this.postOrderTraverseNode(node.right, callback);
callback(node.key);
}
}
}
在二叉查找樹上進行查找
對 BST 通常有下列三種類型的查找:
- 查找給定值;
- 查找最小值;
- 查找最大值。
class BinarySearchTree {
...
min() {
// 最小值
return this.minNode(this.root);
}
max() {
// 最大值
return this.maxNode(this.root);
}
search(key) {
// 查找
this.searchNode(this.root, key);
}
minNode(node) {
if (node) {
while (node && node.left !== null) {
node = node.left;
}
return node.key;
}
return null;
}
maxNode(node) {
if (node) {
while (node && node.right !== null) {
node = node.right;
}
return node.key;
}
return null;
}
searchNode(node, key) {
if (node === null) return false;
if (key < node.key) {
return this.searchNode(node.left, key);
} else if (key > node.key) {
return this.searchNode(node.right, key);
} else {
return true;
}
}
findMinNode(node) {
if (node) {
while (node && node.left !== null) {
node = node.left;
}
return node.key;
}
return null;
}
}
從二叉查找樹上刪除節點
class BinarySearchTree {
...
remove(key) {
//移除樹節點
this.removeNode(this.root, key);
}
removeNode(node, key) {
if (node === null) return null;
if (key < node.key) {
node.left = this.removeNode(node.left, key);
return node;
} else if (key > node.key) {
node.right = this.removeNode(node.right, key);
return node;
} else {
if (node.left === null && node.right === null) {
node = null;
return node;
} else if (node.left === null) {
node = node.right;
return node;
} else if (node.right === null) {
node = node.left;
return node;
}
const aux = this.findMinNode(node.right);
node.key = aux.key;
node.right = this.removeNode(node.right, aux.key);
return node;
}
}
}
完整代碼
class Node {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}
class BinarySearchTree {
constructor() {
this.root = null;
}
insert(key) {
// 插入
const newNode = new Node(key);
if (this.root === null) {
this.root = newNode;
} else {
this.insertNode(this.root, newNode);
}
console.log(this.root);
}
inOrderTraverse(callback) {
// 中序查找
this.inOrderTraverseNode(this.root, callback);
}
preOrderTraverse(callback) {
// 先序查找
this.preOrderTraverseNode(this.root, callback);
}
postOrderTraverse(callback) {
// 后序查找
this.postOrderTraverseNode(this.root, callback);
}
min() {
// 最小值
return this.minNode(this.root);
}
max() {
// 最大值
return this.maxNode(this.root);
}
search(key) {
// 查找
this.searchNode(this.root, key);
}
remove(key) {
//移除樹節點
this.removeNode(this.root, key);
}
insertNode(node, newNode) {
if (newNode.key < node.key) {
if (node.left === null) {
node.left = newNode;
} else {
this.insertNode(node.left, newNode);
}
} else {
if (node.right === null) {
node.right = newNode;
} else {
this.insertNode(node.right, newNode);
}
}
}
inOrderTraverseNode(node, callback) {
if (node !== null) {
this.inOrderTraverseNode(node.left, callback);
callback(node.key);
this.inOrderTraverseNode(node.right, callback);
}
}
preOrderTraverseNode(node, callback) {
if (node !== null) {
callback(node.key);
this.preOrderTraverseNode(node.left, callback);
this.preOrderTraverseNode(node.right, callback);
}
}
postOrderTraverseNode(node, callback) {
if (node !== null) {
this.postOrderTraverseNode(node.left, callback);
this.postOrderTraverseNode(node.right, callback);
callback(node.key);
}
}
minNode(node) {
if (node) {
while (node && node.left !== null) {
node = node.left;
}
return node.key;
}
return null;
}
maxNode(node) {
if (node) {
while (node && node.right !== null) {
node = node.right;
}
return node.key;
}
return null;
}
searchNode(node, key) {
if (node === null) return false;
if (key < node.key) {
return this.searchNode(node.left, key);
} else if (key > node.key) {
return this.searchNode(node.right, key);
} else {
return true;
}
}
removeNode(node, key) {
if (node === null) return null;
if (key < node.key) {
node.left = this.removeNode(node.left, key);
return node;
} else if (key > node.key) {
node.right = this.removeNode(node.right, key);
return node;
} else {
if (node.left === null && node.right === null) {
node = null;
return node;
} else if (node.left === null) {
node = node.right;
return node;
} else if (node.right === null) {
node = node.left;
return node;
}
const aux = this.findMinNode(node.right);
node.key = aux.key;
node.right = this.removeNode(node.right, aux.key);
return node;
}
}
findMinNode(node) {
if (node) {
while (node && node.left !== null) {
node = node.left;
}
return node.key;
}
return null;
}
}
參考書籍
數據結構與算法JavaScript描述