《算法导论》实验六:红黑树插入算法(C++)——控制台树型显示

一、问题描述

我们知道一颗高度为h的二叉搜索树,可以支持任何一种基本动态集合操作,且其时间复杂度均为O(h)。因此,二叉搜索树的性能与树的高度密切相关,如果树的高度较高时,这些集合操作可能并不比在链表上执行得快。所以让树中的元素尽量地平衡在树的两侧,使得树的高度尽量地低,便可提高二叉搜索树的性能。而红黑树(red-black- tree)是许多“平衡的”查找树中的一种,可以保证在最坏情况下基本动态集合操作的时间复杂度为O(lgn)。
因此,为了加强对红黑树的性质及操作的了解,本实验通过编码来实现红黑树的一系列操作(主要是红黑树的插入算法)。

二、算法分析与设计

(一)红黑树的性质如下:
1、每个结点或是红的,或是黑的。
2、根结点是黑的。
3、每个叶结点(NIL)是黑的。
4、如果一个结点是红的,则它的两个儿子都是黑的。
5、对每个结点,从该结点到其子孙的所有路径上包含相同数目的黑结点。

(二)为了实现红黑树插入算法,必须对红黑树的性质有透彻的了解。因为在不断的向红黑树的插入过程中,为了满足“平衡”的优良性质,可能会破坏红黑树的5条性质的某几条,而此时就需要根据5条性质的要求对插入结点及其“附近”结点进行调整。
算法步骤(如下图-1所示):
Step1:将带插入节点z按BST树规则插入红黑树中,z是叶子节点;
Step2:将z节点涂红;
Step3:调整使其满足红黑树的性质(关键步骤)。
《《算法导论》实验六:红黑树插入算法(C++)——控制台树型显示》
《《算法导论》实验六:红黑树插入算法(C++)——控制台树型显示》
图-1 整个插入算法RBInsert()

(三)调整
红黑树的插入代码与二叉查找树的插入代码大致相同,区别在于最后z的左右孩子都设为哨兵元素(黑色地NIL),而且z的颜色属性设为红色。新元素插入可能会破坏红黑性质,所以我们要做额外的操作RBInsertFixUp来保持。
Z插入后违反情况:如果它是根结点,则它破坏了性质2:根必须是黑色的。如果它的父亲是红色的,则它破坏了性质4:红结点必须有两个黑色的孩子。对于性质1、3、5,它并不会破坏:它是红色的,满足性质1;它的两个孩子是黑色的T.nil,满足性质3;它是红色的,不会增加黑 高度,满足性质5。
调整步骤
A、若z为根,将其涂黑;
B、若z为非根,则p[z]存在:
①若p[z]为黑,无需调整
②若p[z]为红,违反性质4,则需调整,且分六种情况进行调整,即父结点是左孩子的三种情况加上父结点是右孩子的三种情况。但后三种情况与前三种情况对称,所以不再赘述,仅以前三种为例:case1:z的叔叔y是红色;case2,:z的叔叔y是黑色,且z是双亲的右孩子;case3:z的叔叔y是黑色,且z是双亲的左孩子。
调整算法如下图-2:
《《算法导论》实验六:红黑树插入算法(C++)——控制台树型显示》
图-2 调整算法RBInsertFixUp(),case4~case6在此略去

三、实验结果与分析

本次实验中,为了能较为全面的覆盖红黑树插入时出现的调整状态,即测试用例能体现插入后不满足性质并且case1、case2和case3的情况都包含进,实验者选择了算法导论(第3版)中的题13.3-2为测试用例来进行试验。
问题13.3-2:将关键字41、38、31、12、19、8连续地插入一颗出初始为空的红黑树后,试画出该结果树?
刚开始程序运行时出现图-3画面,可自由选择实验者想要执行的操作:输入1则表示要插入结点;输入2表示展示红黑树当前状态;输入0表示退出控制台。
因为刚开始红黑树是颗空树,所以选择1进行插入41
《《算法导论》实验六:红黑树插入算法(C++)——控制台树型显示》
图-3 对接下来的操作进行选择

选择1并输入要插入的数41,见下图-4:
《《算法导论》实验六:红黑树插入算法(C++)——控制台树型显示》
图-4 选择1并输入要插入的数41

同理,继续插入38、31,输入操作标记2可树形显示红黑树当前状态:插入31时出现case3,调整颜色并进行一次右旋。见下图-5
《《算法导论》实验六:红黑树插入算法(C++)——控制台树型显示》
图-5 插入38、31,并树形显示

