JavaScript 二叉搜索树实现详解:从节点构造到增删查改
本文详细解析 JavaScript 中二叉搜索树(BST)的实现原理。内容涵盖节点类与树类的构造、基于递归的插入算法、三种遍历方式(前序、中序、后序)的栈执行机制,以及查找和删除操作的逻辑判断。通过具体代码示例,深入理解递归在树结构操作中的应用及时间复杂度优化。
二叉树与二叉搜索树基础
二叉树是一种基础的数据结构。
在二叉树中,每个节点最多只能拥有两个子节点。
二叉搜索树是二叉树的一种特殊形式。
二叉搜索树在普通二叉树的基础上增加了排序约束:插入值时,若新值小于当前节点,则尝试插入左子节点;若大于当前节点,则尝试插入右子节点。若目标子节点已存在,则继续按照上述规则向下比较,直至找到空位插入新节点。
二叉搜索树具备特定的性能特征。
得益于其有序结构,二叉搜索树在插入、删除和查找操作上的时间复杂度均为 O(h),其中 h 为树的高度。为了保持高效性能,树的高度应尽可能小,即左右子树应尽量保持平衡。
二叉搜索树的节点与树类构造
构建二叉搜索树需先定义节点结构。
根据二叉树特性,每个节点需包含左子节点引用、右子节点引用以及存储的值。因此,首先定义节点类如下:
class Node {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}
随后构建二叉搜索树的主类。
class Tree{
constructor(param = null) {
if (param) {
this.root = new Node(param);
} else {
this.root = null;
}
}
}
此处 this.root 属性指向当前树对象的根节点。
二叉搜索树的插入算法实现
接下来实现二叉搜索树的新增功能。
利用左子树节点值小于当前节点、右子树节点值大于当前节点的特性,可编写新增算法,具体实现如下:
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);{1}
} else {
this._insertNode(node.left, key);{2}
}
} else if (key > node.key) {
if (node.right === null) {
node.right = new Node(key);{3}
} else {
this._insertNode(node.right, key);{4}
}
}
}
上述代码首先比较新增 key 与当前节点 key 的大小。若较小,则递归遍历左子节点,直至找到为 null 的左子节点位置;若较大,则同理处理右子节点。代码中 {1}{2}{3}{4} 标记处之所以能直接修改 this.root 的值,是因为 JavaScript 中函数参数按值传递,但当传递非基本类型(如对象)时,传递的是内存地址的副本,因此对对象属性的修改会直接反映在原始对象上。
二叉搜索树的遍历机制与递归解析
二叉搜索树支持多种遍历方式。
主要包括先序遍历、中序遍历和后序遍历三种模式。
inOrderTraverse(callback) {
this._inOrderTraverse(this.root, callback);
}
_inOrderTraverse(node, callback) {
if (node) {
this._inOrderTraverse(node.left, callback);
callback(node.key);
this._inOrderTraverse(node.right, callback);
}
}
以上代码展示了中序遍历的实现逻辑。
理解递归机制至关重要。函数执行可抽象为栈数据结构,系统维护一个调用栈来存储执行环境。每次递归时,当前执行环境入栈并记录执行位置。
以示例代码为例,执行流程如下:从节点 11 开始,执行 {1} 入栈,进入节点 7 并执行 {1} 入栈,接着到节点 5 执行 {1} 入栈,再到节点 3 执行 {1} 入栈。此时发现节点 3 的左子节点为 null,开始出栈,弹出节点 3 的执行环境,执行 {2} 和 {3}。由于节点 3 的右子节点也为 null,{3} 的递归执行完毕。接着弹出节点 5,执行 {2} 和 {3};弹出节点 7,执行 {2} 和 {3}。当执行 {3} 时发现节点 7 有右子节点,则继续执行 {1} 进入节点 8,再执行 {1}。由于节点 8 无左子节点,{1} 执行完毕,接着执行 {2} 和 {3},以此类推完成遍历。
前序遍历与中序遍历的区别在于访问当前节点的时机,其执行顺序为 2 1 3(即先访问节点本身,再遍历左右子树)。
后序遍历同理,其执行顺序为 1 3 2(即先遍历左右子树,最后访问节点本身)。
可以看出,无论前序、中序还是后序遍历,核心逻辑都是先递归遍历左节点,左节点遍历完毕出栈后再遍历右节点。三者的唯一区别在于访问当前节点本身的时机不同。
二叉搜索树的查找与删除操作
二叉搜索树的查找操作相对简单。
根据左子节点值小于当前节点、右子节点值大于当前节点的原则,通过循环或递归判断即可实现查找。
search(value) {
if (this.root) {
if (value === this.root.key) {
return true;
} else {
return this._searchNode(value, this.root);
}
}
throw new Error('this.root 不存在');
}
_searchNode(value, node) {
if (!node) {
return false;
}
if (value === node.key) {
return true;
}
if (value > node.key) {
return this._searchNode(value, node.right);
} else if (value < node.key) {
return this._searchNode(value, node.left);
}
}
二叉搜索树的删除操作较为复杂,需根据不同情况分别处理。
具体判断逻辑如下:
首先判断目标节点是否有左子树。若无左子树,则直接将右子树的根节点替换被删除节点。
若存在左子树,则将右子树中的最小节点替换被删除节点,以维持树的有序性。
remove(key) {
this._removeNode(this.root, key);
}
_removeNode(node, value) {
if (!node) {
return null;
}
if (value > node.key) {
node.right = this._removeNode(node.right, value);
} else if (value < node.key) {
node.left = this._removeNode(node.left, value);
} else {
// 如果没有左子树,那么将右子树根节点作为替换节点
if (!node.left) {
return node.right;
// 如果存在左子树,那么取右子树最小节点作为替换节点
} else if (node.left) {
return this._minNode(node.right);
}
}
}
总结与反思。
通过实现二叉搜索树,重新深入理解了递归机制。此前对递归的理解仅停留在理论层面,此次实践加深了对递归执行过程的认识。
这让人联想到数学学习:理论公式易于记忆,但若仅看公式,掌握程度或许仅为满分中的 2 分。因为实际问题千变万化,只有将理论付诸实践,经过反复调试与验证,才能真正理解其内在奥秘。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
JS语法错误会阻止加载吗?区分SyntaxError与资源加载失败
2026-11-05 16:08:46
JavaScript常见SyntaxError语法错误排查与修复方法
2026-11-05 13:58:44
JS中slice、substring和substr截取字符串的区别
2026-11-05 13:43:47
JS 转 TS 手动迁移最佳实践:从配置 tsconfig 到消除 any 类型
2026-11-05 11:53:32
为什么JS数组push后得到的是数字而不是数组?
2026-11-05 09:59:18
