前言
排序一直是个算法中的重点,且无法回避的问题。排序算法必须要全部遍历每一个数据,才能保证数据是有序的,所以排序算法的复杂度一定是大于 O(n), 像经典的冒泡排序, 插入排序的复杂度是 O(n^2), 快速排序,归并排序的复杂要低些,是 O(nlogn)
而二叉排序树也是一个算法复杂低的排序算法,基于二叉排序树可以衍生很多快速查找的算法,像二叉平衡术,红黑树等。数据库查询数据之所以这么快,很大原因是用了红黑树这样的数据结果。让查询速度呈指数集下降。但是维护一个良好的红黑树需要消耗不小的性能。
回到正题,为什么二叉排序树是有序的呢
二叉排序树(Binary Sort Tree)是一种特殊的二叉树,它的每个结点的值都大于等于(或小于等于)其左子树中所有结点的值,小于等于(或大于等于)其右子树中所有结点的值。就像下面这样
很明显,二叉排序树的中序遍历结果是一个递增的序列。所以要以顺序输出数据,就直接输出二叉排序树的中序遍历结果就好了。
而且,二叉排序树插入、删除和查找等操作的时间复杂度均为O(log n),这就是它的厉害之处。为什么是 logn 呢,因为一个平衡的二叉树,节点数量和树的高度呈对数关系:log n 约等于 h
二叉排序树,下面看看代码如何实现吧
准备数据
这里准备一个乱序的是个小于 10 的自然数
javascript
const treeData = [5, 7, 6, 3, 8, 7, 4, 1, 2];
构建二叉树
javascript
const generateTree = (data) => {
const root = {};
root.value = data[0];
for (let i = 1; i < data.length; i++) {
let tree = root;
while (tree) {
if (data[i] <= tree.value) {
if (tree.left) tree = tree.left;
else {
tree.left = { value: data[i] };
break;
}
} else {
if (tree.right) tree = tree.right;
else {
tree.right = { value: data[i] };
break;
}
}
}
}
return root;
};
generateTree
函数,接受一个数组作为参数。然后将传入的数组转成一个排序二叉树。中间不断遍历数组,遍历到一个,就往构建好的二叉树中插入一个节点。直到所有的数据被遍历完成。
javascript
const tree = generateTree(treeData);
构建好了一个二叉排序树,如何校验它的正确性呢?有两种方法,第一种就是在 IDE 中查看生成树的结构;
下面是编译器中查看到的 json 结构:
javascript
{
value: 5,
right: {
value: 7,
left: {
value: 6,
right: {
value: 7,
},
},
right: {
value: 8,
},
},
left: {
value: 3,
right: {
value: 4,
},
left: {
value: 1,
right: {
value: 2,
},
},
},
}
可以用二叉排序的性质解读这个 json ,看看是否正确。我是觉得没问题😋
第二种就是输出二叉排序树的中序遍历;
javascript
const printTreeIn = (tree) => {
if (!tree) return null;
printNode(tree.left);
console.log(tree.value);
printNode(tree.right);
};
这是一段极其简单的中序遍历代码,下面用它来输出看看:
javascript
printNodeIn(tree);
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 7
// 8
完全正确
构建二叉树的递归写法
上面是构建的循环写法,可能不太好写。相对于循环来讲,递归代码会更加容易懂些:
javascript
const insertNode = (tree, node) => {
if (node.value <= tree.value) {
if (tree.left) insertNode(tree.left, node);
else tree.left = node;
} else {
if (tree.right) insertNode(tree.right, node);
else tree.right = node;
}
};
const generateTree2 = (data) => {
const root = {};
root.value = data[0];
for (let i = 1; i < data.length; i++) {
// insert node
insertNode(root, { value: data[i], left: null, right: null });
}
return root;
};
generateTree2
和上面构建逻辑一致,也是接受一个数组作为参数,然后遍历这个数组,每遍历到一个节点,就插入一个节点。只是这个插入的过程这里专门写了一个函数来做。
insertNode
作用是向二叉排序树中的插入一个新节点。如果要插入节点的值小于等于当前节点,那就插入当前节点的左节点。否则就插入又节点。
代码使用了递归写法,是不是更简单了
下面用printNodeIn
测试写得对不对:
javascript
const tree2 = generateTree2(treeData);
printNodeIn(tree2);
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 7
// 8
完全正确
完整代码
非递归写法
javascript
const treeData = [5, 7, 6, 3, 8, 7, 4, 1, 2];
const generateTree = (data) => {
const root = {};
root.value = data[0];
for (let i = 1; i < data.length; i++) {
let tree = root;
while (tree) {
if (data[i] <= tree.value) {
if (tree.left) tree = tree.left;
else {
tree.left = { value: data[i] };
break;
}
} else {
if (tree.right) tree = tree.right;
else {
tree.right = { value: data[i] };
break;
}
}
}
}
return root;
};
const tree = generateTree(treeData);
const printNode = (tree) => {
if (!tree) return null;
printNode(tree.left);
console.log(tree.value);
printNode(tree.right);
};
printNodeIn(tree);
递归写法
javascript
const insertNode = (tree, node) => {
if (node.value <= tree.value) {
if (tree.left) insertNode(tree.left, node);
else tree.left = node;
} else {
if (tree.right) insertNode(tree.right, node);
else tree.right = node;
}
};
const generateTree2 = (data) => {
const root = {};
root.value = data[0];
for (let i = 1; i < data.length; i++) {
insertNode(root, { value: data[i], left: null, right: null });
}
return root;
};
const tree2 = generateTree2(treeData);
printNodeIn(tree2);
总结
这篇还是比较简单的,就是介绍了二叉树排序的树的构建,也就相当于节点的插入操作。
下篇文章介绍二叉排序树节点删除操作如何来做?有人可能好奇,这个有什么好分享的,不就是直接删除吗?不是的,删除节点之后,还要保证整个二叉树满足二叉排序树
喜欢就关注一下吧❤️,你的点赞和关注是我不断分享的动力