一、树
- 树(Tree)是n(n>=0)个结点的有限集。 在任意一棵非空树中: 有且仅有一个特定的称为根(Root)的结点;
- 树的结点包含一个数据元素及若干指向其子树的分支。结点拥有的子树数称为结点的度(Degree)。(b)中A的度为3,C的度为1,F的度为0.
- 度为0的结点称为叶子(Leaf)或者终端结点。
- 结点的层次(Level)从根开始定义起,根为第一层,跟的孩子为第二层。树中结点的最大层次称为树的深度(Depth)或高度。(b)的树的深度为4。
二、二叉树
- 二叉树(Binary Tree),它的特点是每个结点至多只有两棵子树(即二叉树中不存在度大于2的结点),并且,二叉树的子树有左右之分(其次序不能任意颠倒。)
- 一棵深度为k且有2的k次方-1个结点的二叉树称为满二叉树。
- 深度为k的,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应时,称之为完全二叉树。
- 二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树) 它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空, 则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空, 则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。 特点: 左子树小于父节点,右子树大于父节点
三、创建二叉查找树
接下来就是代码部分,实现上图中的创建一个二叉查找树的逻辑。
- 创建类
// 创建树节点类
class Node {
constructor (value) {
this.value = value;
this.left = null;
this.right = null;
}
}
// 创建二叉查找树类
class BinaryTree {
constructor () {
this.root = null;
}
// 若根 (root) 为null,则直接设为根;否则递归插入
insert(value) {
let newNode = new Node(value);
if (this.root === null) {
this.root = newNode;
} else {
this.insertNode(this.root, newNode);
}
}
insertNode(node, newNode) {
// 依照树的特点,小值递归插入左树,大值递归插入右树
if (newNode.value < node.value) {
if (node.left === null) {
node.left = newNode;
} else {
this.insertNode(node.left, newNode);
}
} else {
if (node.right === null) {
node.right = newNode;
} else {
this.insertNode(node.right, newNode);
}
}
}
}
- 遍历树
遍历分为前序遍历、中序遍历、后序遍历
- 中序遍历: 先输出左树,再输出根,最后输出右树
- 前序遍历: 先输出根,再输出左树,最后输出右树
- 后序遍历: 先输出左树,再输出右树,最后输出根
(1)递归遍历
//三种遍历的区别在于输出顺序
//中序遍历
inOrderTraverse(callback) {
this.inOrderTraverseNode(this.root, callback)
}
inOrderTraverseNode(node, callback) {
if (node !== null) {
this.inOrderTraverseNode(node.left, callback);
callback(node.value);
this.inOrderTraverseNode(node.right, callback);
}
}
//先序遍历
preOrderTraverse(callback) {
this.preOrderTraverseNode(this.root, callback)
}
preOrderTraverseNode(node, callback) {
if (node !== null) {
callback(node.value);
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.value);
}
}
(2)非递归遍历
//栈 的先进后出特点
//中序遍历
//1.进入循环,先将根入栈,再将左节点作为新的根,进入下次循环
//2.若没有左节点则将当前节点出栈,并将右节点作为下次循环根,进入下次循环
//3.若没有右节点,则将当前节点出栈,重复第二步
inOrderTraverseByStack(callback) {
let stack = [],
node = this.root;
while (stack.length > 0 || node) {
if (node) {
stack.push(node);
node = node.left;
} else {
node = stack.pop();
callback(node.value);
node = node.right;
}
}
}
//先序遍历
//1.先将根节点入栈
//2.然后进入循环,当前节点出栈,右节点、左节点依次入栈 (注意顺序,栈先进后出)
preOrderTraverseByStack(callback) {
let stack = [],
node = this.root;
stack.push(node);
while (stack.length > 0) {
node = stack.pop();
callback(node.value);
if (node.right) {
stack.push(node.right);
}
if (node.left) {
stack.push(node.left);
}
}
}
//后序遍历
//1.创建两个栈,先把根放入栈1
//2.进入循环,从栈1出栈一个节点,放入栈2
//3.判断这个节点的左右节点是否存在,存在则分别放入放入栈1 (注意左节点先入栈1,右节点后入,这样到栈2就是反过来的顺序)
//4.依次循环,直到栈1为空,循环栈2,依次出栈即可
postOrderTraverseByStack(callback) {
let stack1 = [],
stack2 = [],
node = this.root;
stack1.push(node);
while (stack1.length > 0) {
node = stack1.pop();
stack2.push(node);
if (node.left !== null) {
stack1.push(node.left);
}
if (node.right !== null) {
stack1.push(node.right);
}
}
while (stack2.length > 0) {
node = stack2.pop();
callback(node.value);
}
}
- 查找最小、最大值以及特定值
// 查找最小、最大值
findMin() {
let minNode = this.findMinNode(this.root);
return minNode ? minNode.value : null;
}
findMinNode(node) {
if (node) {
while (node && node.left !== null) {
node = node.left;
}
return node;
}
return null;
}
findMax() {
let maxNode = this.findMaxNode(this.root);
return maxNode ? maxNode.value : null;
}
findMaxNode(node) {
if (node) {
while (node && node.right !== null) {
node = node.right;
}
return node;
}
return null;
}
//查找特定值
find(value) {
return this.findSearchNode(this.root, value);
}
findSearchNode(node, value) {
if (node === null) {
return false;
}
if (value < node.value) {
return this.findSearchNode(node.left, value);
} else if (value > node.value) {
return this.findSearchNode(node.right, value);
} else {
return true;
}
}
- 删除一个指定节点
//首先找到要删除节点的位置
//然后分3种情况
//1.没有子节点,直接删除
//2.有一个子节点,用子节点替换当前节点
//3.有两个子节点,则在右树中找到最小值替换当前节点的值,并删除替换的节点,以保持树的特性
remove(value) {
this.root = this.removeNode(this.root, value);
}
removeNode(node, value) {
if (node === null) {
return node;
}
if (value < node.value) {
node.left = this.removeNode(node.left, value);
return node;
} else if (value > node.value) {
node.right = this.removeNode(node.right, value);
return node;
} else {
//情况1 没有子节点
if (node.left === null && node.right === null) {
node = null;
return node;
}
//情况2 一个子节点
if (node.left === null) {
node = node.right;
return node;
} else if (node.right === null) {
node = node.left;
return node;
}
//情况3 两个子节点
let aux = this.findMinNode(node.right);
node.value = aux.value;
node.right = this.removeNode(node.right, aux);
return node;
}
}
至此,二叉查找树的创建、插入、遍历、查找、删除已全部实现。下面是实例化代码。
// 实例化二叉树
let nodes = [8, 6, 16, 5, 10, 9, 7, 18, 15, 17, 23, 29];
let binaryTree = new BinaryTree();
nodes.forEach(key => {
binaryTree.insert(key);
});
let inOrderNodes = [];
let preOrderNodes = [];
let postOrderNodes = [];
let callback = function (key) {
inOrderNodes.push(key)
}
let callback2 = function (key) {
preOrderNodes.push(key)
}
let callback3 = function (key) {
postOrderNodes.push(key)
}
// 中序遍历数组
binaryTree.inOrderTraverse(callback);
// binaryTree.inOrderTraverseByStack(callback);
console.log(inOrderNodes);
// 先序遍历数组
binaryTree.preOrderTraverse(callback2);
// binaryTree.preOrderTraverseByStack(callback2)
console.log(preOrderNodes);
// 后序遍历数组
binaryTree.postOrderTraverse(callback3);
// binaryTree.postOrderTraverseByStack(callback3)
console.log(postOrderNodes);
//[8, 6, 16, 5, 10, 9, 7, 18, 15, 17, 23, 29];
//=========== 删除节点前 ==========//
console.log('=========删除节点前==========');
// 查找最小、最大值
console.log(`最小值为 ${binaryTree.findMin()}`);
console.log(`最大值为 ${binaryTree.findMax()}`);
// 查找特定值是否存在
console.log(`9 ${binaryTree.find(9) ? '存在' : '不存在'}`);
console.log(`88 ${binaryTree.find(88) ? '存在' : '不存在'}`);
//=========== 删除节点后 ==========//
console.log('=========删除节点5, 9 插入节点88==========');
// 删除子节点
binaryTree.remove(5);
binaryTree.remove(9);
//插入88
binaryTree.insert(88);
// 查找最小、最大值
console.log(`最小值为 ${binaryTree.findMin()}`);
console.log(`最大值为 ${binaryTree.findMax()}`);
// 查找特定值是否存在
console.log(`9 ${binaryTree.find(9) ? '存在' : '不存在'}`);
console.log(`88 ${binaryTree.find(88) ? '存在' : '不存在'}`);
binaryTree.inOrderTraverseByStack(callback);
console.log(inOrderNodes)
// [ 6, 7, 8, 10, 15, 16, 17, 18, 23, 29, 88 ]
// 对比原输出
// [ 5, 6, 7, 8, 9, 10, 15, 16, 17, 18, 23, 29 ]
查看 完整代码
Generator与for...of的运用
递归遍历还可以结合ES6中的Generator函数,yield*以及for...of来实现
inOrderTraverseByGen(callback) {
//for...of循环可以应用于任何部署了Iterator接口的数据结构
for (let value of this.inOrderTraverseNodeByGen(this.root)) {
callback(value);
}
}
// 函数名前面加上* 代表这是一个Generator函数
* inOrderTraverseNodeByGen(node) {
if (node !== null) {
//yield* 可以在一个Generator函数内部执行另一个Generator函数
yield* this.inOrderTraverseNodeByGen(node.left);
yield node.value;
yield* this.inOrderTraverseNodeByGen(node.right);
}
}
//...
//用法和输出结果和之前一致
binaryTree.inOrderTraverseByGen(callback);
console.log(inOrderNodes);
//[ 5, 6, 7, 8, 9, 10, 15, 16, 17, 18, 23, 29 ]