一、二叉树基础
高度、深度:是从零开始算的
层:和深度一样,只是从一开始算
完全二叉树:最后一层只允许缺右子节点的二叉树,如果不缺则是满二叉树
存储方式:
- 链式存储法(常用)
- 数组。假设i为数组下标,根节点存在i = 1的位置,那么左子节点存储在下标 2 i 的位置,右子节点存储在 2 i + 1。如果是完全二叉树,那么该方式最节约空间。如果不是,则会浪费比较多的数组存储空间。
遍历:
前序遍历、中序遍历、后续遍历
以中序遍历为例:
function inOrder(point) {
if (point === null) {
return
}
inOrder(point.left);
console.log(point.value);
inOrder(point.right)
}
时间复杂度:O(n)
二、二叉查找树
树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。二叉查找树支持快速查找、插入、删除操作
下面代码实现了二叉查找树的相关功能:
class Node {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
class SearchTree {
constructor() {
this.root = null
}
// 插入
insert(num) {
if (this.root === null) {
this.root = new Node(num)
return
}
let current = this.root
while (true) {
if (num <= current.value) {
if (current.left === null) {
current.left = new Node(num)
return
}
current = current.left
} else {
if (current.right === null) {
current.right = new Node(num)
return
}
current = current.right
}
}
}
// 中序遍历
print(current) {
if (current === null) {
return
}
this.print(current.left)
console.log(current.value)
this.print(current.right)
}
// 查找,支持查找多个
find(num) {
let result = []
let current = this.root
while (current !== null) {
if (num <= current.value) {
if (num === current.value) {
result.push(current)
}
current = current.left
} else {
current = current.right
}
}
return result.length > 1 ? result : (result[0] || null)
}
remove(num) {
let p = this.root;
let pp = null; // pp记录的是p的父节点
while (p !== null && p.value != num) {
pp = p;
p = num > p.value ? p.right : p.left
}
if (p === null) { // 没找到
return false
}
// 要删除的节点有两个子节点
if (p.left != null && p.right != null) { // 查找右子树中最小节点
let minP = p.right;
let minPP = p; // minPP表示minP的父节点
while (minP.left != null) {
minPP = minP;
minP = minP.left;
}
p.value = minP.data; // 将minP的数据替换到p中
p = minP; // 下面就变成了删除minP了
pp = minPP;
}
// 删除节点是叶子节点或者仅有一个子节点
let child = null;
if (p.left !== null) child = p.left
else if (p.right !== null) child = p.right
if (pp === null) tree = child; // 删除的是根节点
else if (pp.left.value === p.value) pp.left = child;
else pp.right = child;
return true
}
}
function test() {
const searchTree = new SearchTree();
searchTree.insert(4);
searchTree.insert(1);
searchTree.insert(2);
searchTree.insert(5);
console.log('before remove');
searchTree.print(searchTree.root); //1 2 4 5
searchTree.remove(1);
console.log('after remove');
searchTree.print(searchTree.root); //1 2 4 5
console.log('search 1', searchTree.find(1))
console.log('search 5', searchTree.find(5))
}
test()
中序遍历二叉查找树,可以输出有序的数据序列,时间复杂度是 O(n)
通过遍历左右子树可以快速查找到最小值和最大值
插入、删除和查找,时间复杂度其实都跟树的高度成正比,也就是 O(height)
完全二叉树的高度小于等于 log2n,极度不平衡的二叉查找树高度可以是n
平衡二叉查找树:任意一个节点的左右子树的高度相差不能大于 1 的二叉查找树。它的优势:让整棵树的高度相对来说低一些,插入、删除、查找等操作的效率高一些。使用最多的是红黑树
三、堆
概念:堆是一个完全二叉树;堆中每一个节点的值都必须大于等于(大顶堆)或小于等于(小顶堆)其子树中每个节点的值。
用数组实现:i的子节点为2i和2i+1,父节点为i/2向下取整
插入操作:往数组尾部插入,为了保证堆的特性,要将该数据依次与父节点对比,如果大于父节点则交换(假设是大顶堆),部分代码逻辑为:
while (i/2 >= 1 && a[i] > a[Math.floor(i/2)]) {
swap(a, i, i/2);
i = Math.floor(i/2);
}
这部分循环次数最多为树的高度,所以时间复杂度为O(logn)
删除操作:将数组最后一个节点赋值给要删除的节点,最后一个节点删除,再将该节点与子节点依次对比交换,时间复杂度也是O(logn)
应用:堆排序
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。