什么是二叉树?
一幅图胜过千言万语:
什么是排序二叉树(平衡二叉树/二叉搜索树)?
首先,排序二叉树肯定是一颗二叉树,然后它还需要满足一些条件:
如果一个节点有左孩子,那它左孩子的节点值一定小于它,如果一个节点有右孩子,那它右孩子的节点值一定大于它,然后它的左右孩子也是一颗排序二叉树,上图就是一颗平衡二叉树
如何构建排序二叉树
给定一个数组,[8, 3, 10, 1, 6, 14, 4, 7, 13] 作为节点值
var nodes = [8, 3, 10, 1, 6, 14, 4, 7, 13];
var binaryTree = new BinaryTree();
nodes.forEach(function(key) {
binaryTree.insert(key);
});
通过调用节点插入函数构建一颗排序二叉树,首先,将数组中每个值生成节点,以此插入,第一个插入的节点作为根节点。
//插入节点的函数接口
this.insert = function(key) {
var newNode = new Node(key);
//如果此时是插入第一个节点,则将其作为根节点
if (root === null) {
root = newNode;
} else {
insertNode(root, newNode);
}
};
如果树不为空,则将待插入的节点通过比较,将其插入到相应位置。具体情况看下面 的代码注释。
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);
}
}
};
二叉树的前中后序遍历
前序遍历:根 左 右
中序遍历:左 根 右
后序遍历:左 右 根
这里我们以前序遍历为例子实现:
//前序遍历接口函数
this.preOrderTraverse = function(callback) {
preOrderTraverseNode(root, callback);
};
callback 函数理解为回调函数,用于在控制台上输出遍历的节点
//回调函数
var callback = function(key) {
console.log(key);
};
前序遍历节点过程的实现
var preOrderTraverseNode = function(node, callback) {
if (node != null) {
callback(node.key);
preOrderTraverseNode(node.left, callback);
preOrderTraverseNode(node.right, callback);
}
};
根据前序遍历的例子写出中序遍历和后序遍历的代码应该不难,这里就不贴了,在最后面会给出整个的功能实现代码
二叉树节点的查找
首先查找最小值节点,只需从根节点开始查找,只要当前节点有左节点,就将其更新为其左节点,直到它没有左节点,根据二叉排序树的构建原理,那它就是最小的。
同理,查找最大值节点,只需从根节点开始查找,只要当前节点含有右节点,就将其更新为其右节点,直到它没有右节点为止,它就是最大值节点。
//查找最小值接口函数
this.min = function() {
return minNode(root);
};
//查找最大值接口函数
this.max = function() {
return maxNode(root);
};
//查早最小节点
var minNode = function(node) {
if (node) {
while (node && node.left != null) {
node = node.left;
}
return node.key;
}
return null;
};
//查找最大节点
var maxNode = function(node) {
if (node) {
while (node && node.right != null) {
node = node.right;
}
return node.key;
}
return null;
};
查找一般的节点,如果存在,则返回true,否则返回false,这个需要有一个参数,key通过 key与当前节点值(从根节点开始)node.key比较,如果比当前节点值小,则在当前节点的左子树中寻找,如果比当前节点值大,则往当前节点的右子树中寻找。递归调用search函数。
//查找是否存在给定值节点接口函数
this.search = function(key) {
return searchNode(root, key);
};
//查找是否存在给定值节点
var searchNode = function(node, key) {
if (node == null) {
return false;
}
if (key < node.key) {
return searchNode(node.left, key);
} else if (key > node.key) {
return searchNode(node.right, key);
} else {
return true;
}
};
删除排序二叉树结点
这个又需要分情况讨论了,我们还是看到这幅图:
注意,我们删除结点之后还要保证它是一颗排序二叉树,即大小关系不变
- 删除的是叶子结点:如 1
- 删除的结点只有左子树或者只有右子树 ,如 14,这种情况只需将当前结点替换为其左子树或者右子树即可
- 删除的 结点同时具有左右孩子树 如 3 ,查找其右子树的最小结点值,并用其替换当前结点值3 ,再删除最小值结点。
//删除节点操作
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;
}
//要删除的节点只有左子树
else if (node.right === null) {
node = node.left;
return node;
}
//删除的节点是含有两个孩子的节点
var temp = findMinNode(node.right); //找到最小值节点
node.key = temp.key; //替换要删除的节点值
node.right = removeNode(node.right, temp.key); //从该节点的右子树中删除最小值节点
return node;
}
};
整个的二叉树功能实现代码
可以用浏览器控制台,采用单步调试对代码进行验证
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Binary Trees</title>
</head>
<body>
<script>
function BinaryTree() {
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);
}
}
};
//插入节点的函数接口
this.insert = function(key) {
var newNode = new Node(key);
//如果此时是插入第一个节点,则将其作为根节点
if (root === null) {
root = newNode;
} else {
insertNode(root, newNode);
}
};
//中序遍历二叉树
var inOrderTraverseNode = function(node, callback) {
if (node != null) {
inOrderTraverseNode(node.left, callback);
callback(node.key);
inOrderTraverseNode(node.right, callback);
}
};
//前序遍历二叉树
var preOrderTraverseNode = function(node, callback) {
if (node != null) {
callback(node.key);
preOrderTraverseNode(node.left, callback);
preOrderTraverseNode(node.right, callback);
}
};
//后序遍历二叉树
var postOrderTraverseNode = function(node, callback) {
if (node != null) {
postOrderTraverseNode(node.left, callback);
postOrderTraverseNode(node.right, callback);
callback(node.key);
}
};
//查早最小节点
var minNode = function(node) {
if (node) {
while (node && node.left != null) {
node = node.left;
}
return node.key;
}
return null;
};
//查找最大节点
var maxNode = function(node) {
if (node) {
while (node && node.right != null) {
node = node.right;
}
return node.key;
}
return null;
};
//查找是否存在给定值节点
var searchNode = function(node, key) {
if (node == null) {
return false;
}
if (key < node.key) {
return searchNode(node.left, key);
} else if (key > node.key) {
return searchNode(node.right, key);
} else {
return true;
}
};
//寻找最小值节点
var findMinNode = function(node) {
if (node) {
while (node && node.left) {
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;
}
//要删除的节点只有左子树
else if (node.right === null) {
node = node.left;
return node;
}
//删除的节点是含有两个孩子的节点
var temp = findMinNode(node.right); //找到最小值节点
node.key = temp.key; //替换要删除的节点值
node.right = removeNode(node.right, temp.key); //从该节点的右子树中删除最小值节点
return node;
}
};
//中序遍历函数接口
this.inOrderTraverse = function(callback) {
inOrderTraverseNode(root, callback);
};
//前序遍历接口函数
this.preOrderTraverse = function(callback) {
preOrderTraverseNode(root, callback);
};
//后序遍历接口函数
this.postOrderTraverse = function(callback) {
postOrderTraverseNode(root, callback);
};
//查找最小值接口函数
this.min = function() {
return minNode(root);
};
//查找最大值接口函数
this.max = function() {
return maxNode(root);
};
//查找是否存在给定值节点接口函数
this.search = function(key) {
return searchNode(root, key);
};
//删除叶子节点接口函数
this.remove = function(key) {
removeNode(root, key);
};
}
//保存二叉树节点数值
var nodes = [8, 3, 10, 1, 6, 14, 4, 7, 13];
var binaryTree = new BinaryTree();
nodes.forEach(function(key) {
binaryTree.insert(key);
});
//回调函数
var callback = function(key) {
console.log(key);
};
// binaryTree.inOrderTraverse(callback);
// binaryTree.preOrderTraverse(callback);
// binaryTree.postOrderTraverse(callback);
// console.log("the minNode is :" + binaryTree.min());
// console.log("the maxNode is :" + binaryTree.max());
// console.log(binaryTree.search(7)?'key 7 is found':'key 7 is not found');
// console.log(binaryTree.search(9)?'key 9 is found':'key 9 is not found');
// binaryTree.remove(1);
binaryTree.remove(3);
</script>
</body>
</html>
因为有些功能,比如删除结点 的操作效果需要自行单步调试才可以理解地更加彻底,建议自己调试一下。