红黑树的算法实现

来源:互联网 发布:想做淘宝客怎么申请 编辑:程序博客网 时间:2024/06/05 05:26

在搜索树的大家族里,红黑树算是用途最广泛的一个代表了。在我们使用的C++STL库中,set、map都是以它作为底层去实现的。当然在一些其他的地方,比如Java集合中的TreeSet和TreeMap,还有Linux虚拟内存的管理,也是通过红黑树去实现的。所以,今天我们来分析一下红黑树是怎样实现的。

红黑树是一棵二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色,可以是Red或Black。通过对任何一条从根到叶子简单 路径上的颜色来约束,红黑树保证最长路径不超过最短路径的两倍,因而近似于平衡。

所以我们就把满足下面性质的二叉搜索树称为红黑树:

1. 每个节点不是红色就是黑色
2. 根节点是黑色的
3. 如果有一个节点是红的,那么它的子节点一定是黑的(不存在连续的红节点)
4. 每条路径下存在相同数目的黑节点
5. 每个叶子(NIL)节点是黑色的(这是的叶子节点指的是为(NIL/NULL)的叶子结点)

1.插入

红黑树的插入情况是比较多的,最开始创建一个红黑树,它的根节点一定是黑,插入的第二个节点为了满足黑色节点数目相同的性质,肯定是红的,以此类推,当节点较多时我们可以将插入情况分为以下几类:

第一种:祖父节点是黑的,父亲和叔叔节点是红的,现在我们插入一个cur红节点(方框节点可存在,可不存在)

这里写图片描述

分析:这个时候,这棵红黑树已经不满足性质“不存在连续红节点”,所以这个时候需要对树进行调整。这种情况我们可以直接将父亲和叔叔节点的颜色进行改变为黑色,改变后又满足了红黑树的性质。

这里写图片描述

第二种:祖父节点是黑的,父亲节点是红的,叔叔节点不存在或者是黑色的,此时cur红节点(方框节点可存在,可不存在)

这里写图片描述

分析:同理,这种情况也不满足黑色节点数量相同这一性质。这个时候简单的改变颜色肯定不能解决问题,在这种情况下往往是因为cur子节点的调整,引起上面节点也需要调整。所以这个时候,我们需要对这棵树进行旋转。

这里写图片描述

第三种:祖父节点是黑的,父亲节点是红的,叔叔节点不存在或者是黑色的,此时cur红节点是父亲的右孩子(方框节点可存在,可不存在)

这里写图片描述

分析:现在对树做调整,可能情况会比较复杂,如果我们把cur节点变成父亲的左孩子,那剩下的问题就和第二种情况一样了。所以我们对父亲子树进行右旋即可。

