之前 其实应该接触过树结构的数据结构 就是堆。 其实堆就是一个树结构。
二叉查找树
二叉查找树 是二叉树的种类型。 他需要满足一种特性。 就是左节点不大于根节点。 右节点不小于根节点。
1.遍历
二叉查找树的顺序遍历我们使用的是中序遍历;
printTree(root){
if(root!=null){
printTree(root.left)
print(root)
printTree(root.right)
}
}
上面的简单代码就是中序遍历。
另外还有前序遍历 和后续遍历。都是一个意思。
2.查找/最大值/最小值
二分查找树的查找很方便。 从根节点出发 值比当前值大就右节点。小就左节点。
二对于最大值和最小值的查找也很简单。 都是从根节点做法。 最小值 就是 左节点到底。 最大值 就是右节点到底。
3.前趋和后继
就同过后继来理解。 所谓后继 就是在中序遍历的的下一个节点。 而一个节点 如果存在后继节点的话 会有2种情况
1. 如果当前节点有右节点 就是获取右节点最为根节点的树的最小节点(左节点到底)
2.如果没有右节点。那么后继节点就是 将这个节点规划到左边的最近的祖先节点。
简单的代码如下:(随手写的代码跟语言无关。什么语言的特性都有 反正能看懂就行)
succeed(x){
if(x.right!=null){
return min(x.right)
}
p=parent(x)
while p!=null&& x==p.right // 如果还是右节点就一直做下去 需要找的是规划为左节点的
{
x=p;
p=parent(p);
}
return p;
}
4.插入和删除
插入和删除或者其他修改操作 无论什么操作 需要注意的就是:要保证改变后二叉查找树的性质不改变。
插入跟查找一样,从根节点开始 比当前值大就到右节点,小就左节点。一直到最后。
删除可能比较麻烦一点
1.如果没有 子节点 直接删除
2.如果有一个子节点 删除本身 用子节点替代就ok。
3.如果有2个子节点。 首先删除后继结点(因为是有2个子节点 就是有右节点。那么后继节点 就是右节点的最后一个左节点)。需要注意 如果后继结点有右节点。需要处理别忘了。 用后继节点替代本身。就ok了
红黑树
红黑树是在二叉查找树上面做了一些特殊处理。 因为 在理想的情况下二分查找树很不错。可是特殊的情况,二分查找树的效率就很低了。甚至就是一个链表。
所谓红黑树 就是指每个节点用一个记录颜色是红或者黑。
条件
一个红黑树需要满足以下几点
1.每个节点不是红的就是黑的。
2.根节点是黑的
3.每个叶节点是黑的
4.如果一个节点是红的 那么他的2个儿子是黑的。
5.对于每个节点。从这个节点到其子孙的所以路径包含相同个数的黑节点。
红黑树的插入,删除 ,查找 都是 O(lgn)的根本原因是 红黑树 保证了 树高 小于等于2*lg(n+1)
证明: 根据第四个特性。 说明最长的那条线的 黑节点必然超过或者等于一半。
这里需要知道一些概念 红黑树的内节点 必然是有2个子节点的(只有一个子节点 完美也会用一个哨兵节点作为另外一个节点 哨兵节点是 黑的, 黑红树的 最下面一个节点永远 指向 哨兵节点 这个概念很重要)
如下图 显示
如上图 a 表示 每个 节点旁边的的数字 指的是这个节点的黑高度 bh(x)。
所谓黑高度 是值这个节点到 其他子节点的 黑节点的个数。
条件5 5.对于每个节点。从这个节点到其子孙的所以路径包含相同个数的黑节点。 这个值 记的就是这个意思
接下来开始证明:
我们以节点x 为例子 他的黑高度为 hb(x) 首先需要证明 x节点的 至少有 2^bh(x)-1个内节点。
对于 x节点的2个儿子 他的黑高度 至少为 hb(x)-1(如果是红节点 就是 hb(x) 如果是黑节点 就是 hb(x-1)) 利用刚刚的归纳假设
2个儿子的 内节点 为 2^(bh(x)-1)-1 + 2^(bh(x)-1)-1 就是 2*2^(bh(x)-1) -2 加上本身 就等于 2^bh(x)-1 证明了刚刚的假设。
首先根据条件4. 可以得到 x 节点的的黑高度 肯定小于等于 h/2
n>=2^(h/2)-1
h<=2*lg(n+1)
旋转
树的旋转是一个很重要的特性 树通过旋转 改变它的结构 同时又保证了原本的特性
旋转分为左旋 和 右旋 下面的哪张图 说明了旋转的过程
看图 应该能明白 拿左旋做例子。 上图 对 x 左旋。先将x 变成 y 的做节点 而 原本 y 的左节点变成 x 的右节点。
插入
1.首先 将要插入的节点着红色 按照正常的 查找树 插入。
2.这时候 因为插入了一个值 毫无疑问 破坏了 红黑树的特性。 红黑树的5个特性 只有第二 或者四(只能是一个)个特性会被破坏。(第二个特性 比较好解决 关键是第四个 如果一个节点是红的 那么他的2个儿子是黑的。)那么久需要来修复这个问题了。
下面记录一下 修复的过程
先定义一下约定。 我们 假设 插入的节点是z 获取一个节点的 父节点 用 p 获取兄弟节点 用b
即 p[z] 获取的是z的 父节点 bp[z] 获取的是 自己的叔父节点 pp[z] 获取的是 爷爷节点。 这些节点 都可以用程序获取到 方便理解 就省略了。
接下来开始 处理。
1.如果 p[z] 和 bp[z]都是红色的 那么 将他们都设成 黑色 并且把 pp[z]设成红色 然后z =p[z] 递归调用
如图所示。
2.如果 bp[z] 是 黑色 这里 有2中情况 一种 是 z 是 p[z]的 左节点 另外一种 是 右节点 其实 本质是一样的 只是 相反的处理而已
如上图所示 第一步 我们需要坐的 就是 确定 是左旋 还是右旋一下。 让 z, p[z],pp[z] 呈一条直线。 然后 在宣传一下 让 p[z]成为 中间的 节点
无论是第一种情况 还是第二种情况 对于整个树的性质 没有任何改变。 bh(root) 也没有发生改变。 只有当 第一种情况的C 发生在根节点的时候。这时候 需要将更节点 从红色 变成黑色 这样 会导致 bh(root)的值 +1 。
删除
1.如果没有 子节点 直接删除
2.如果有一个子节点 删除本身 用子节点替代就ok。
3.如果有2个子节点。 首先删除后继结点(因为是有2个子节点 就是有右节点。那么后继节点 就是右节点的最后一个左节点)。需要注意 如果后继结点有右节点。需要处理别忘了。 用后继节点替代本身。就ok了