因为从看到写笔记,中间隔了好久,木有激情啊啊啊,但是立flag了丫,你看flag对我这样的人多有用。
二叉树
树啥的,差不多是不管看什么书都很不想看的一个章节了。
树的基本概念
节点:树结构中的每个元素都称作节点。
节点的高度:节点到叶子节点的最长路径
节点的深度:根节点到该节点所经历的边的个数
节点的层数:节点深度+1
树的高度:根节点的高度

二叉树的基本概念
二叉树:最多两个子结点。左子节点、右子节点
满二叉树:除了叶子节点外,每个节点都有左右两个子结点。
完全二叉树:最后一层的叶子节点都靠左排列,除了最后一层,其他层的节点个数都要达到最大。
二叉树的存储
链式存储
基于链表进行存储。每个节点三个字段,存储数据、左右子结点的指针。
顺序存储
基于数组进行存储。根节点存储在i = 1
的位置,根节点下标为i
,则其左子节点存储在下标为2*i
的位置,右子节点存储在下标为2*i+1
的位置。
二叉树的遍历
下面的描述都是左子树、右子树,不是左子节点、右子节点。
前序遍历:对树中的任意节点来说,先打印当前节点,再打印左子树,最后打印右子树。
中序遍历:对树中的任意节点来说,先打印左子树,再打印当前节点,最后打印右子树。
后序遍历:对树中的任意节点来说,先打印右子树,再打印当前节点,最后打印左子树。
二叉查找树
为了实现快速查找,支持快速查找、插入、删除数据。
要求:在树中的任意一个节点,其左子树的每个节点的值小于该节点,右子树节点的值大于该节点。
实现
二叉查找树的插入、查找、删除、前序遍历、中序遍历、后序遍历,按层遍历的实现如下
public class BinSearchTree<T extends Comparable<T>> {
static class Node<T> {
T val;
Node<T> left;
Node<T> right;
Node(T value) {
val = value;
}
public Node(T value, Node left, Node right) {
this.val = value;
this.left = left;
this.right = right;
}
@Override
public String toString() {
return String.valueOf(val);
}
}
private Node root;
BinSearchTree(T value) {
root = new Node(value);
}
public void insert(T value) {
Node<T> node = new Node(value);
if (root == null) {
root = node;
return;
}
Node<T> p = root;
while(p != null) {
if (value.compareTo(p.val) > 0) {
if (p.right == null) {
p.right = node;
return;
}
p = p.right;
} else {
if (p.left == null) {
p.left = node;
return;
}
p = p.left;
}
}
}
public Node<T> find(T value) {
Node<T> p = root;
while(p != null) {
if (value.compareTo(p.val) < 0) {
p = p.left;
} else if (value.compareTo(p.val) > 0) {
p = p.right;
} else {
return p;
}
}
return null;
}
public void delete(T value) {
Node<T> p = root;
Node<T> pp = null;
while (p != null && p.val != value) {
pp = p;
if (value.compareTo(p.val) > 0) {
p = p.right;
} else {
p = p.left;
}
}
if (p == null) {
return;
}
if (p.left != null && p.right != null) {
Node<T> minP = p.right;
Node<T> minPP = p;
while (minP.left != null) {
minPP = minP;
minP = minP.left;
}
p.val = minP.val;
p = minP;
pp = minPP;
}
Node<T> child;
if (p.left != null) {
child = p.left;
} else if (p.right != null) {
child = p.right;
} else {
child = null;
}
if (pp == null) {
root = child;
} else if (pp.left == p) {
pp.left = child;
} else if (pp.right == p) {
pp.right = child;
}
}
Node<T> getRoot() {
return root;
}
public void preOder(Node<T> root) {
if (root == null)
return;
System.out.print(root + " ");
preOder(root.left);
preOder(root.right);
}
public void inOder(Node<T> root) {
if (root == null)
return;
inOder(root.left);
System.out.print(root + " ");
inOder(root.right);
}
public void postOder(Node<T> root) {
if (root == null)
return;
postOder(root.right);
postOder(root.left);
System.out.print(root + " ");
}
public void layerOder(Node<T> root) {
Queue<Node<T>> queue = new LinkedList<Node<T>>();
queue.add(root);
while (!queue.isEmpty()) {
Node<T> curNode = queue.poll();
System.out.print(curNode + " ");
if (curNode.left != null) {
queue.add(curNode.left);
}
if (curNode.right != null) {
queue.add(curNode.right);
}
}
}
}
复杂度分析
时间复杂度跟树的高度成正比。O(logn)。
红黑树
平衡查找二叉树是用来应对树在动态更新中,出现的复杂度退化的情况。
平衡查找二叉树
平衡二叉树定义:二叉树中任意一个节点的做右子树的高度差不能大于1。
平衡查找二叉树定义:平衡二叉树 + 二叉查找树
红黑树
红黑树中的节点,一类标记为黑色,一类标记为红色。此外还需满足:
- 根节点是黑色
- 每个叶子节点都是黑色的空节点(NIL)。叶子节点不存储数据
- 任何相邻的节点不能同时为红色
- 每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数据的黑色节点。

平衡:树的高度稳定地趋近于log2n
【性能预推导】
我喜欢这样换个角度把问题简单化,至少在如临大敌前,打个乐观预防针。
根据据红黑树定义的最后一条,来分析最好情况下的复杂度。

去掉红色节点后的“红黑树”,看作是四叉树,比包含相同节点个数的完全二叉树的高度要小,所以复杂度不超过logn。
然后把红色节点加回去,根据红黑树定义第三条,红色节点的个数不超过黑色节点,所以最长路径不超过2logn。
红黑树相较于高度平衡的AVL树,是近似平衡。在维护平衡的成本上要低一点。
实现的基本思路
概念:
- 左旋:围绕某个节点的左旋
- 右旋:围绕某个节点的右旋

因为红黑树首先是一个二叉查找树,所以旋完之后要调整为正确的查找树。
插入
首先有个小规律记一下:二叉查找树新插入的节点都是放在叶子节点上。
红黑树规定:插入的节点必须是红色。
此时需要处理两种特殊情况:
- 插入的是父节点,直接改颜色
- 插入节点的父节点是黑色的,直接插入即可。
其他情况均违背红黑树的定义,需要进行调整:左右旋、改颜色。

调整策略就是这样了,不晓得为啥,但是背后肯定有一个计算公式丫,定理丫啥的在支持。
删除
分为两步:
- 初步调整:保证树在节点删除后仍满足红黑树的最后一条规定。
- 针对关注节点调整:保证满足红黑树的第三条定义。
初步调整

二次调整

大概就是记一下,不知道为啥要这样调之前我肯定是不会用到这些规则的,红黑树这么难撩,交给专业的人写就好啦。
递归树
用来求解时间复杂度。
(不要骂,我就看明白了这个……)