您的位置:首页 > 其它

erl_tree RBT 红黑树

2015-10-30 15:49 204 查看
文字说明大部分转载: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)

红黑树的树高(h)不大于两倍的红黑树的黑深度(bd),即h<=2bd

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

一棵拥有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所不具备的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: