erl_tree RBT 红黑树

来源:互联网 发布:centos nginx web目录 编辑:程序博客网 时间:2024/05/23 00:13

文字说明大部分转载:http://yanglongylj.blog.163.com/blog/static/563834532009113021438417/

红黑树的性质与定义
红黑树(red-black tree) 是一棵满足下述性质的二叉查找树:
1. 每一个结点要么是红色,要么是黑色。
2. 根结点是黑色的。
3. 所有叶子结点都是黑色的(实际上都是Null指针,下图用NIL表示)。叶子结点不包含任何关键字信息,所有查询关键字都在非终结点上。
4. 每个红色结点的两个子节点必须是黑色的。换句话说:从每个叶子到根的所有路径上不能有两个连续的红色结点
5. 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点
这里写图片描述
黑深度 ——从某个结点x出发(不包括结点x本身)到叶结点(包括叶子结点)的路径上的黑结点个数,称为该结点x的黑深度,记为bd(x),根结点的黑深度就是该红黑树的黑深度。叶子结点的黑深度为0。比如:上图bd(13)=2,bd(8)=2,bd(1)=1
内部结点 —— 红黑树的非终结点
外部节点 —— 红黑树的叶子结点

红黑树相关定理
1. 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。
根据上面的性质5我们知道上图的红黑树每条路径上都是3个黑结点。因此最短路径长度为2(没有红结点的路径)。再根据性质4(两个红结点不能相连)和性质1,2(叶子和根必须是黑结点)。那么我们可以得出:一条具有3个黑结点的路径上最多只能有2个红结点(红黑间隔存在)。也就是说黑深度为2(根结点也是黑色)的红黑树最长路径为4,最短路径为2。从这一点我们可以看出红黑树是 大致平衡的。 (当然比平衡二叉树要差一些,AVL的平衡因子最多为1)

  1. 红黑树的树高(h)不大于两倍的红黑树的黑深度(bd),即h<=2bd
    根据定理1,我们不难说明这一点。bd是红黑树的最短路径长度。而可能的最长路径长度(树高的最大值)就是红黑相间的路径,等于2bd。因此h<=2bd。

  2. 一棵拥有n个内部结点(不包括叶子结点)的红黑树的树高h<=2log(n+1)
    下面我们首先证明一颗有n个内部结点的红黑树满足n>=2^bd-1。这可以用数学归纳法证明,施归纳于树高h。当h=0时,这相当于是一个叶结点,黑高度bd为0,而内部结点数量n为0,此时0>=2^0-1成立。假设树高h<=t时,n>=2^bd-1成立,我们记一颗树高 为t+1的红黑树的根结点的左子树的内部结点数量为nl,右子树的内部结点数量为nr,记这两颗子树的黑高度为bd’(注意这两颗子树的黑高度必然一 样),显然这两颗子树的树高<=t,于是有nl>=2^bd’-1以及nr>=2^bd’-1,将这两个不等式相加有nl+nr>=2^(bd’+1)-2,将该不等式左右加1,得到n>=2^(bd’+1)-1,很显然bd’+1>=bd,于是前面的不等式可以 变为n>=2^bd-1,这样就证明了一颗有n个内部结点的红黑树满足n>=2^bd-1。
    在根据定理2,h<=2bd。即n>=2^(h/2)-1,那么h<=2log(n+1)
    从这里我们能够看出,红黑树的查找长度最多不超过2log(n+1),因此其查找时间复杂度也是O(log N)级别的。

红黑树的操作

因为每一个红黑树也是一个特化的二叉查找树,因此红黑树上的查找操作与普通二叉查找树上的查找操作相同。然而,在红黑树上进行插入操作和删除操作会导致不 再符合红黑树的性质。恢复红黑树的属性需要少量(O(log n))的颜色变更(实际是非常快速的)和不超过三次树旋转(对于插入操作是两次)。 虽然插入和删除很复杂,但操作时间仍可以保持为 O(log n) 次 。

插入操作
我们首先以二叉查找树的方法增加节点并标记它为红色。 ( 如果设为黑色,就会导致根到叶子的路径上有一条路上,多一个额外的黑节点,这个是很难调整的。但是设为红色节点后,可能会导致出现两个连续红色节点的冲突,那么可以通过颜色调换(color flips)和树旋转来调整。) 下面要进行什么操作取决于其他临近节点的颜色。同人类的家族树中一样,我们将使用术语叔父节点来指一个节点的父节点的兄弟节点。

假设新加入的结点为N,父亲结点为P,叔父结点为Ui(叔父结点就是一些列P的兄弟结点),祖父结点G(父亲结点P的父亲)。