同理,继续插入12、19,树形显示红黑树当前状态:插入12时出现case1,仅需调整颜色,并且递归向上调整;插入19时出现case2,先左旋,调整颜色后再右旋。见下图-6
《《算法导论》实验六:红黑树插入算法(C++)——控制台树型显示》
图-6 插入12、19,并树形显示

最后,插入8,树形显示红黑树当前状态:出现case1,仅需调整颜色,并且递归向上调整。
《《算法导论》实验六:红黑树插入算法(C++)——控制台树型显示》
图-7 插入8,树状显示最终结果

四、实验总结

1、 红黑树的是一个“平衡性”很好的二叉搜索树,其基本动态集合操作的时间复杂度为O(lgn),但在插入时要不断调整以满足红黑树的5条性质。
2、 红黑树插入的过程可能会违反性质2和性质4:当z是根时违反性质2;当z的父母结点是红时违反性质4,因此要做调用函数RBInsertFixUp()进行相应调整。
3、 在时间复杂度方面:调整算法的时间复杂度为O(logn);整个插入算法的时间复杂度也为O(logn);此外,调整算法中至多使用2次旋转。

五、源代码(C++)

/* 性质1: 每个结点是红色或黑色 性质2: 根结点点是黑色 性质3: 每个叶节点(NIL)是黑的 性质4: 如果一个结点是红的,则它的两个儿子都是黑色 性质5: 对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点 */  

#include <iostream>
#include <deque>
#include <iomanip>
#include <sstream>
#define SENTINEL -100000     //哨兵,作为nil结点的key,方便树状输出时临界点判断
using namespace std;


enum Color{RED=0,BLACK=1};   //定义枚举类型,即红黑树结点颜色类型,0表示红色,1表示黑色

typedef struct Node          //声明红黑树结点
{
    Color color;             //红黑树结点的颜色类型,
    struct Node *parent;     //父节点
    struct Node *left;       //左孩子
    struct Node *right;      //右孩子
    int key;                 //结点键值
}Node;

typedef struct RBTree        //定义一个红黑树
{
    Node *root;              //根节点
    Node *nil;               //哨兵结点,避免讨论结点的边界情况
}RBTree;

void Display()               //选择显示
{
    cout<<"*********************************************************\n";
    cout<<"**** 请选择您要的红黑树操作!! ****\n";
    cout<<"**** 1:插入结点 2:红黑树当前状态 0:退出 ****\n";
    cout<<"**** ****\n";
    cout<<"*********************************************************\n";
}

//左旋,结点x原来的右子树y旋转成x的父母
void LeftRotate(RBTree * rbTree,Node *x)
{
    if(x->right!=rbTree->nil)
    {
        Node *y=x->right;
        x->right=y->left;
        if(y->left!=rbTree->nil)
        {
            y->left->parent=x;
        }
        y->parent=x->parent;
        if(x->parent==rbTree->nil)    //空树,将y设为根
        {
            rbTree->root=y;
        }
        else if(x==x->parent->left)   //x为左子树,将y放在x父节点的左子树
        {
            x->parent->left=y;
        }
        else
        {
            x->parent->right=y;
        }

        y->left=x;
        x->parent=y;
    }
    else
    {
        cout<<"Error: can't left rotate,because no rigth child!"<<endl;
    }
}

//右旋,结点x原来的左子树y旋转成x的父母
void RightRotate(RBTree * rbTree,Node *x)
{
    if(x->left!=rbTree->nil)
    {
        Node *y=x->left;
    x->left=y->right;
    if(y->right!=rbTree->nil)
    {
        y->right->parent=x;
    }

    y->parent=x->parent;
    if(x->parent==rbTree->nil)   
    {
        rbTree->root=y;
    }
    else if(x==x->parent->left)  
    {
        x->parent->left=y;
    }
    else
    {
        x->parent->right=y;
    }

    y->right=x;
    x->parent=y;
    }
    else
    {
        cout<<"Error: can't right rotate,because no left child!"<<endl;
    }


}

