学习 《算法导论》第13章 红黑树 总结一

来源:互联网 发布:java数组排序从大到小 编辑:程序博客网 时间:2024/05/15 11:21

学习 《算法导论》第13章 红黑树 总结一

前面学习了二叉查找树,我们知道一棵高度为h的二叉查找树实现的任何一种动态集合操作,其时间都是O(h); 但若二叉查找树退化为n个结点的线性链后,则最坏情况运行时间为O(n); 下面说的红黑树能保证在最坏情况下,任何一种动态集合操作的运行时间为O(h);

红黑树介绍

红黑树是一种二叉查找树,但在每个结点上增加了一个存储位表示结点的颜色,可以是RED或BLACK;通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。
上面说了红黑树是一种二叉查找树,所以红黑树也满足二叉查找树的基本性质。

红黑树的五个基本性质

红黑树中每个结点包含五个域:key, left, right, parent, color;
一棵二叉查找树若满足下面的红黑性质,则是一棵红黑树:
1. 每个结点要么是红色的,要么是黑色的
2. 根结点一定是黑色的
3. 每个叶结点(这里的叶结点指的是:树尾端NULL指针或NULL结点)都是黑色的
4. 若一个结点是红色的,则它的两个儿子都是黑色的
5. 对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点.
而这五条性质就保证了红黑树在基本的动态集合操作的最坏情况时间为O(h).

注意:这里说的叶结点和之前说的叶结点不同,可以忽略这条,将注意力放在红黑树的内部结点。这里的叶结点并不包含数据,就是个NULL结点。
下面是一棵有叶结点(NULL结点)的红黑树图:

这里写图片描述

之后的学习我们不画出NULL结点。

结点黑高度:从某个结点x出发(不包括该结点)到达一个叶结点的任意一条路径上,黑色结点的个数。
由第五条性质,可以保证从该结点出发的所有下降路径都有相同的黑结点个数,所以可以定义红黑树的黑高度为其根结点的黑高度。

LINUX的内存管理里面用了红黑树,其定义路径在:
include/Linux/rbtree.h,有空可以去学习下。

红黑树结点的C定义

typedef struct RB_Node{    struct RB_Node* parent; // 指向父结点    struct RB_Node* left;    struct RB_Node* right;    ElemType data; // key data    int color; // 取值:0--RED, 1--BLACK}RN_Node, *RB_Tree;

树的旋转

在对红黑树进行查找和删除操作时,由于这两个操作对树作了修改,结果有可能违背了红黑树的性质,为保持这些性质,可以通过对结点进行重新着色,以及对树进行相关的旋转操作,即通过修改树中某些结点的颜色及指针结构,来达到对红黑树进行插入或删除结点等操作后继续保持它的性质或平衡的目的。
树的旋转包括两种情况:左旋和右旋

左旋

这里写图片描述

上图是将结点pivot进行左旋,它以pivot和它的右结点之间的链为“支轴”进行的,使结点Y称为该子树的新根结点,pivot成为它的左孩子,而Y的左孩子成为pivot的右孩子。
能进行左旋的结点有个要求:就是树中任意右孩子不是NULL结点的结点。如图中的C结点。

左旋的算法

LEFT-ROTATE(T,x)    y<--x.right // y是x的右孩子    x.right <-- y.left  // y的左孩子(或左子树)称为x的右孩子(或右子树)    if y.left != NULL        y.left.parent <-- x    y.parent <-- x.parent // y成为x的父及结点    if x.parent == NULL // x为根结点        T <-- y    else if x == x.parent.left        x.parent.left <-- y     else        x.parent.right <-- y    y.left = x // x为y的左孩子    x.parent = y;

左旋的C语言实现

// 左旋// 将结点Node进行左旋RB_Node* RB_Left_Rotate(RB_Tree RB_Root, RB_Node* Node){    RB_Node* rightnode = Node->right;    // rightnode的左子树为Node的右子树    Node->right = rightnode->left;    if (NULL != rightnode->left)    {         rightnode->left->parent = Node;    }    // rightnode的父结点指向Node的父结点指针    rightnode->parent = Node->parent;    // Node 为根结点    if(NULL == Node->parent)    {        RB_Root = rightnode;    }    else if (Node == Node->parent->left) // 确定rightnode是放在左边还是右边    {        Node->parent->left = rightnode;    }    else    {        Node->parent->right = rightnode;    }    rightnode->left = Node;    Node->parent = rightnode;    return RB_Root;}

右旋

这里写图片描述

右旋过程是对称的,算法不写了。直接写下右旋的C语言实现吧:

右旋的C语言实现

// 右旋// 将Node结点进行右旋// 代码可以参考上面的图进行剖析RB_Node RB_Right_Rotate(RB_Tree RB_Root, RB_Node* Node){    RB_Node leftnode = Node->left;    // leftnode的右子树为Node的左子树    Node->left = leftnode->right;    if (NULL != leftnode->right)    {         leftnode->left->parent = Node;    }    // leftnode的父结点指向Node的父结点指针    leftnode->parent = Node->parent;    // Node 为根结点    if(NULL == Node->parent)    {        RB_Root = leftnode;    }    else if (Node == Node->parent->left) // 确定leftnode是放在左边还是右边    {        Node->parent->left = leftnode;    }    else    {        Node->parent->right = leftnode;    }    leftnode->right = Node;    Node->parent = leftnode;    return RB_Root;}   

左旋和右旋都是在O(1)时间内执行的,在旋转的时候,只有指针被改变了,而结点中所有其他的域都没有改变。

树在经过左旋右旋之后,树的搜索性质保持不变,但树的红黑性质则被破坏了,所以,红黑树插入和删除数据后,需要利用旋转与颜色重涂来重新恢复树的红黑性质。

0 0
原创粉丝点击