算法 树小结

来源:互联网 发布:creis中指数据 账号 编辑:程序博客网 时间:2024/05/02 01:36


之前 其实应该接触过树结构的数据结构 就是堆。 其实堆就是一个树结构。

二叉查找树

      二叉查找树 是二叉树的种类型。 他需要满足一种特性。 就是左节点不大于根节点。 右节点不小于根节点。

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了


下面是算法导论里面的伪代码:


这样处理 会出现集中情况:
 1. 如果删除的是根 并且用红色的子节点代替了(这种情况 应该只能出现在 跟 只有一个子节点的情况下 直接 将它变成黑色 就ok了)  违反 2
 2. 当删除的节点之后 他的子节点 和父节点 (即下卖弄代码的 p[y]  和 x) 必然 连接起来 如果都是 红的   违反 4 (这种情况 只需要      将 x 直接变成黑色 替代 之前删除的 节点 就ok了)
 3. 删除一个节点之后 包含这个节点的所有 黑高度 会少1. 违反 5 (这里 会将 x 节点 加上一个 黑的 标志 使得x节点 变成 红黑节点          或者 黑黑节点 这样 会违反 第一条规则。 然后 我们解决 第一条 来实现)
     所以 上面 我们主要需要解决的 就是 bh(x)会少一的问题 二这个问题 我们可以转化成了 破坏条件1来实现
     
我们吧重点放在 RE-DELETE-FIXUP(T,X) 这个方法 。这个方法用来重新使红黑树 满足条件。
     
我们首先 要注意。我们在 x 指向的节点 添加了一个 黑 的属性 这就导致 x 是 红黑 节点  或者 黑黑节点。 这时候 要知道 我们并不是 改变节点的结构来实现的。 红黑节点的 color 仍然是 红的。  黑黑节点的属性 仍然是黑的。 只是 当x 指向 哪个节点的时候。我们在逻辑上 理解 它多一个黑属性。

-------------------------------------------------下面之间的 是我自己的想的 不是书上的内容   跳过---------------------------------------------
然后 我们想办法 把 x 的指针 上移 一直到 跟节点 或者 红黑节点。 然后 设置成 黑色就ok 了。
x 只有 指向 黑黑的时候才需要特殊处理  而黑黑 节点提供了 2个 黑节点 所有 x 肯定有个 非 nil 的兄弟节点。

因为 我们的思想 就是 将 额外的 黑属性上移 ,当上移后 影响的 就是 以 p[x]为根的子树。 我需要着重考虑的是 b[x] 和 p[x] 的情况。
当然 主要还是 b(x). 我们需要b(x) 是黑色。
         想想。 如果 b(x) 是黑色了  我们只需要将它设成 红色。 把 x 上移到 p[x]上面。 因为 p[x]
多提供了 一个黑色  而  b(x)  由黑色变成了 红色 那么 条件5就会满足。
 当然 这样 有可能 破坏条件4. 但是 我们插入 的时候 已经解决了这种情况了。当然 更好的办法不是这样解决的。但是这是一个思路(ps: 这是我一开始自己的思路 然后就看书了。就进入书上的思路了。)
---------------------------------------------------------------------------------------------------------------------------------------------------------------

一 如果 b(x)为红色

那么 号为疑问 b(x)的2个子节点都是黑色的 这时候。如果 外面把x上面 额外的黑色上移到

上图一 已经 详述了这种情况 w 指向 b(x) 。 因为 b(x) 是红的 那么 p(x)肯定是黑的。 外面交换 p(x) 和 b(x)的颜色 然后左旋。这样 外面就将 b(x)变成了黑色 就用 下面的情况来处理了

二 如果 b(x)为黑色

当b(x)为黑色的时候。 我们分成2种情况来处理
1. b(x)的 2个子节点都是黑色。这种情况很简单。 将 x 的额外的 黑属性上移。 这样 x 这半边 没有影响 但是  b(x)那半边 会多一个黑属性。 直接把b(x)变成红色 。 这样 就将 黑节点上移了。 递归调用。
2. b(x) 的子节点存在红色的。 我们 不需要考虑 红色的 个数。 只要有红色。 上面的方法就行不通了。 这样 我们考虑这样的情况:
(1) b(x)的同一边的子节点是红色的情况 (这个的意思是 b(x)==right(pb(x))  即 b(x) 如果 是他 的父节点的 右节点的话。 那么 他右子节点 是红色的情况;是左 相反。
w指向了b(x)   b,c 节点的颜色我们并不关心。 这种情况下 将 w 的颜色 设成 b的颜色 并且把b的颜色设成黑色 旋转后。 就直接去掉了 多余的黑色。 
考虑这种情况 根节点 从 B 变成了 D  颜色 不改变。 
右边的已经 将 原w的 右节点变成了黑色。 左节点 左旋到了 原x的那边。 所以右边的结构虽然变化了 但是红黑树的性质没 有改变 。
左边多出一层黑节点。 真好把 双黑的x去掉了。 而移过来的 C节点 处于 B(黑)下 也没有改变。昨晚这个操作 就解决了双黑的情况。 当然 这时候 还没有结束 因为 如果 D节点 变成了根节点 又破坏了红黑树的性质。所以直接将x指向root 继续做循环
(2)和上面的情况相反 不是同一边的是红的。 这时候上面的就不行了 但是 可以通过旋转 转化成上面的情况
这个情况 对 D右旋。 交换CD颜色  因为 C是红的 那么 2个子节点 是黑的 所以旋转之后 不会影响结构。


总结

其实删除  解决黑黑节点 应该是4种情况。 真正解决的 是2种 另外2种 是转化成其他的情况来解决。
1. b(x)是红的  我们通过左旋*(我们的例子是因为b(x)是其父节点的右节点。 如果是左节点就是 右旋 下面的 所有情况的旋转都    是这样 如果在左边 所有旋转 全部相反。 一些 左和 右的操作 也是相反) p(x) 并且交换b(x) 和p(x)的颜色 将这种情况转化成     b(x)为黑的情况
2.b(x)是黑色:3种情况
(1) b(x)的2个子节点都是黑的 这个最简单 x的黑节点 上移到p(x) 将b(x)变成红色就ok了
(2) 如果 b(x)的同一边的 子节点 不是红的 另一个节点是红的。 我们对 b(x)做右旋转(可能左旋) 交换 b(x)和其左(右)节点的颜色
(3)如果 b(x)的同一边的子节点 是红的。这是 另一个 不需要转换了 直接解决的情况。
将 b(x) 变成 p(x)的 颜色
将 p(x) 设成黑色 
左旋(右旋) p(x)
将x指向root节点。
原创粉丝点击