目录
一、引入二叉树的结构封装
function BinarySearchTree(){
// 新结点创建的构造函数
function Node(key){
this.key = key;
this.lchild = null;
this.rchild = null;
}
// 保存根的属性
this.root = null;
// 创建关于二叉搜索树的相关操作方法
}
二、插入insert(key)
思路:
创建insert方法
1、创建新结点
2、判断是否为空树
2.1如果是空树,则让根的属性指向新结点
2.2如果不是非空树,则寻找合适的位置插入
再创建一个用于内部使用的方法insertNode()
1、比较结点与新结点的值大小
1.1如果结点的值比新结点的值小
比较结点的右子树为空时插入,不为空时继续往右比较
1.2 如果结点的值比新结点的值大
比较结点的左子树为空时插入,不为空时继续往左比较
对于以下这棵树,想要插入20这个结点,执行以下步骤:
代码实现:
// 插入元素,给用户使用
BinarySearchTree.prototype.insert = function(key){
// 1、创建新结点
var newNode = new Node(key);
// 2、判断是否为空树
// 2.1如果是空树,则让根的属性指向新结点
if (this.root == null){
this.root = newNode;
}else{// 2.2如果不是非空树,则寻找合适的位置插入
this.insertNode(this.root, newNode);
}
}
// 用于内部使用
BinarySearchTree.prototype.insertNode = function(node, newNode){
// 1、比较结点与新结点的值大小
// 1.1如果结点的值比新结点的值小
if (node.key < newNode.key) {
// 比较结点的右子树为空时插入,不为空时继续往下比较
if (node.rchild == null) {
node.rchild = newNode;
}else{
this.insetNode(node.rchild, newNode);
}
}else{ // 1.2 如果结点的值比新结点的值大
// 比较结点的左子树为空时插入,不为空时继续往下比较
if (node.lchild == null) {
node.lchild = newNode;
}else{
this.insertNode(node.lchild, newNode);
}
}
}
代码测试
// 测试
// 1、创建BinarySearchTree
var bst = new BinarySearchTree();
// 2、插入数据
bst.insert(18);
bst.insert(4);
bst.insert(2);
bst.insert(7);
bst.insert(30);
bst.insert(24);
bst.insert(20);
// 3、输出结构
console.log(bst);
——》
三、二叉树的遍历方式
二叉搜索树的遍历即是二叉树的遍历,想要遍历每一个结点,就必须按照某种特定的顺序进行遍历。
二叉树常见的方法有三种:
- 先序遍历
- 中序遍历
- 后序遍历
还有一种不常见的遍历是层次遍历,就是按照树的每一层从上到下从左开始往右遍历。
1、先序遍历
操作定义:
若二叉树为空,则空操作;否则
(1)访问根结点;
(2)先序遍历左子树;
(3)先序遍历右子树。
代码实现:
// 先序遍历
BinarySearchTree.prototype.preOrderTraversal = function(fun){
// 让preOrderTraversalNode去实现递归操作
this.preOrderTraversalNode(this.root, fun)
}
// 内部使用
BinarySearchTree.prototype.preOrderTraversalNode = function(node, fun){
if (node) {
// 1、打印当前结点
fun(node.key);
// 2、遍历所有的左子树;
this.preOrderTraversalNode(node.lchild, fun);
// 3、遍历所有的右子树。
this.preOrderTraversalNode(node.rchild, fun);
}
}
测试代码
// 测试
// 1、创建BinarySearchTree
var bst = new BinarySearchTree();
// 2、插入数据
bst.insert(18);
bst.insert(4);
bst.insert(2);
bst.insert(7);
bst.insert(30);
bst.insert(24);
bst.insert(20);
// 3、先序遍历
var result = '';
bst.preOrderTraversal(function (key){
result += key + ' ';
})
console.log(result); //18 4 2 7 30 24 20
2、中序遍历
操作定义:
若二叉树为空,则空操作;否则
(1)中序遍历左子树;
(2)访问根结点;
(3)中序遍历右子树。
代码实现:
// 中序遍历
BinarySearchTree.prototype.midOrderTraversal = function(fun){
// 让midOrderTraversalNode去实现递归操作
this.midOrderTraversal(this.root, fun)
}
// 内部使用
BinarySearchTree.prototype.midOrderTraversalNode = function(node, fun){
if (node) {
// 1、遍历所有的左子树;
this.midOrderTraversalNode(node.lchild, fun);
// 2、打印当前结点
fun(node.key);
// 3、遍历所有的右子树。
this.midOrderTraversalNode(node.rchild, fun);
}
}
测试代码
// 测试
// 1、创建BinarySearchTree
var bst = new BinarySearchTree();
// 2、插入数据
bst.insert(18);
bst.insert(4);
bst.insert(2);
bst.insert(7);
bst.insert(30);
bst.insert(24);
bst.insert(20);
// 3、中序遍历
var result = '';
bst.midOrderTraversal(function (key){
result += key + ' ';
})
console.log(result); //2 4 7 18 20 24 30
3、后序遍历
操作定义:
若二叉树为空,则空操作;否则
(1)后序遍历左子树;
(2)后序遍历右子树。
(3)访问根结点;
代码实现:
// 后序遍历
BinarySearchTree.prototype.postOrderTraversal = function(fun){
// 让postOrderTraversalNode去实现递归操作
this.postOrderTraversalNode(this.root, fun)
}
// 内部使用
BinarySearchTree.prototype.postOrderTraversalNode = function(node, fun){
if (node) {
// 1、遍历所有的左子树;
this.postOrderTraversalNode(node.lchild, fun);
// 2、遍历所有的右子树。
this.postOrderTraversalNode(node.rchild, fun);
// 3、打印当前结点
fun(node.key);
}
}
测试代码
// 测试
// 1、创建BinarySearchTree
var bst = new BinarySearchTree();
// 2、插入数据
bst.insert(18);
bst.insert(4);
bst.insert(2);
bst.insert(7);
bst.insert(30);
bst.insert(24);
bst.insert(20);
// 3、先序遍历
var result1 = '';
bst.preOrderTraversal(function (key){
result1 += key + ' ';
})
console.log(result1); //18 4 2 7 30 24 20
// 4、中序遍历
var result2 = '';
bst.midOrderTraversal(function (key){
result2 += key + ' ';
})
console.log(result2); //2 4 7 18 20 24 30
// 5、后序遍历
var result3 = '';
bst.postOrderTraversal(function (key){
result3 += key + ' ';
})
console.log(result3); //2 7 4 20 24 30 18
四、二叉搜索树查找最值
如果树为空,则无最值,
如果树不为空
- 从根结点开始一直向右找到最后即为最大值。
- 从根结点开始一直向左找到最后即为最小值。
代码实现:
// 最大值
BinarySearchTree.prototype.max = function(){
var node = this.root;
var key = null;
while(node){
key = node.key;
node = node.rchild;
}
return key;
}
// 最小值
BinarySearchTree.prototype.min = function(){
var node = this.root;
var key = null;
while(node){
key = node.key;
node = node.lchild;
}
return key;
}
五、二叉搜索树查找某个Key
查找key与插入一项时的查找很相似,这里使用循环来编写代码。
// 查找某个key
BinarySearchTree.prototype.searchKey = function(key){
var node = this.root;
while(node != null){
if (node.key < key) {
node = node.rchild;
}else if (node.key > key) {
node = node.lchild;
}else{
return true;
}
}
return false;
}
六、二叉搜索树删除结点
之前提起一点,要进行二叉搜索树的结点删除操作是非常复杂的,因为情况比较多,那我们就一点一点来进行分析,操作是不难理解的,最主要的就是考虑周全。针对二叉树的结构,我们以删除结点相连的子结点个数分三种情况
情况一:删除的结点无子结点(即是叶子结点)。
情况二:删除的结点有一个子结点。
情况三:删除的结点有两个子结点。
具体步骤:
1、寻找要删除的结点。
- 1.1 定义变量,保存信息。
current 保存根结点;
parent 记录删除结点的父节点;
isLeftChild 判断删除的结点是否是该父结点的左子结点。
var current = this.root; // 保存根结点 var parent = null; // 记录删除结点的父节点 var isLeftChild = true; //判断删除的结点是否是该父结点的左子结点
- 1.2 查找要删除的结点,已经找到最后一个结点还没有找到就退出。
// 1.2 查找要删除的结点 while(current.key != key){ parent = current; if (key < current.key) { isLeftChild = true; current = current.lchild; }else{ isLeftChild = false; current = current.rchild; } // 已经找到最后一个结点还没有找到就退出 if (current == null) return false; }
2、查找到结点,根据对应的情况删除结点。
- 2.1 情况一:删除的结点是叶子结点(即没有子结点)。
① 如果该树只有一个根结点,将之移除即可。
② 如果该树有多个结点,又是针对叶子结点进行删除的话,就可以根据要删除的结点的父结点进行指针域变空即可。
// 2.1删除的结点是叶子结点(即没有子结点) if (current.lchild == null && current.rchild == null) { if (current == this.root) { this.root = null; } else if (isLeftChild) { parent.lchild = null; } else { parent.rchild = null; } }
- 2.2 情况二:删除的结点是有一个子结点。
① 删除的结点是根结点,把 this.root 指向一个要删除的结点的子结点。
② 查找到删除的结点不是根结点。有四种具体的情况。
else if (current.rchild == null) { if (current == this.root) { this.root = current.lchild; } else if (isLeftChild) { parent.lchild = current.lchild; } else { parent.rchild = current.lchild; } } else if (current.lchild == null) { if (current == this.root) { this.root = current.rchild; } else if (isLeftChild) { parent.lchild = current.rchild; } else { parent.rchild = current.rchild; } }
- 2.3 情况三:删除的结点是有两个子结点。
如果我们删除的结点是有两个子结点的,有可能子结点中还有结点,那我们就需要找一个结点来替换删除的结点。
我们可以找比删除结点大一点点的,也可以找比删除结点小一点点的,也就是最接近删除结点的替代结点。
那就必定:
比删除结点小一点点的,一定是删除结点左子树的最大值(前驱)。
比删除结点大一点点的,一定是删除结点右子树的最小值(后继)。
这里使用后继查找替换结点的方式
① 删除的结点是根结点,把 this.root 指向要替换的结点,被替换的结点的父结点与右结点相连。
② 查找到删除的结点不是根结点。
// 2.3删除的结点两个子结点 else { // 1、寻找后继 var replaceNode = this.getReplaceNode(current); // 2、判断是否是根结点 if (current == this.root) { this.root = replaceNode; } else if (isLeftChild) { parent.lchild = replaceNode; } else { parent.rchild = replaceNode; } // 3、将替代的结点的左子树 = 删除结点的左子树 replaceNode.lchild = current.lchild; }
// 找后继的方法 BinarySearchTree.prototype.getReplaceNode = function (delNode) { // 1、定义变量,来保存找到的后继 var replaceNode = delNode; // 替代的结点 var previous = delNode.rchild; //删除结点的右结点 var replaceParent = delNode; // 2、循环查找 while(previous != null){ replaceParent = replaceNode; replaceNode = previous; previous = previous.lchild; } // 3、判断找到的替换结点(后继)是否就是delNode的rchild结点,如果是则填补结点 if (replaceNode != delNode.rchild) { // 后继结点必定是小于两个子结点的结点,将后继结点的父结点的左结点指向替代结点的右结点,连接操作。 replaceParent.lchild = replaceNode.rchild; // 替换 replaceNode.rchild = delNode.rchild; } return replaceNode; }
代码测试:
// 测试
// 1、创建BinarySearchTree
var bst = new BinarySearchTree();
// 2、插入数据
bst.insert(18);
bst.insert(4);
bst.insert(2);
bst.insert(7);
bst.insert(30);
bst.insert(24);
bst.insert(20);
// 3、先序遍历
var result1 = '';
bst.preOrderTraversal(function (key){
result1 += key + ' ';
})
console.log('先序遍历:' + result1); //18 4 2 7 30 24 20
删除18
// 6、删除操作
// 删除根结点18
bst.remove(18);
// 先序遍历
var result4 = '';
bst.preOrderTraversal(function (key){
result4 += key + ' ';
})
console.log('先序遍历为18 4 2 7 30 24 20 ,删除结点18后得到先序遍历结果:' + result4);
//先序遍历为18 4 2 7 30 24 20 ,删除结点18后得到先序遍历结果:20 4 2 7 30 24
// 插入数据
bst.insert(26);
bst.insert(21);
bst.insert(25);
bst.insert(27);
// 先序遍历
var result5 = '';
bst.preOrderTraversal(function (key){
result5 += key + ' ';
})
console.log('在先序为20 4 2 7 30 24 的二叉树搜索树中插入26、21、25、27,得到先序遍历结果:' + result5);
//在先序为20 4 2 7 30 24 的二叉树搜索树中插入26、27、28、29,得到先序遍历结果:20 4 2 7 30 24 21 26 25 27
删除24
// 删除结点24
bst.remove(24);
// 先序遍历
var result6 = '';
bst.preOrderTraversal(function (key){
result6 += key + ' ';
})
console.log('先序遍历为20 4 2 7 30 24 21 26 25 27 ,删除结点24后得到先序遍历结果:' + result6);
//先序遍历为20 4 2 7 30 24 21 26 25 27 ,删除结点24后得到先序遍历结果:20 4 2 7 30 25 21 26 27
完整打印
七、完整代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>二叉搜索树的相关方法</title>
</head>
<body>
<script type="text/javascript">
function BinarySearchTree(){
// 新结点创建的构造函数
function Node(key){
this.key = key;
this.lchild = null;
this.rchild = null;
}
// 保存根的属性
this.root = null;
// 创建关于二叉搜索树的相关操作方法
// 插入元素,给用户使用
BinarySearchTree.prototype.insert = function(key){
// 1、创建新结点
var newNode = new Node(key);
// 2、判断是否为空树
// 2.1如果是空树,则让根的属性指向新结点
if (this.root == null){
this.root = newNode;
}else{// 2.2如果不是非空树,则寻找合适的位置插入
this.insertNode(this.root, newNode);
}
}
// 用于内部使用
BinarySearchTree.prototype.insertNode = function(node, newNode){
// 1、比较结点与新结点的值大小
// 1.1如果结点的值比新结点的值小
if (node.key < newNode.key) {
// 比较结点的右子树为空时插入,不为空时继续往下比较
if (node.rchild == null) {
node.rchild = newNode;
}else{
this.insertNode(node.rchild, newNode);
}
}else{ // 1.2 如果结点的值比新结点的值大
// 比较结点的左子树为空时插入,不为空时继续往下比较
if (node.lchild == null) {
node.lchild = newNode;
}else{
this.insertNode(node.lchild, newNode);
}
}
}
// 先序遍历
BinarySearchTree.prototype.preOrderTraversal = function(fun){
// 让preOrderTraversalNode去实现递归操作
this.preOrderTraversalNode(this.root, fun)
}
// 内部使用
BinarySearchTree.prototype.preOrderTraversalNode = function(node, fun){
if (node) {
// 1、打印当前结点
fun(node.key);
// 2、遍历所有的左子树;
this.preOrderTraversalNode(node.lchild, fun);
// 3、遍历所有的右子树。
this.preOrderTraversalNode(node.rchild, fun);
}
}
// 中序遍历
BinarySearchTree.prototype.midOrderTraversal = function(fun){
// 让midOrderTraversalNode去实现递归操作
this.midOrderTraversalNode(this.root, fun)
}
// 内部使用
BinarySearchTree.prototype.midOrderTraversalNode = function(node, fun){
if (node) {
// 1、遍历所有的左子树;
this.midOrderTraversalNode(node.lchild, fun);
// 2、打印当前结点
fun(node.key);
// 3、遍历所有的右子树。
this.midOrderTraversalNode(node.rchild, fun);
}
}
// 后序遍历
BinarySearchTree.prototype.postOrderTraversal = function(fun){
// 让postOrderTraversalNode去实现递归操作
this.postOrderTraversalNode(this.root, fun)
}
// 内部使用
BinarySearchTree.prototype.postOrderTraversalNode = function(node, fun){
if (node) {
// 1、遍历所有的左子树;
this.postOrderTraversalNode(node.lchild, fun);
// 2、遍历所有的右子树。
this.postOrderTraversalNode(node.rchild, fun);
// 3、打印当前结点
fun(node.key);
}
}
// 最大值
BinarySearchTree.prototype.max = function(){
var node = this.root;
var key = null;
while(node){
key = node.key;
node = node.rchild;
}
return key;
}
// 最小值
BinarySearchTree.prototype.min = function(){
var node = this.root;
var key = null;
while(node){
key = node.key;
node = node.lchild;
}
return key;
}
// 查找某个key
BinarySearchTree.prototype.searchKey = function(key){
var node = this.root;
while(node != null){
if (node.key < key) {
node = node.rchild;
}else if (node.key > key) {
node = node.lchild;
}else{
return true;
}
}
return false;
}
// 删除某个结点
BinarySearchTree.prototype.remove = function(key){
// 1、寻找要删除的结点
// 1.1 定义变量,保存信息
var current = this.root; // 保存根结点
var parent = null; // 记录删除结点的父节点
var isLeftChild = true; //判断删除的结点是否是该父结点的左子结点
// 1.2 查找要删除的结点
while(current.key != key){
parent = current;
if (key < current.key) {
isLeftChild = true;
current = current.lchild;
}else{
isLeftChild = false;
current = current.rchild;
}
// 已经找到最后一个结点还没有找到就退出
if (current == null) return false;
}
// 2、根据对应的情况删除结点
// 2.1删除的结点是叶子结点(即没有子结点)
if (current.lchild == null && current.rchild == null) {
if (current == this.root) {
this.root = null;
} else if (isLeftChild) {
parent.lchild = null;
} else {
parent.rchild = null;
}
}
// 2.2删除的结点有一个子结点
else if (current.rchild == null) {
if (current == this.root) {
this.root = current.lchild;
} else if (isLeftChild) {
parent.lchild = current.lchild;
} else {
parent.rchild = current.lchild;
}
} else if (current.lchild == null) {
if (current == this.root) {
this.root = current.rchild;
} else if (isLeftChild) {
parent.lchild = current.rchild;
} else {
parent.rchild = current.rchild;
}
}
// 2.3删除的结点两个子结点
else {
// 1、寻找后继
var replaceNode = this.getReplaceNode(current);
// 2、判断是否是根结点
if (current == this.root) {
this.root = replaceNode;
} else if (isLeftChild) {
parent.lchild = replaceNode;
} else {
parent.rchild = replaceNode;
}
// 3、将替代的结点的左子树 = 删除结点的左子树
replaceNode.lchild = current.lchild;
}
}
// 找后继的方法
BinarySearchTree.prototype.getReplaceNode = function (delNode) {
// 1、定义变量,来保存找到的后继
var replaceNode = delNode; // 替代的结点
var previous = delNode.rchild; //删除结点的右结点
var replaceParent = delNode;
// 2、循环查找
while(previous != null){
replaceParent = replaceNode;
replaceNode = previous;
previous = previous.lchild;
}
// 3、判断找到的替换结点(后继)是否就是delNode的rchild结点,如果是则填补结点
if (replaceNode != delNode.rchild) {
// 后继结点必定是小于两个子结点的结点,将后继结点的父结点的左结点指向替代结点的右结点,连接操作。
replaceParent.lchild = replaceNode.rchild;
// 替换
replaceNode.rchild = delNode.rchild;
}
return replaceNode;
}
}
// 测试
// 1、创建BinarySearchTree
var bst = new BinarySearchTree();
// 2、插入数据
bst.insert(18);
bst.insert(4);
bst.insert(2);
bst.insert(7);
bst.insert(30);
bst.insert(24);
bst.insert(20);
// 3、先序遍历
var result1 = '';
bst.preOrderTraversal(function (key){
result1 += key + ' ';
})
console.log('先序遍历:' + result1); //18 4 2 7 30 24 20
// 4、中序遍历
var result2 = '';
bst.midOrderTraversal(function (key){
result2 += key + ' ';
})
console.log('中序遍历:' + result2); //2 4 7 18 20 24 30
// 5、后序遍历
var result3 = '';
bst.postOrderTraversal(function (key){
result3 += key + ' ';
})
console.log('后序遍历:' + result3); //2 7 4 20 24 30 18
console.log('最大值是' + bst.max());
console.log('最小值是' + bst.min());
console.log('是否存在24?' + bst.searchKey(24)); //true
console.log('是否存在30?' + bst.searchKey(30)); //true
console.log('是否存在5?' + bst.searchKey(5)); //false
console.log('是否存在8?' + bst.searchKey(8)); //false
console.log('是否存在500?' + bst.searchKey(500)); //false
// 6、删除操作
// 删除根结点18
bst.remove(18);
// 先序遍历
var result4 = '';
bst.preOrderTraversal(function (key){
result4 += key + ' ';
})
console.log('先序遍历为18 4 2 7 30 24 20 ,删除结点18后得到先序遍历结果:' + result4);
//先序遍历为18 4 2 7 30 24 20 ,删除结点18后得到先序遍历结果:20 4 2 7 30 24
// 插入数据
bst.insert(26);
bst.insert(21);
bst.insert(25);
bst.insert(27);
// 先序遍历
var result5 = '';
bst.preOrderTraversal(function (key){
result5 += key + ' ';
})
console.log('在先序为20 4 2 7 30 24 的二叉树搜索树中插入26、21、25、27,得到先序遍历结果:' + result5);
//在先序为20 4 2 7 30 24 的二叉树搜索树中插入26、27、28、29,得到先序遍历结果:20 4 2 7 30 24 21 26 25 27
// 删除结点24
bst.remove(24);
// 先序遍历
var result6 = '';
bst.preOrderTraversal(function (key){
result6 += key + ' ';
})
console.log('先序遍历为20 4 2 7 30 24 21 26 25 27 ,删除结点24后得到先序遍历结果:' + result6);
//先序遍历为20 4 2 7 30 24 21 26 25 27 ,删除结点24后得到先序遍历结果:20 4 2 7 30 25 21 26 27
</script>
</body>
</html>