二叉搜尋樹的簡明實現(ES5 & ES6)
二叉樹 & 二叉搜尋樹
二叉樹(Binary Tree)是 n(n >= 0)個節點的有限集合,集合為空集時,叫作空二叉樹;不為空時,由根節點及左子樹、右子樹組成,左子樹、右子樹也都是二叉樹。
從這個描述,可以看出樹的結構與遞迴之間存在密切關係,這種密切關係在樹的遍歷時能夠得到充分體現。
二叉搜尋樹(Binary Search Tree),又叫二叉查詢樹;也稱為有序二叉樹(Ordered Binary Tree),排序二叉樹(Sorted Binary Tree)。
這是維基百科上歸納的一些二叉搜尋樹的性質:
- 若任意節點的左子樹不空,則左子樹上所有節點的值均小於它的根節點的值;
- 若任意節點的右子樹不空,則右子樹上所有節點的值均大於它的根節點的值;
- 任意節點的左、右子樹也分別為二叉查詢樹;
- 沒有鍵值相等的節點。
本次實現中有點不一樣的地方,右節點是大於或等於父節點的。不過對示例沒有影響,而且很容易改成只能大於父節點。
關於《學習JavaScript資料結構與演算法(第2版)》
當年看過這本書的第一版,最近打算複習一下資料結構與演算法,於是看了第二版。
這是難得的一本用 JavaScript 實現的資料結構與演算法的書,它的講解十分清晰,相對來說質量較高,但問題也有很多。應該說第一版的內容還是比較可靠的,第二版新增的內容可靠性就差了很多。總的來說,這是一本從非常淺顯的層面講解資料結構與演算法的書,想獲得比較全面的知識還是需要閱讀更專業的資料。
本文的 ES5 實現參考了這本書,因為我覺得它是比較工整的實現,用於學習和理解時,好過我看到的其他一些實現方式。在原書示例程式碼的基礎上,我做了一些小調整。本書第二版號稱擁抱 ES6,但我看過之後發現至少樹這一章沒有改為 ES6 的實現,於是自己寫了一遍,正好當作練習的機會。
另外,這本書中提到的樹的遍歷方式包括中序、先序、後序遍歷,這些都屬於深度優先遍歷。在本文的程式碼中,我補充了廣度優先遍歷以及按照層次遍歷二叉樹的實現。
Binary Search Tree - ES5
var BinarySearchTree = function() {
var Node = function(key) {
this.key = key;
this.left = null;
this.right = null;
};
var root = null;
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);
}
}
};
var inOrderTraverseNode = function(node, cb) {
if (node !== null) {
inOrderTraverseNode(node.left, cb);
cb(node.key);
inOrderTraverseNode(node.right, cb);
}
};
var preOrderTraverseNode = function(node, cb) {
if (node !== null) {
cb(node.key);
preOrderTraverseNode(node.left, cb);
preOrderTraverseNode(node.right, cb);
}
};
var postOrderTraverseNode = function(node, cb) {
if (node !== null) {
postOrderTraverseNode(node.left, cb);
postOrderTraverseNode
}
};
var levelOrderTraverseNode = function(node, cb) {
if (node === null) {
return null;
}
var list = [node];
while (list.length > 0) {
node = list.shift();
cb(node.key);
if (node.left) {
list.push(node.left);
}
if (node.right) {
list.push(node.right);
}
}
};
var separateByLevelFn = function(node, cb, separator) {
var list = [];
var END_FLAG = 'END_FLAG';
list.push(node);
list.push(END_FLAG);
separator = separator || '---*---';
while (list.length > 0) {
node = list.shift();
// 遇到結束訊號,表示已經遍歷完一層;若佇列中還有元素,說明它們是剛剛遍歷完的這一層的所有子元素。
if (node === END_FLAG && list.length > 0) {
list.push(END_FLAG);
cb(separator);
} else {
cb(node.key);
if (node.left) {
list.push(node.left)
}
if (node.right) {
list.push(node.right);
}
}
}
};
var minNode = function(node) {
if (node) {
while (node.left !== null) {
node = node.left;
}
return node.key;
}
return null;
};
var maxNode = function(node) {
if (node) {
while (node.right !== null) {
node = node.right;
}
return node.key;
}
return null;
};
var searchNode = function(node, val) {
if (node === null) {
return false;
}
if (val < node.key) {
return searchNode(node.left, val);
} else if (val > node.key) {
return searchNode(node.right, val);
} else {
return true;
}
};
var findMinNode = function(node) {
if (node) {
while (node.left !== null) {
node = node.left;
}
return node;
}
return null;
};
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 {
if (node.left === null && node.right === null) {
node = null;
return node;
}
if (node.left === null) {
node = node.right;
return node;
}
if (node.right === null) {
node = node.left;
return node;
}
var aux = findMinNode(node.right);
node.key = aux.key;
node.right = removeNode(node.right, aux.key);
return node;
}
};
this.insert = function(key) {
var newNode = new Node(key);
if (root === null) {
root = newNode;
} else {
insertNode(root, newNode);
}
};
// 中序遍歷是一種以上行順序訪問BST所有節點的遍歷方式,也就是以從最小到最大的順序訪
// 問所有節點。中序遍歷的一種應用就是對樹進行排序操作。
this.inOrderTraverse = function(cb) {
inOrderTraverseNode(root, cb);
};
// 先序遍歷是以優先於後代節點的順序訪問每個節點的。先序遍歷的一種應用是列印一個結構化的文件。
this.preOrderTraverse = function(cb) {
preOrderTraverseNode(root, cb);
};
// 後序遍歷則是先訪問節點的後代節點,再訪問節點本身。後序遍歷的一種應用是計算一個目
// 錄和它的子目錄中所有檔案所佔空間的大小。
this.postOrderTraverse = function(cb) {
postOrderTraverseNode(root, cb);
};
// Breadth-First-Search
// 可以用來解決尋路徑的問題。
this.levelOrderTraverse = function(cb) {
levelOrderTraverseNode(root, cb);
}
// Breadth-First-Search
// 區分層次
this.separateByLevel = function(cb) {
separateByLevelFn(root, cb);
}
this.min = function() {
return minNode(root);
};
this.max = function() {
return maxNode(root);
};
this.search = function(val) {
searchNode(root, val);
};
this.remove = function(key) {
root = removeNode(root, key);
};
};
/* ========== test case ========== */
var tree = new BinarySearchTree();
/**
*
* 11
* / \
* / \
* / \
* / \
* / \
* / \
* 7 15
* / \ / \
* / \ / \
* 5 9 13 20
* / \ / \ / \ / \
* 3 6 8 10 12 14 18 25
*
*/
tree.insert(11);
tree.insert(7);
tree.insert(15);
tree.insert(5);
tree.insert(3);
tree.insert(9);
tree.insert(8);
tree.insert(10);
tree.insert(13);
tree.insert(12);
tree.insert(14);
tree.insert(20);
tree.insert(18);
tree.insert(25);
tree.insert(6);
var printNode = function(val) {
console.log(val);
};
tree.inOrderTraverse(printNode);
console.log('\n')
tree.levelOrderTraverse(printNode);
console.log('\n')
tree.separateByLevel(printNode);
console.log('\n')
tree.remove(7)
tree.inOrderTraverse(printNode);
console.log('\n')
tree.preOrderTraverse(printNode);
console.log('\n')
tree.postOrderTraverse(printNode);
Binary Search Tree - ES6
class Node {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}
// insert(key):向樹中插入一個新的鍵。
// search(key):在樹中查詢一個鍵,如果節點存在,則返回true;如果不存在,則返回false。
// inOrderTraverse:通過中序遍歷方式遍歷所有節點。
// preOrderTraverse:通過先序遍歷方式遍歷所有節點。
// postOrderTraverse:通過後序遍歷方式遍歷所有節點。
// min:返回樹中最小的值/鍵。
// max:返回樹中最大的值/鍵。
// remove(key):從樹中移除某個鍵。
class BinarySearchTree {
constructor() {
this.root = null;
}
static insertNode(node, newNode) {
if (node.key > newNode.key) {
if (node.left === null) {
node.left = newNode;
} else {
BinarySearchTree.insertNode(node.left, newNode);
}
} else {
if (node.right === null) {
node.right = newNode;
} else {
BinarySearchTree.insertNode(node.right, newNode);
}
}
}
static searchNode(node, key) {
if (node === null) {
return false;
}
if (node.key === key) {
return true;
} else if (node.key > key) {
BinarySearchTree.searchNode(node.left, key);
} else if (node.key < key) {
BinarySearchTree.searchNode(node.right, key);
}
}
static inOrderTraverseNode(node, cb) {
if (node === null) {
return;
}
BinarySearchTree.inOrderTraverseNode(node.left, cb);
cb(node.key);
BinarySearchTree.inOrderTraverseNode(node.right, cb);
}
static preOrderTraverseNode(node, cb) {
if (node === null) {
return;
}
cb(node.key);
BinarySearchTree.preOrderTraverseNode(node.left, cb);
BinarySearchTree.preOrderTraverseNode(node.right, cb);
}
static postOrderTraverseNode(node, cb) {
if (node === null) {
return;
}
BinarySearchTree.postOrderTraverseNode(node.left, cb);
BinarySearchTree.postOrderTraverseNode(node.right, cb);
cb(node.key);
}
static levelOrderTraverseNode(node, cb) {
if (node === null) {
return null;
}
const list = [node];
while (list.length > 0) {
node = list.shift();
cb(node.key);
if (node.left) {
list.push(node.left);
}
if (node.right) {
list.push(node.right);
}
}
}
static separateByLevelFn(node, cb, separator = '---*---') {
const list = [];
const END_FLAG = 'END_FLAG';
list.push(node);
list.push(END_FLAG);
while (list.length > 0) {
node = list.shift();
// 遇到結束訊號,表示已經遍歷完一層;若佇列中還有元素,說明它們是剛剛遍歷完的這一層的所有子元素。
if (node === END_FLAG && list.length > 0) {
list.push(END_FLAG);
cb(separator);
} else {
cb(node.key);
if (node.left) {
list.push(node.left)
}
if (node.right) {
list.push(node.right);
}
}
}
}
static removeNode(node, key) {
if (node === null) {
return null;
}
if (node.key === key) {
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;
} else if (node.left && node.right) {
let rightMinNode = node.right;
while (rightMinNode.left !== null) {
rightMinNode = rightMinNode.left;
}
node.key = rightMinNode.key;
node.right = BinarySearchTree.removeNode(node.right, rightMinNode.key);
return node;
}
} else if (node.key > key) {
node.left = BinarySearchTree.removeNode(node.left, key);
return node;
} else if (node.key < key) {
node.right = BinarySearchTree.removeNode(node.right, key);
return node;
}
}
static printNode(val) {
console.log(val);
}
insert(key) {
const newNode = new Node(key);
if (this.root === null) {
this.root = newNode;
} else {
BinarySearchTree.insertNode(this.root, newNode);
}
}
search(key) {
return BinarySearchTree.searchNode(key);
}
// 中序遍歷是一種以上行順序訪問BST所有節點的遍歷方式,也就是以從最小到最大的順序訪
// 問所有節點。中序遍歷的一種應用就是對樹進行排序操作。
inOrderTraverse(cb = BinarySearchTree.printNode) {
BinarySearchTree.inOrderTraverseNode(this.root, cb);
}
// 先序遍歷是以優先於後代節點的順序訪問每個節點的。先序遍歷的一種應用是列印一個結構化的文件。
preOrderTraverse(cb = BinarySearchTree.printNode) {
BinarySearchTree.preOrderTraverseNode(this.root, cb);
}
// 後序遍歷則是先訪問節點的後代節點,再訪問節點本身。後序遍歷的一種應用是計算一個目
// 錄和它的子目錄中所有檔案所佔空間的大小。
postOrderTraverse(cb = BinarySearchTree.printNode) {
BinarySearchTree.postOrderTraverseNode(this.root, cb);
}
// Breadth-First-Search
// 可以用來解決尋路徑的問題。
levelOrderTraverse(cb = BinarySearchTree.printNode) {
BinarySearchTree.levelOrderTraverseNode(this.root, cb);
}
// Breadth-First-Search
// 區分層次
separateByLevel(cb = BinarySearchTree.printNode) {
BinarySearchTree.separateByLevelFn(this.root, cb);
}
min() {
let node = this.root;
if (node === null) {
return null;
}
while (node.left !== null) {
node = node.left;
}
return node.key;
}
max() {
let node = this.root;
if (node === null) {
return null;
}
while (node.right !== null) {
node = node.right;
}
return node.key();
}
remove(key) {
this.root = BinarySearchTree.removeNode(this.root, key);
}
}
/* ========== test case ========== */
const tree = new BinarySearchTree();
/**
*
* 11
* / \
* / \
* / \
* / \
* / \
* / \
* 7 15
* / \ / \
* / \ / \
* 5 9 13 20
* / \ / \ / \ / \
* 3 6 8 10 12 14 18 25
*
*/
tree.insert(11);
tree.insert(7);
tree.insert(15);
tree.insert(5);
tree.insert(3);
tree.insert(9);
tree.insert(8);
tree.insert(10);
tree.insert(13);
tree.insert(12);
tree.insert(14);
tree.insert(20);
tree.insert(18);
tree.insert(25);
tree.insert(6);
tree.inOrderTraverse();
console.log('\n')
tree.levelOrderTraverse();
console.log('\n')
tree.separateByLevel();
console.log('\n')
tree.remove(7)
tree.inOrderTraverse();
console.log('\n')
tree.preOrderTraverse();
console.log('\n')
tree.postOrderTraverse();
相關文章
- javascript實現二叉搜尋樹JavaScript
- 二叉搜尋樹的python實現Python
- js實現完全排序二叉樹、二叉搜尋樹JS排序二叉樹
- JavaScript 二叉搜尋樹以及實現翻轉二叉樹JavaScript二叉樹
- 資料結構-二叉搜尋樹的實現資料結構
- Java實現二叉搜尋樹的插入、刪除Java
- 二叉樹的插入和搜尋–python實現二叉樹Python
- 如何在 Java 中實現二叉搜尋樹Java
- 二叉搜尋樹
- 二叉搜尋樹(Binary Search Tree)(Java實現)Java
- 資料結構之二叉搜尋樹—Java實現資料結構Java
- 二叉搜尋樹的操作集
- 二叉搜尋樹的結構
- Day20 | 654.最大二叉樹 、 617.合併二叉樹 、 700.二叉搜尋樹中的搜尋 98.驗證二叉搜尋樹二叉樹
- 實現二叉搜尋樹的新增,查詢和刪除(JAVA)Java
- Python資料結構——二叉搜尋樹的實現(上)Python資料結構
- 二叉搜尋樹和二叉樹的最近公共祖先二叉樹
- 資料結構高階--二叉搜尋樹(原理+實現)資料結構
- 二叉搜尋樹演算法詳解與Java實現演算法Java
- 96. 不同的二叉搜尋樹
- 資料結構中的樹(二叉樹、二叉搜尋樹、AVL樹)資料結構二叉樹
- 如何在Java中實現二叉搜尋樹( binary search tree)?Java
- 有序表和搜尋二叉樹二叉樹
- Leetcode 700. 二叉搜尋樹中的搜尋(DAY 2)LeetCode
- 資料結構和演算法-Go實現二叉搜尋樹資料結構演算法Go
- 【javascript實現】幾道題目帶你學習二叉搜尋樹JavaScript
- Elasticsearch 實現簡單搜尋Elasticsearch
- 演算法篇 - 二叉搜尋樹演算法
- 資料結構-二叉搜尋樹資料結構
- 【資料結構】二叉搜尋樹!!!資料結構
- 二叉搜尋樹程式碼例項
- leetcode 700. 二叉搜尋樹中的搜尋 思考分析LeetCode
- 程式碼隨想錄day18 || 530 二叉搜尋樹最小差,501 二叉搜尋樹眾數,236 二叉搜尋樹最近公共祖先
- 6-12 二叉搜尋樹的操作集
- [Python手撕]不同的二叉搜尋樹Python
- 程式碼隨想錄 第20天 20的總結沒看 | 654.最大二叉樹 ● 617.合併二叉樹 ● 700.二叉搜尋樹中的搜尋 ● 98.驗證二叉搜尋樹二叉樹
- 滿二叉樹、完全二叉樹、平衡二叉樹、二叉搜尋樹(二叉查詢樹)和最優二叉樹二叉樹
- 資料結構之「二叉搜尋樹」資料結構