树的基本概念
节点的一个属性是深度,节点的深度取决于它的祖先节点的数量。比如,节点 3 有 3 个祖先节点(5、7 和 11),它的深度为 3。
树的高度取决于所有节点深度的最大值。一棵树也可以被分解成层级。根节点在第 0 层,它的子节点在第 1 层,以此类推。上图中的树的高度为 3(最大高度已在图中表示——第 3 层)。
二叉搜索树(BST)
二叉搜索树(BST)是二叉树的一种,但是只允许你在左侧节点存储(比父节点)小的值,在右侧节点存储(比父节点)大的值
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 BST
* 二叉搜索树(BST)是二叉树的一种,但是只允许你在左侧节点存储(比父节点)小的值,在右侧节点存储(比父节点)大的值
*/
class BinarySearchTree {
constructor() {
this.root = null; // Node 类型的根节点
}
insert(key){
if(this.root === null){
this.root = new Node(key);
}else{
this.insertNode(this.root, key);
}
}
insertNode(node, key){
if(key < node.key){
if(node.left === null){
node.left = new Node(key);
}else{
this.insertNode(node.left, key);
}
}else{
if(node.right === null){
node.right = new Node(key);
}else{
this.insertNode(node.right, key);
}
}
}
// 中序遍历:一定是按照从小到大的顺序输出的 左-》中-》右
// 中序遍历是一种以上行顺序访问 BST 所有节点的遍历方式,也就是以从最小到最大的顺序访问所有节点。
inOrderTraverse(callback) {
this.inOrderTraverseNode(this.root, callback);
}
inOrderTraverseNode(node, callback){
if(node !== null){
this.inOrderTraverseNode(node.left, callback);
callback(node.key)
this.inOrderTraverseNode(node.right, callback);
}
}
// 先序遍历:中-》左-》右 可以确定根节点
// 先序遍历是以优先于后代节点的顺序访问每个节点的。先序遍历的一种应用是打印一个结构化的文档。
preOrderTraverse(callback) {
this.preOrderTraverseNode(this.root, callback);
}
preOrderTraverseNode(node, callback) {
if (node != null) {
callback(node.key);
this.preOrderTraverseNode(node.left, callback);
this.preOrderTraverseNode(node.right, callback);
}
}
// 后序遍历:左-》右-》中
// 后序遍历则是先访问节点的后代节点,再访问节点本身。后序遍历的一种应用是计算一个目录及其子目录中所有文件所占空间的大小
postOrderTraverse(callback) {
this.postOrderTraverseNode(this.root, callback);
}
postOrderTraverseNode(node, callback) {
if (node != null) {
this.postOrderTraverseNode(node.left, callback);
this.postOrderTraverseNode(node.right, callback);
callback(node.key);
}
}
// 最小值
min(){
return this.minNode(this.root);
}
minNode(node) {
let current = node;
while (current != null && current.left != null) {
current = current.left;
}
return current;
}
// 最大值
max(){
return this.maxNode(this.root);
}
maxNode(node) {
let current = node;
while (current != null && current.right != null) {
current = current.right;
}
return current;
}
// 搜索一个特定的值
search(key) {
return this.searchNode(this.root, key);
}
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;
}
}
// 移除一个节点
remove(key) {
this.root = this.removeNode(this.root, key);
}
removeNode(node, key) {
if (node == null) {
return false;
}
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 {
// 第一种情况:没有子节点,直接设置为null
if (node.left == null && node.right == null) {
node = null;
return node;
}
// 第二种情况:有一个子节点,就直接指向它子节点
if (node.left == null) {
node = node.right;
return node;
} else if (node.right == null) {
node = node.left;
return node;
}
// 第三种情况:有两个子节点,找到右侧最小的指向它,然后在删掉它
const aux = this.minNode(node.right);
node.key = aux.key;
node.right = this.removeNode(node.right, aux.key);
return node;
}
}
}
const tree = new BinarySearchTree();
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);
const printNode = (value) => console.log(value);
// 中序遍历
tree.inOrderTraverse(printNode);
// 先序遍历
tree.preOrderTraverse(printNode);
// 后序遍历
tree.postOrderTraverse(printNode);
console.log(tree.max(), tree.min());
console.log(tree.search(1) ? 'Key 1 found.' : 'Key 1 not found.');
console.log(tree.search(8) ? 'Key 8 found.' : 'Key 8 not found.');
先序遍历
先序遍历:中-》左-》右 可以确定根节点
先序遍历是以优先于后代节点的顺序访问每个节点的。先序遍历的一种应用是打印一个结构化的文档。
中序遍历
中序遍历:一定是按照从小到大的顺序输出的 左-》中-》右
中序遍历是一种以上行顺序访问 BST 所有节点的遍历方式,也就是以从最小到最大的顺序访问所有节点。
后序遍历
后序遍历:左-》右-》中
后序遍历则是先访问节点的后代节点,再访问节点本身。后序遍历的一种应用是计算一个目录及其子目录中所有文件所占空间的大小