//插入结点
void RBInsert(RBTree *rbTree,int key)
{
    void RBInsertFixUp(RBTree *rbTree,Node *z); 
    if(rbTree->root==NULL)
    {//当根为空时,单独处理,直接插入到根结点中
        rbTree->root=new Node;
        rbTree->nil=new Node;

        rbTree->root->left=rbTree->nil;
        rbTree->root->right=rbTree->nil;
        rbTree->root->parent=rbTree->nil;
        rbTree->root->key=key;

        rbTree->root->color=BLACK;    //根节点color设为黑


        rbTree->nil->parent=rbTree->root;
        rbTree->nil->left=rbTree->root;
        rbTree->nil->right=rbTree->root;
        rbTree->nil->key=SENTINEL;
        rbTree->nil->color=BLACK;     //nil结color也设为黑
    }
    else
    {//如果树不为空,那么从根节点开始,从上往下查找插入点
        Node *y=rbTree->nil;         //y用于当前扫描结点x的父节点
        Node *x=rbTree->root;        //从根节点开始扫描
        while(x!=rbTree->nil)        //查找插入位置
        {
            if(key==x->key)
            {
                cout<<"键值重复,请输入不同的键值!!"<<endl;
                return;
            }
            y=x;
            x=key<x->key ? x->left : x->right;  
        }
        Node *z=new Node;       //new一个Node结点空间
        z->color=RED;           //新插入的color设为红色
        z->key=key;
        z->left=z->right=rbTree->nil;    
        z->parent=y;
        if(key<y->key)
            y->left=z;
        else
            y->right=z;

        RBInsertFixUp(rbTree,z);     //插入后对树进行调整
    }
}

void RBInsertFixUp(RBTree *rbTree,Node *z)   //插入后调整树,以维持红黑树的5条性质
{
    Node *y;     //用于记录z的叔叔结点
    while(z->parent->color==RED)     //因为插入的结点是红色的,所以只可能违背性质4,即假如父结点也是红色的,要做调整 
    {
        if(z->parent->parent->left==z->parent)    //如果要插入的结点z是其父结点的左子树
        {
            y=z->parent->parent->right;           // y设置为z的叔父结点
            if(y->color==RED)                     //case 1: y的颜色为红色
            {
                z->parent->parent->color=RED;
                y->color=BLACK;
                z->parent->color=BLACK;
                z=z->parent->parent;
            }
            else
            {
                if(z==z->parent->right)           //case 2: y的颜色为黑色,并且z是z的父母的右结点,则z左旋转
                {
                    z=z->parent;
                    LeftRotate(rbTree,z);
                }
                z->parent->parent->color=RED;     //case 3: 如果y的颜色为黑色,并且z是z的父母的左结点
                z->parent->color=BLACK;
                RightRotate(rbTree,z->parent->parent);
            }
        }
        else     //与前一种情况对称,要插入的结点z是其父结点的右子树,注释略去 
        {
            y=z->parent->parent->left;
            if(y->color==RED)
            {
                z->parent->parent->color=RED;
                y->color=BLACK;
                z->parent->color=BLACK;
                z=z->parent->parent;
            }
            else
            {
                if(z->parent->left==z)
                {
                    z=z->parent;
                    RightRotate(rbTree,z);
                }
                z->parent->parent->color=RED;
                z->parent->color=BLACK;
                LeftRotate(rbTree,z->parent->parent);
            }
        }
    }
    rbTree->root->color=BLACK;          //最后如果上升为rbTree的根的话,把根的颜色设为黑色
}

//求树高
int MaxHeight(Node * root,Node *nil)
{
    if(root==nil)return 0;
    int leftHeight=MaxHeight(root->left,nil);
    int rightHeight=MaxHeight(root->right,nil);
    return (leftHeight>rightHeight)?leftHeight+1:rightHeight+1;
}

// convert an integer value to string
string IntToString(int val) 
{
  ostringstream ss;
  ss << val;
  return ss.str();
}

// Print the arm branches (eg, / \ ) on a line
void PrintBranches(int branchLen, int nodeSpaceLen, int startLen, int nodesInThisLevel, const deque<Node*>& nodesQueue) 
{
  deque<Node*>::const_iterator iter = nodesQueue.begin();
  for (int i = 0; i < nodesInThisLevel / 2; i++) 
  {
    cout << ((i == 0) ? setw(startLen-1) : setw(nodeSpaceLen-2)) << "" << ((*iter&&(*iter)->key!=SENTINEL) ? "/" : " ");
    *iter++;
    cout << setw(2*branchLen+5) << "" << ((*iter&&(*iter)->key!=SENTINEL) ? "\\" : " ");
    *iter++;
  }
  cout << endl;
}