情况1. 当前红黑树为空,新结点N位于树的根上,没有父结点。
情况2. 新结点N的父结点P是黑色。

情况3.如果父节点P和叔父节点U二者都是红色。

    如下图,因为新加入的N结点必须为红色,那么我们可以将父结点P(保证性质4),以及N的叔父结点U(保证性质5)重新绘制成黑色。如果此时祖父结点G是根,则结束变化。如果不是根,则祖父结点重绘为红色(保证性质5)。但是,G的父亲也可能是红色的,为了保证性质4。我们把G递归当做新加入的结点N在进行各种情况的重新检查。

这里写图片描述
注意:在情形4和5下,我们假定父节点P 是祖父结点G 的左子节点。如果它是右子节点,情形4和情形5中的左和右应当对调。

情况4. 父节点P是红色而叔父节点U是黑色或缺少; 另外,新节点N是其父节点P的右子节点,而父节点P又是祖父结点G的左子节点。
如下图, 在这种情形下,我们进行一次左旋转调换新节点和其父节点的角色(与AVL树的左旋转相同); 这导致某些路径通过它们以前不通过的新节点N或父节点P中的一个,但是这两个节点都是红色的,所以性质5没有失效。但目前情况将违反性质4,所以接着,我们按下面的情况5继续处理以前的父节点P。
这里写图片描述
情况5. 父节点P是红色而叔父节点U 是黑色或缺少,新节点N 是其父节点的左子节点,而父节点P又是祖父结点的G的左子节点。

   如下图: 在这种情形下,我们进行针对祖父节点P 的一次右旋转; 在旋转产生的树中,以前的父节点P现在是新节点N和以前的祖父节点G 的父节点。我们知道以前的祖父节点G是黑色,否则父节点P就不可能是红色。我们切换以前的父节点P和祖父节点G的颜色,结果的树满足性质4[3]。性质 5[4]也仍然保持满足,因为通过这三个节点中任何一个的所有路径以前都通过祖父节点G ,现在它们都通过以前的父节点P。在各自的情形下,这都是三个节点中唯一的黑色节点。

这里写图片描述