这里写图片描述

    pair<Node*, bool> Insert(const K& key, const V& value)    {        if (_root == NULL)        {            _root = new Node(key, value);            _root->_color = BLACK;            return make_pair(_root, true);        }        Node* parent = NULL;        Node* cur = _root;        while (cur)        {            if (cur->_key < key)            {                parent = cur;                cur = cur->_right;            }            else if (cur->_key > key)            {                parent = cur;                cur = cur->_left;            }            else                return make_pair(cur,false);        }        cur = new Node(key, value);        if (parent->_key > key)            parent->_left = cur;        else            parent->_right = cur;        cur->_parent = parent;        while (parent && parent->_color == RED)        {            Node* grandfather = parent->_parent;      //1.判断父亲和叔叔的关系            if (parent == grandfather->_left)       //父亲在左,叔叔在右            {                Node* uncle = grandfather->_right;    //2.判断叔叔的情况                if (uncle != NULL && uncle->_color == RED)    //a.叔叔存在且为红 -> 变色                {                    parent->_color = BLACK;                    uncle->_color = BLACK;                    grandfather->_color = RED;                    cur = grandfather;                    parent = cur->_parent;                }                else   //b.叔叔不存在或者叔叔为黑                {                    if (cur == parent->_right)                      {                        RotateL(parent);                        swap(cur, parent);                    }                    RotateR(grandfather);                    parent->_color = BLACK;                    grandfather->_color = RED;                    break;                }            }            else       //叔叔在左,父亲在右            {                Node* uncle = grandfather->_left;                if (uncle && uncle->_color == RED)    //叔叔存在且为红 ,变色                {                    parent->_color = BLACK;                    uncle->_color = BLACK;                    grandfather->_color = RED;                    cur = grandfather;                    parent = cur->_parent;                }                else    //叔叔不存在或为黑                {                    if (cur == parent->_left)                    {                        RotateR(parent);                        swap(cur, parent);                    }                    RotateL(grandfather);                    parent->_color = BLACK;                    grandfather->_color = RED;                    break;                }            }        }        _root->_color = BLACK;        return make_pair(_root, true);    }

总结:关于红黑树的插入,我们是从插入的节点开始,依次向上调整,直到这棵树的节点符合红黑树的性质。

2.红黑树的左右旋

对于左右旋这个概念,其实在学习查找树这一个系列的时候并不陌生,很多时候树的插入问题,我们都会用到这个方法来使它达到一种平衡。
对红黑树也是如此。首先,我们借助一个动画来看看右旋是怎么个旋法。

这里写图片描述

根据这个动画再去实现,就比较容易了。代码实现如下。

void RotateR(Node* parent)    //右旋    {        assert(parent);        Node* child = parent->_left;        Node* grandfather = parent->_parent;        Node* Rchild = child->_right;        if (Rchild)            Rchild->_parent =parent ;        parent->_left = Rchild;        parent->_parent = child;        child->_right = parent;        if (grandfather == NULL)        {            child->_parent = NULL;            _root = child;        }        else        {            if (grandfather->_left == parent)                grandfather->_left = child;            else                grandfather->_right = child;            child->_parent = grandfather;        }    }

同理,左旋的实现也可以借助动画来理解。

这里写图片描述

void RotateL(Node* parent)   //左旋    {        assert(parent);        Node* child = parent->_right;        Node* Lchild = child->_left;        Node* grandfather = parent->_parent;        if (Lchild)            Lchild->_parent = parent;        parent->_right = Lchild;        parent->_parent = child;        child->_left = parent;        if (grandfather == NULL)        {            _root = child;            child->_parent = NULL;        }        else        {            if (parent == grandfather->_left)                grandfather->_left = child;            else                grandfather->_right = child;            child->_parent = grandfather;        }    }

总结:在查找树中,合理的使用左右旋会使整个问题化繁为简。

3.如何判断当前的红黑树是红黑树

在我们完成插入算法后,这个时候就有了一个新问题,我们如何判断一棵树是不是没有问题的红黑树?要解决这个问题,就得从红黑树的性质入手了。

1. 如果根节点是红的,肯定不是红黑树;
2. 如果有连续的红节点,肯定不是红黑树;
3. 如果黑色节点的路径长度不一样,肯定不是红黑树。

这三条性质就够我们去判断一个树是不是红黑树了。第一二条性质比好判断,如何实现第三个?这时候我们就需要去任意路径count一次黑色节点的路径长度,然后再用这个长度和每一条路径的长度进行比较,只要有一条路径不一样,那就可以直接得出这不是一颗红黑树了。直到判断到每一条路径的叶子节点后,它都相等,那么此时这个性质就满足了。

bool IsBalance()    {        if (_root == NULL) return true;        if (_root->_color == RED) return false;        int BlackNum = 0;    //统计一条路径的黑色节点数量        Node* left = _root;        while (left)        {            if (left->_color == BLACK)                BlackNum++;            left = left->_left;        }        int count = 0;        return _IsBalance(_root,BlackNum,count);    }bool _IsBalance(Node* root, const int BlackNum, int count)    {        if (root == NULL)  return true;        if (root->_color == RED && root->_parent->_color == RED)        {            cout << "存在连续红节点" << endl;            return false;        }        if (root->_color == BLACK)            ++count;        if (root->_left == NULL && root->_right == NULL)        {            if (count != BlackNum)            {                cout << "黑节点数目不同" << endl;                return false;            }            else                return true;        }        return _IsBalance(root->_left, BlackNum, count) && _IsBalance(root->_right, BlackNum, count);    }

其实,相对比其他查找树,红黑树的查找时间复杂度一直控制在 O(lgn),这就要求它一直处于近似平衡状态。也正是因为这一点,红黑树是一种非常高效的平衡查找树,所以在很多语言的内部都会或多或少使用它来作为底层实现。

1 0
原创粉丝点击