// Print the branches and node (eg, ___10___ )
void PrintNodes(int branchLen, int nodeSpaceLen, int startLen, int nodesInThisLevel, const deque<Node*>& nodesQueue)
{
  deque<Node*>::const_iterator iter = nodesQueue.begin();
  for (int i = 0; i < nodesInThisLevel; i++, iter++)
  {
    cout << ((i == 0) ? setw(startLen) : setw(nodeSpaceLen)) << "" << ((*iter && (*iter)->key!=SENTINEL && (*iter)->left->key!=SENTINEL ) ? setfill('_') : setfill(' '));
    if(*iter&&(*iter)->key!=SENTINEL)
    {
        cout<<setw(branchLen+2)<<IntToString((*iter)->key);
        cout<<(((*iter)->color==RED) ? "红":"黑");
    }
    else
    {
        cout << setw(branchLen+1)<<"";
    }
    cout << ((*iter && (*iter)->key!=SENTINEL && (*iter)->right->key!=SENTINEL ) ? setfill('_') : setfill(' ')) << setw(branchLen+1) << "" << setfill(' ');
  }
  cout << endl;
}

// Print the leaves only (just for the bottom row)
void PrintLeaves(int indentSpace, int level, int nodesInThisLevel, const deque<Node*>& nodesQueue) 
{
  deque<Node*>::const_iterator iter = nodesQueue.begin();
  for (int i = 0; i < nodesInThisLevel; i++, iter++)
  {
    cout << ((i == 0) ? setw(indentSpace+2) : setw(2*level+2));
    if(*iter&&(*iter)->key!=SENTINEL)
    {
        cout<<IntToString((*iter)->key);
        cout<<(((*iter)->color==RED) ? "红":"黑");
    }
  }
  cout << endl;
}

// Pretty formatting of a binary tree to the output stream
// @ param
// level Control how wide you want the tree to sparse (eg, level 1 has the minimum space between nodes, while level 2 has a larger space between nodes)
// indentSpace Change this to add some indent space to the left (eg, indentSpace of 0 means the lowest level of the left node will stick to the left margin)
void PrintPretty(RBTree * tree, int level, int indentSpace) 
{//在控制台树形输出红黑树
    int h = MaxHeight(tree->root,tree->nil);
    int nodesInThisLevel = 1;

    int branchLen = 2*((int)pow(2.0,h)-1) - (3-level)*(int)pow(2.0,h-1);  // eq(=) of the length of branch for each node of each level
    int nodeSpaceLen = 2 + (level+1)*(int)pow(2.0,h);  // distance between left neighbor node's right arm and right neighbor node's left arm
    int startLen = branchLen + (3-level) + indentSpace;  // starting space to the first node to print of each level (for the left most node of each level only)

    deque<Node*> nodesQueue;
    nodesQueue.push_back(tree->root);
    for (int r = 1; r <= h; r++) {
        PrintBranches(branchLen, nodeSpaceLen, startLen, nodesInThisLevel, nodesQueue);
        branchLen = branchLen/2 - 1;
        nodeSpaceLen = nodeSpaceLen/2 + 1;
        startLen = branchLen + (3-level) + indentSpace;
        PrintNodes(branchLen, nodeSpaceLen, startLen, nodesInThisLevel, nodesQueue);

        for (int i = 0; i < nodesInThisLevel; i++) {
            Node *currNode = nodesQueue.front();
            nodesQueue.pop_front();
            if (currNode&&currNode!=tree->nil) {
                nodesQueue.push_back(currNode->left);
                nodesQueue.push_back(currNode->right);
            } else {
                nodesQueue.push_back(NULL);
                nodesQueue.push_back(NULL);
            }
        }
        nodesInThisLevel *= 2;
    }
}

int main()
{
    RBTree tree;
    tree.root=tree.nil=NULL;
    int choose,value;
    while(true)
    {
        Display();
        cin>>choose;
        switch(choose)
        {
            case 0:exit(0);break;   //选择0则跳出系统
            case 1:     
                {//选择1则插入结点
                    cout<<"请输入要插入的数:";
                    cin>>value;
                    RBInsert(&tree,value);
                    cout<<endl;
                    break;
                }

            case 2:   
                {//选择2则在控制台以树的形式显示红黑树
                    cout<<endl<<"此时红黑树状态如下:"<<endl;
                    PrintPretty(&tree,2,3);
                    break;
                }
        }       
    }
}
    原文作者:算法小白
    原文地址: https://blog.csdn.net/to_baidu/article/details/50316559
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