%% Copyright-module(red_black_tree).-author("sugar").%% API-compile(export_all).%%=========rb Tree==============%% rb_tree={Size,Tree}%% Tree=  {Key,color, Value, Smaller, Bigger} |nil%% Smaller=Tree%% Bigger=  Tree%% 红黑树(red-black tree) 是一棵满足下述性质的二叉查找树:%% 1. 每一个结点要么是红色,要么是黑色。%% 2. 根结点是黑色的。%% 3. 所有叶子结点都是黑色的(实际上都是Null指针,下图用NIL表示)。叶子结点不包含任何关键字信息,所有查询关键字都在非终结点上。%% 4. 每个红色结点的两个子节点必须是黑色的。换句话说:从每个叶子到根的所有路径上不能有两个连续的红色结点%% 5. 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点rb_tree_init()->    gb_trees:empty().insert(Key,Value,RBT)->    {Size,Tree} = RBT,    {Key1,_Color, V, Smaller, Bigger} = insert1(Key,Value,Tree,[]),    {Size+1,{Key1,b, V, Smaller, Bigger}}.insert1(Key,Value,nil,_)->    {Key,b,Value,nil,nil};insert1(Key, Value, {Key1,Color, V, Smaller, Bigger},PassNodeTrack) when Key < Key1 ->    insert1(Key, Value, Smaller,[{{Key1,Color, V, nil, Bigger},0}|PassNodeTrack]);insert1(Key, Value, {Key1,Color, V, Smaller, Bigger},PassNodeTrack) when Key > Key1 ->    insert1(Key, Value, Bigger,[{{Key1,Color, V, Smaller, nil},1}|PassNodeTrack]);insert1(Key, Value, nil,PassNodeTrack) ->    NewPassNodeTrack = [{{Key,r, Value, nil, nil},2}|PassNodeTrack],    insert2(NewPassNodeTrack);insert1(Key, _, _, _) ->    erlang:error({key_exists, Key}).insert2(NewPassNodeTrack) when length(NewPassNodeTrack) < 3->    insert2_1(NewPassNodeTrack);%%向上重构树insert2(NewPassNodeTrack)->    {HNodeTrack,TNodeTrack} = lists:split(3,NewPassNodeTrack),    NTree = insert2_1(HNodeTrack),    NTree1 = insert2_1_turn(NTree,HNodeTrack),    insert2_1([{NTree1,2}|TNodeTrack]).%%插入者为r   改变颜色操作insert2_1_turn({_K,r,_V,_ST,_BT} = Tree,_) ->     %%父亲为黑不用变色    Tree;insert2_1_turn({K,_C,V,{K1,r,V1,ST1,BT1},{K2,r,V2,ST2,BT2}},_) ->    {K,r,V,{K1,b,V1,ST1,BT1},{K2,b,V2,ST2,BT2}};insert2_1_turn(Tree,HNodeTrack) ->     %%父亲为黑不用变色    insert2_1_turn1(Tree,HNodeTrack).insert2_1_turn1(Tree,HNodeTrack)->    List = [Dir||{_,Dir}<-HNodeTrack],    insert2_1_turn2(Tree,List).%%父左旋转insert2_1_turn2({K,_C,V,{K1,C1,V1,ST1,{K2,C2,V2,ST2,BT2}},BT},[ND,1,0])->    {K3,_C3,V3,ST3,BT3} = avl_tree_balance_left_turn({K1,C1,V1,ST1,{K2,C2,V2,ST2,BT2}}),    NewList = [ND,0,0],    insert2_1_turn3({K,r,V,{K3,b,V3,ST3,BT3},BT},NewList);%%父右旋转insert2_1_turn2({K,_C,V,ST,{K1,C1,V1,{K2,C2,V2,ST2,BT2},BT1}}, [ND,0,1]) ->    {K3,_C3,V3,ST3,BT3} = avl_tree_balance_right_turn({K1,C1,V1,{K2,C2,V2,ST2,BT2},BT1}),    NewList = [ND,1,1],    insert2_1_turn3({K,r,V,ST,{K3,b,V3,ST3,BT3}},NewList).%%爷左旋转insert2_1_turn3(Tree,[_,0,0])->    avl_tree_balance_right_turn(Tree);%%爷右旋转insert2_1_turn3(Tree, [_,1,1]) ->    avl_tree_balance_left_turn(Tree).%% RR  当树中节点X的右孩子的右孩子上插入新元素,且平衡因子从-1变成-2后,就需要绕节点X进行左旋转。%% S={80,1,{60,2,nil,nil},{90,3,{85,4,nil,nil},{120,5,{100,6,nil,nil},nil}}}.avl_tree_balance_left_turn({Key,C, V, ST, nil})->    {Key,C, V, ST, nil};avl_tree_balance_left_turn({Key, C,V, ST, {Key1,C1, V1, ST1, BT1}}) ->    {Key1,C1, V1, {Key,C, V, ST,ST1}, BT1}.%% LL 当树中节点X的左孩子的左孩子上插入新元素,且平衡因子从1变成2后,就需要绕节点X进行右旋转。%% S1={100,1,{85,2,{60,3,nil,{80,4,nil,nil}},{90,5,nil,nil}},{120,6,nil,nil}}.avl_tree_balance_right_turn({Key, C, V, nil, BT})->    {Key, C, V, nil, BT};avl_tree_balance_right_turn({Key, C, V, {Key1, C1, V1, ST1, BT1},BT }) ->    {Key1, C1, V1, ST1,{Key, C, V, BT1,BT}}.%%向上重构树insert2_1(HNodeTrack)->    insert_2_2(HNodeTrack,nil).insert_2_2([],NewTree)->    NewTree;insert_2_2([{{K,C,V,ST,BT},2}|TL], nil) ->    insert_2_2(TL,{K,C,V,ST,BT});insert_2_2([{{K,C,V,_ST,BT},0}|TL], NewTree) ->    insert_2_2(TL,{K,C,V,NewTree,BT});insert_2_2([{{K,C,V,ST,_BT},1}|TL], NewTree) ->    insert_2_2(TL,{K,C,V,ST,NewTree}).lookup(Key, {_, T}) ->    lookup_1(Key, T).lookup_1(Key, {Key1,_, _, Smaller, _}) when Key < Key1 ->    lookup_1(Key, Smaller);lookup_1(Key, {Key1,_, _, _, Bigger}) when Key > Key1 ->    lookup_1(Key, Bigger);lookup_1(_, {_,_, Value, _, _}) ->    {value, Value};lookup_1(_, nil) ->    none.get(Key, {_, T}) ->    get_1(Key, T).get_1(Key, {Key1,_, _, Smaller, _}) when Key < Key1 ->    get_1(Key, Smaller);get_1(Key, {Key1,_, _, _, Bigger}) when Key > Key1 ->    get_1(Key, Bigger);get_1(_, {_,_, Value, _, _}) ->    Value.

红黑树的优势

红黑树能够以O(log2(N))的时间复杂度进行搜索、插入、删除操作。此外,任何不平衡都会在3次旋转之内解决。这一点是AVL所不具备的。

0 0
原创粉丝点击