树
/*
根节点: 位于树顶部的节点叫作根节点(11)。它没有父节点。树中的每个元素都叫作节点,节点分为内部节点和外部节点。
内部节点: 至少有一个子节点的节点称为内部节点(7、5、9、15、13 和 20 是内部节点)。
外部节点或叶节点: 没有子元素的节点称为外部节点或叶节点(3、6、8、10、12、14、18 和 25 是叶节点)
*/
/*
二叉树和二叉搜索树:
二叉树中的节点最多只能有两个子节点:一个是左侧子节点,另一个是右侧子节点。这个定义有助于我们写出更高效地在树中插入、查找和删除节点的算法。二叉树在计算机科学中的应用
非常广泛;
二叉搜索树(BST)是二叉树的一种,但是只允许你在左侧节点存储(比父节点)小的值,
在右侧节点存储(比父节点)大的值。上一节的图中就展现了一棵二叉搜索树。
*/
//创建 Node 类来表示二叉搜索树中的每个节点
class Node {
constructor(key) {
this.key = key; // 节点值
this.left = null; // 左侧子节点引用
this.right = null; // 右侧子节点引用
}
}
/*
为了保证代码优雅,我们可以声
明一个 Compare 常量来表示每个值
*/
const Compare = {
LESS_THAN: -1,
BIGGER_THAN: 1
}
// 用来比较元素的函数
/*
注意在这里,由于键可能是复杂的对象而不是数,我们使用
传入二叉搜索树构造函数的 compareFn 函数来比较值
*/
function defaultCompare(a, b) {
if(a === b) {
return 0;
}
return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN;
}
/*
insert(key):向树中插入一个新的键。
search(key):在树中查找一个键。如果节点存在,则返回 true;如果不存在,则返回
false。
inOrderTraverse():通过中序遍历方式遍历所有节点。
preOrderTraverse():通过先序遍历方式遍历所有节点。
postOrderTraverse():通过后序遍历方式遍历所有节点。
min():返回树中最小的值/键。
max():返回树中最大的值/键。
remove(key):从树中移除某个键
*/
class BinarySearchTree {
constructor(compareFn = defaultCompare) {
this.compareFn = compareFn; // 用来比较节点值
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 (this.compareFn(key, node.key) === Compare.LESS_THAN) {
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(tihs.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 currrent = 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 (this.compareFn(key, node.key) === Compare.LESS_THAN) {
return this.searchNode(node.left, key);
} else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
return this.searchNode(node.right, key);
} else {
// 否则就说明要找的键和当前节点的键相等,返回 true 来表示找到了这个键
return true;
}
}
// 移除一个节点(要实现的最复杂的方法)
remove(key) {
this.root = this.removeNode(this.root, key);
}
removeNode(node, key){
if (node == null) {
return null;
}
//我们需要在树中找到要移除的键
if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
node.left = this.removeNode(node.left, key);
return node;
} else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
node.right = this.removeNode(node.right, key);
return node;
} else {
// 如果我们找到了要找的键(键和 node.key 相等),就需要处理三种不同的情况。
// 第一种情况--移除一个叶节点
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;
}
}
}
let tree = new BinarySearchTree();
tree.insert(11);
tree.insert(10);
tree.insert(15);
tree.insert(20);
tree.insert(8);
// 测试中序遍历
const printNode = (value) => console.log(value);
tree.inOrderTraverse(printNode);
// 搜索一个特定的值-返回true
let isSearch = tree.search(8);
console.log('是否找到了制定元素8',isSearch);
树结构
中序遍历
先序遍历
后序遍历
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?