数据结构拾遗(1) --红黑树的设计与实现(上)

红黑树是一种自平衡的二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组(C++ STL 中的map/set)。它是在1972年由Rudolf Bayer发明的,他称之为"对称二叉B树",它现代的名字是在 Leo J. Guibas 和 Robert Sedgewick 于1978年写的一篇论文中获得的。红黑树虽然很复杂,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目(来源:百度百科)。

算法导论对R-B Tree的介绍:

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

由于红黑树也是一颗二叉查找树, 因此红黑树也满足二叉查找树的一般性质(关于二叉查找树的一般性质请参考博客:http://blog.csdn.net/zjf280441589/article/details/42611161)

但由于普通的二叉查找树不具备自动平衡的功能, 因此普通的二叉查找树树很有可能会退化成为一条链表, 若二叉树退化成了一棵具有n个结点的线性链后,则插入/删除/查找操作最坏情况运行时间就变为了O(n)。

因此就有了红黑树, 他的最终查找、插入、删除的时间复杂度最坏情况下依然为O(logn), 而红黑树之所以这么牛的原因就是它在二叉查找树的基础上增加了着色和相关的性质, 这就是红黑规则:

红黑规则

1.每一个节点不是红色的就是黑色的;

2.根总是黑色的;

3.如果节点是红色的, 则它的子节点必须是黑色的;

4.从根到叶节点的每条路径, 必须包含相同数目的黑色节点(黑高度相同);

5.每个叶结点(叶结点即指树尾端NIL指针或NULL结点)是黑的;

根据规则4, 新增节点必须为红;

根据规则3, 新增节点之父节点必须为黑.

当新节点根据二叉树的规则到达插入点, 却未能符合上述红黑规则时, 就必须调整颜色并旋转树形;

红黑结点

template <typename Type>
class RedBlackNode
{
    friend RedBlackTree<Type>;
private:
    RedBlackNode(const Type &_element = Type(),
                 RedBlackNode *_left = NULL,
                 RedBlackNode *_right = NULL,
                 int _color = RedBlackTree<Type>::BLACK)
        : element(_element), left(_left), right(_right), color(_color) {}

private:
//为了在红黑树尚未完成的时候进行测试
//故将之作为public, 待红黑树完成之时,
//就是将其改为private之时;
public:
    Type            element;   //节点值
    RedBlackNode    *left;
    RedBlackNode    *right;
    //红黑树的颜色
    int             color;
};

红黑树

template <typename Type>
class RedBlackTree
{
public:
    //为结点定义别名
    typedef RedBlackNode<Type> Node;

    enum {RED, BLACK};

public:
    //此时红黑树所支持的操作尚不完善,
    //而且函数功能(如析构函数, insert操作)
    //实现的也并不理想,
    //但随着红黑树代码的不断演进,
    //这些代码会不断的完善
    RedBlackTree(const Type &negInf);
    ~RedBlackTree();

    void insert(const Type &data);

private:
//为了在红黑树尚未完成的时候进行测试
//故将之作为public, 待红黑树完成之时,
//就是将其改为private之时;
public:
    //指向红黑树的头(伪根节点)
    //RedBlackNode<Type> *header;
    Node *header;
    //空节点
    Node *nullNode;

    //在插入过程中需要的指针
    Node *current;  //当前节点
    Node *parent;   //父节点
    Node *grand;    //祖父节点(爷爷)
    Node *great;    //曾祖父节点(爷爷的爸爸)

    /* 单旋转 */
    //带着左孩子旋转: 向右旋转
    void rotateWithLeftChild(Node *& k2) const;
    //带着有孩子旋转: 向左旋转
    void rotateWithRightChild(Node *& k1) const;

    /* 双旋转 */
    //向右转
    void doubleRotateWithLeftChild(Node *& k3) const;
    //向左转
    void doubleRotateWithRightChild(Node *& k1) const;
};

构造与析构

//构造函数
template <typename Type>
RedBlackTree<Type>::RedBlackTree(const Type &negInf)
{
    nullNode = new Node();
    header = new Node(negInf, nullNode, nullNode);
}
//这一版的析构函数实现并不完善,在后续的版本我们会继续完善该析构函数
template <typename Type>
RedBlackTree<Type>::~RedBlackTree()
{
    delete nullNode;
    delete header;
}

一个二叉查找树的insert

//这时的insert其实就是一个普通的
//二叉查找树的insert, 完全没要照顾到
//二叉树的平衡, 以及红黑规则的实现
template <typename Type>
void RedBlackTree<Type>::insert(const Type &data)
{
    great = grand = parent = current = header;
    //在此处令nullNode成为data, 以作哨兵
    nullNode->element = data;

    while (current->element != data)
    {
        //让祖父成为曾祖父, 父亲成为祖父, 自己成为父亲
        //每个人都长了一辈
        great = grand;
        grand = parent;
        parent = current;

        current = (data < current->element) ? current->left
                  : current->right;
    }

    //如果树中包含相同的元素
    if (current != nullNode)
        throw DuplicateItemException();

    current = new Node(data, nullNode, nullNode);
    if (data < parent->element)
        parent->left = current;
    else
        parent->right = current;
    //在后续的版本上,需要加上自动平衡(即实现红黑规则) -> 红黑树
}

单旋转

注意:内侧孙子节点横向移动(注意下图结点37);

左(单)旋:

当在某个结点k1上,做左旋操作时,我们假设它的右孩子k2不是NIL[T](k1可以是任何不是NIL[T]的左孩子结点); 左旋以k1到k2之间的链为“支轴”进行,它使k2成为该子树新的根,而k2的左孩子B则成为k1的右孩子。

//实现
//向左转
template <typename Type>
void RedBlackTree<Type>::rotateWithRightChild(Node *& k1) const
{
    Node *k2 = k1->right;
    //结点B横向移动
    k1->right = k2->left;

    //令k2提领k1
    k2->left = k1;
    //令k2为根(使k2替代k1的位置)
    k1 = k2;
}

右(单)旋:

过程与左旋类似;

//实现
//向右转
template <typename Type>
void RedBlackTree<Type>::rotateWithLeftChild(Node *& k2) const
{
    //首先将B横向移动
    Node *k1 = k2->left;
    k2->left = k1->right;

    //令k1提领k2
    k1->right = k2;
    //令k1为根(使k1替代k2的位置)
    k2 = k1;
}

测试(在完成单旋转之后):

(构造一颗二叉查找树树如图所示, 左边为尚未旋转之前, 右为旋转之后)

//测试代码如下:
int main()
{
    //用NEG_INF来代表负无穷大
    const int NEG_INF = -999999;
    RedBlackTree<int> tree(NEG_INF);

    //单旋转时候的测试数据
    tree.insert(30);
    tree.insert(15);
    tree.insert(70);
    tree.insert(20);

    cout << tree.header->right->element << endl;
    cout << tree.header->right->left->element << endl;
    cout << tree.header->right->right->element << endl;
    cout << tree.header->right->left->right->element << endl;   //20

    //向右旋转
    cout << "向右旋转" << endl;
    tree.rotateWithLeftChild(tree.header->right);
    cout << tree.header->right->element << endl;    //15
    cout << tree.header->right->right->element << endl; //30
    cout << tree.header->right->right->left->element << endl;   //20
    cout << tree.header->right->right->right->element << endl;   //70

    //然后再向左转(又转了回来)
    cout << "向左旋转" << endl;
    tree.rotateWithRightChild(tree.header->right);
    cout << tree.header->right->element << endl;
    cout << tree.header->right->left->element << endl;
    cout << tree.header->right->right->element << endl;
    cout << tree.header->right->left->right->element << endl;   //20
}

双旋转

单旋转有时会出现一个问题(如下图所示):

(如果内侧子孙节点[k1]过深, 则将其单向移动是不会解决问题的)

于是就有了双旋转

向右双旋转:

1.首先以k1为轴, k1与k2向左旋转;

2.然后以k3为轴, k3与旋转之后的k1向右旋转;

//实现
//向右双旋转
template <typename Type>
void RedBlackTree<Type>::doubleRotateWithLeftChild(Node *& k3) const
{
    //首先将其左儿子(k1)向左单旋转
    rotateWithRightChild(k3->left);

    //然后将自己(k3)向右单旋转
    rotateWithLeftChild(k3);
}

向左双旋转:

1.首先以k3为轴, k2与k3向右旋转;

2.然后以k1为轴, k1与旋转之后的k2向左旋转;

//实现
//向左双旋转
template <typename Type>
void RedBlackTree<Type>::doubleRotateWithRightChild(Node *& k1) const
{
    //首先将其右儿子(k2)向右单旋转
    rotateWithLeftChild(k1->right);

    //然后将自己(k1)向左单旋转
    rotateWithRightChild(k1);
}
//注:其实红黑树中并没有用到双旋转, 而是自己实现了一个rotate操作,
//在此只为了学习双旋转的理论;

测试(在完成双旋转之后):

(构造一颗二叉查找树树如图所示, 左边为尚未旋转之前, 右为旋转之后, 以8为轴进行双旋转)

int main()
{
    //用NEG_INF来代表负无穷大
    const int NEG_INF = -999999;
    RedBlackTree<int> tree(NEG_INF);

//双旋转时的测试数据
    tree.insert(12);
    tree.insert(16);
    tree.insert(8);
    tree.insert(10);
    tree.insert(4);
    tree.insert(14);
    tree.insert(2);
    tree.insert(6);
    tree.insert(5);
    cout << tree.header->right->element << endl;    //12
    cout << tree.header->right->left->element << endl;  //8
    cout << tree.header->right->right->element << endl; //16

    cout << tree.header->right->left->left->element << endl;    //4
    cout << tree.header->right->left->right->element << endl;   //10
    cout << tree.header->right->right->left->element << endl;   //14
//  cout << tree.header->right->left->right->left->element << endl;   //5曾经做过哨兵
//  cout << tree.header->right->left->right->right->element << endl;  //5

    cout << tree.header->right->left->left->left->element << endl;  //2
    cout << tree.header->right->left->left->right->element << endl; //6

    cout << tree.header->right->left->left->right->left->element << endl;   //5

    cout << "\n向右双旋转" << endl;
    //以8为基准向右双旋转
    tree.doubleRotateWithLeftChild(tree.header->right->left);
    cout << tree.header->right->element << endl;    //12
    cout << tree.header->right->left->element << endl;  //6
    cout << tree.header->right->right->element << endl; //16

    cout << tree.header->right->left->left->element << endl;    //4
    cout << tree.header->right->left->right->element << endl;   //8
    cout << tree.header->right->right->left->element << endl; //14

    cout << tree.header->right->left->left->left->element << endl;  //2
    cout << tree.header->right->left->left->right->element << endl;  //5
    cout << tree.header->right->left->right->right->element << endl;    //10
}

实现红黑树用到的异常

class DSException
{
public:
    DSException(const string &_message = string())
        : message(_message) {}
    ~DSException() {}

    virtual string what() const
    {
        return message;
    }
    virtual string toString() const
    {
        return "Exception: " + what();
    }

private:
    std::string message;
};

class DuplicateItemException : public DSException
{
public:
    DuplicateItemException(const string &_msg = string())
        : DSException(_msg) {}
};
时间: 2024-08-01 14:59:28

数据结构拾遗(1) --红黑树的设计与实现(上)的相关文章

数据结构拾遗(3) --红黑树的设计与实现(下)

完整源代码: http://download.csdn.net/detail/hanqing280441589/8450041 红黑节点设计与实现 template <typename Comparable> class RedBlackNode { friend class RedBlackTree<Comparable>; //所有的成员都是private private: RedBlackNode(const Comparable &theElement = Comp

数据结构拾遗(2) --红黑树的设计与实现(中)

Insert完善 根据规则4, 新增节点必须为红; 根据规则3, 新增节点之父节点必须为黑. 示例: (1)插入16(红色)/55(红色), 则既不用旋转, 也不用重新染色 (2)插入82(红色), 则违反了红黑规则, 需要进行动态的调整; 红黑树所需的处理 1.单旋转 新插入的X与其父P都是红色的, 而且X还是G的外部孙子; 2.双旋转 新插入的X与其父P都是红色的, 而且X还是G的内部孙子; 3.还需要处理有两个红色孩子的节点, 将右儿子变成黑色的, 我们只允许左儿子是红色的;方法: 旋转+

数据结构基础(18) --红黑树的设计与实现(1)

红黑树是一种自平衡的二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组(C++ STL 中的map/set).它是在1972年由Rudolf Bayer发明的,他称之为"对称二叉B树",它现代的名字是 Leo J. Guibas 和 Robert Sedgewick 于1978年写的一篇论文中获得的.红黑树虽然很复杂,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目(来源百度

数据结构基础(19) --红黑树的设计与实现(2)

双旋转 单旋转有时会出现一个问题(如下图所示): (如果内侧子孙节点[k1]过深, 则将其单向移动是不会解决问题的) 于是就有了双旋转 向右双旋转: 1.首先以k1为轴, k1与k2向左旋转; 2.然后以k3为轴, k3与旋转之后的k1向右旋转; //实现 //向右双旋转 template <typename Type> void RedBlackTree<Type>::doubleRotateWithLeftChild(Node *& k3) const { //首先将其

数据结构与算法-红黑树

前言 红黑树是工程中最常用到的一种自平衡二叉排序树,其和AVL树类似,都是在进行插入.删除时通过一定的调整操作来维持相对稳定的树高,从而获得较好的查询性能. 性质 1. 节点是红色或黑色. 2. 根节点是黑色. 3 每个叶节点(null节点)是黑色的. 4 每个红色节点的两个子节点都是黑色.(从每个叶子到根的所有路径上不能有两个连续的红色节点) 5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点. 维护红黑树形状(树高)的,主要就是4.5两条性质,性质4决定了最长的路径莫过于红黑间隔

【数据结构】平衡二叉树—红黑树

红黑树有什么特征,如何保持平衡的? 它或者是一颗空树,或者是具有一下性质的二叉查找树: 1.节点非红即黑. 2.根节点是黑色. 3.所有NULL节点称为叶子节点,且认为颜色为黑. 4.所有红节点的子节点都为黑色. 5.从任一节点到其叶子节点的所有路径上都包含相同数目的黑节点. 平衡分析: 稍微根据以上特征进一步扩展描述: 1.根据树的名称就可以知道,树的节点只有黑色和红色两种颜色,这也是为了保持平衡的一种限制方式(特征1). 2.树的头(根)跟尾(叶子节点)都是黑色的(特征2.3). 3.而红节

【数据结构05】红-黑树基础----二叉搜索树(Binary Search Tree)

目录 1.二分法引言 2.二叉搜索树定义 3.二叉搜索树的CRUD 4.二叉搜索树的两种极端情况 5.二叉搜索树总结 前言 在[算法04]树与二叉树中,已经介绍过了关于树的一些基本概念以及二叉树的前中后序遍历,而这篇文章将是在二叉树的基础上来展开讲解的二叉搜索树,也就是说二叉搜索树建立在树的基础之上.至于博主为何要花一整篇文章来讲这个二叉搜索树呢?原因很简单,红-黑树是基于二叉搜索树的,如果对二叉搜索树不了解,那还谈何红-黑树?红-黑树的重要性我想各位没吃过佩奇肉也肯定看过宜春跑....是的,j

数据结构--可迭代红黑树模板类

一.结点类与红黑树类: (一)结点类基本数据成员: 1.左右子结点指针域 2.父结点指针域,方便回访父结点 3.有序 前驱 / 后继 指针域,迭代访问元素,提供一种顺序存储的假象 4.结点颜色,利用红黑规则,保持树的平衡. (二)结点类的基本成员函数: 两个重载的构造函数,构造红色的新结点 (三)红黑树类基本数据成员: 1.头结点:保存一个据点,用来维系根节点与公用空结点 2.根结点:是红黑树的起始位置 3.空结点:作为所有空指针域的指向,一棵树只有一个 4.全局前驱.后继结点:用来使红黑树线索

红黑树详解 原理 史上最强 精华

未经授权,不得私自转载,否则追究法律责任 联系作者[email protected]取得授权 转载请注明作者和出处 网上很多红黑树的讲解都没有分析清楚插入删除各种情况是怎么来的,他们大多把分析图画的很复杂,其实这些情况都是极其简单的,我这里重点推导各种情况是怎么来的,不解释各种情况怎么调整,因为结构很简单调整很容易,且网上很多.红黑树的精髓是明白各种情况是怎么来的,弄清楚这个才是真正掌握红黑树. 五个性质: 1.节点要么红,要么黑 2.根节点为黑 3.NIL(叶节点)为黑,这条没什么卵用 4.红