《算法导论》实验七:区间树上的重叠区间查找算法(C++)——控制台树型显示

一、题目描述

区间树(interval tree)是一种对动态集合进行维护的扩张红黑树,因此可在实验二红黑树的基础上进行扩张。为此,本实验(实验三)在实验二的基础上对红黑树的节点增加新的附加信息,并设计新的操作。从而熟悉并实现区间树上的重叠区间查找算法,分析该算法的性能。
具体问题测试用例:尝试构造一个区间树。可依次插入下列区间:[41,49] [38,44] [31,35] [12,20] [19,25] [8,10]
查找重叠区间:“find [37,40]”、“find[9,30]”、“find [26,29]”

二、算法分析与设计

(一)基本概念:
区间:一个事件占用的时间;
闭区间:实数的有序对[low,high],使low≤high
区间的对象表示:[low,high]可以用对象interval表示,有两个属性:
low[interval]=t1 //起点或低点;
high[interval]=t2 //终点或高点。
区间的重叠:interval∩interval’≠∅⇔(low[interval]≤high[interval’]) and (low[interval’]≤high[interval])

(二)数据结构:本质上是将红黑树扩充,方法如下:
Step 1:基本结构:
以红黑树为基础,对∀x∈T,x包含区间interval[x]的信息(低点和高点),key=low[interval [x]]。
Step 2:附加信息:
max[x]=max(high[interval [x]], max[left[x]], max[right[x]])
Step 3:维护附加信息(有效性):
在新节点的插入时进行Step2的计算,此外要注意在左旋和右旋时也要对max值进行维护,这是与基本红黑树的左右旋很大不同的地方。
Step 4:开发新操作:查找与给定区间重叠的区间节点x。

(三)查找算法IntervalSearch(T, i)基本思想:
step 1:x ←root[T]; //从根开始查找
step 2:若x≠nil[T]且i与int[x]不重叠
if x的左子树非空且左子树中最大高点≥low[i] then
x ←left[x];//到x的左子树中继续查找
else
x ←right[x];//左子树必查不到,到右子树查
step 3:返回x //x=nil or i和x重叠
由于区间树是红黑树的简单扩重,因此区间树相关操作的实现如左旋、右旋、插入,插入调整等与红黑树基本相同,这些在实验二有详细的描述,此处就不再赘述。但要在左旋和右旋的操作中维护max域的取值。

(四)查找具有最小低端点的重叠区间算法IntervalSearchMin()的基本思想:
采用递归思想,递归先从左子树找;递归到整个树的最左端,从第一个区间开始匹配,如果x与interval重叠,就不需要往下找了;若左子树找不到,则在右子树上找。

三、实验结果与分析

(一)首先,初始化区间树,依次插入下列区间:[41,49] [38,44] [31,35] [12,20] [19,25] [8,10],在树状显示的代码中稍作修改,再在控制台中树形显示区间树,如下图-1所示:
《《算法导论》实验七:区间树上的重叠区间查找算法(C++)——控制台树型显示》
图-1 初始化区间树并树形显示

(二)情况一:查找重叠区间[37,40]:“find [37,40]”
输出结果:最先找到的重叠区间和具有最小低端点的重叠区间相同,均为[38,44]。如下图-2所示:
《《算法导论》实验七:区间树上的重叠区间查找算法(C++)——控制台树型显示》
图-2 查找重叠区间[37,40]:“find [37,40]”

(三)情况二:查找重叠区间[9,30]:“find [9,30]”
输出结果:因为此时存在多个重叠区间,所以最先找到的重叠区间和具有最小低端点的重叠区间不同,最先找到的重叠区间为[19,25];具有最小低端点的重叠区间为[8,10]。如下图-3所示:
《《算法导论》实验七:区间树上的重叠区间查找算法(C++)——控制台树型显示》
图-3 查找重叠区间[9,30]:“find [9,30]”

(四)情况三:查找重叠区间[26,29]:“find [26,29]”
输出结果:因为没有与区间[26,29]重叠的区间,所以显示“Output:无重叠区间”。如下图-4所示:
《《算法导论》实验七:区间树上的重叠区间查找算法(C++)——控制台树型显示》
图-4 查找重叠区间[26,29]:“find [26,29]”

四、实验总结

1、区间树是红黑树的扩张,大部分操作跟红黑树相同或相似,但在对附加信息max进行维护时,左旋和右旋的过程中要对max单独进行更新维护。
2、通过测试证明查找算法是正确有效的。
3、用递归的思想可实现查找具有最小低端点的重叠区间。
4、对于任意一个具有n个结点的区间树,我们最多需要循环logn+2次,(即从根节点查找到叶子结点),每次循环都是常数时间,故在最坏情况下时间复杂度为O(log n)。在一个比较差的搜索中,假设每次我们寻找的区间都是叶子结点,在这个情况下的时间复杂度为Ω(log n)。所以区间树上的重叠区间查找算法的时间复杂度为Ɵ(log n)

五、源代码(C++)

/* 区间树(interval tree)是一种对动态集合进行维护的扩张红黑树,因此可在实验二红黑树的基础上进行扩张。 */  

#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 Interval     //定义一个表示区间范围的结构体
{
    int low;                //区间的低端点(low endpoint)
    int high;               //区间的高端点(high endpoint)
}Interval;

typedef struct Node        //声明红黑树结点
{
    Color color;           //红黑树结点的颜色类型,
    struct Node *parent;   //父节点
    struct Node *left;     //左孩子
    struct Node *right;    //右孩子
    Interval interval;     //区间
    int max;               //附加信息,记录以该节点为根的子树中所有区间端点的最大值
}Node;

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

//选择显示
void Display()             
{
    cout<<"************************************************************************\n";
    cout<<"**** 请选择您要的红黑树操作!! ****\n";
    cout<<"**** I:插入区间 D:区间树当前状态 S:查找区间 E:退出 ****\n";
    cout<<"**** Insert Display Search Exit ****\n";
    cout<<"************************************************************************\n";
}

//求三个参数中的最大值
int GetMax(int high,int leftMax,int rightMax)     
{
    int temp=(leftMax>rightMax)?leftMax:rightMax;
    return (high>temp)?high:temp;
}

//左旋,结点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;

        //以下为区间树与红黑树左旋调整的差异,即要调整结点max的大小,
        //且必须先计算x的max,在计算y的max
        x->max=GetMax(x->interval.high,x->left->max,x->right->max);
        y->max=GetMax(y->interval.high,y->left->max,y->right->max);
    }
    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;

        //以下为区间树与红黑树左旋调整的差异,即要调整结点max的大小
        //且必须先计算x的max,在计算y的max
        x->max=GetMax(x->interval.high,x->left->max,x->right->max);
        y->max=GetMax(y->interval.high,y->left->max,y->right->max);
    }
    else
    {
        cout<<"Error: can't right rotate,because no left child!"<<endl;
    }

}

//插入结点
void RBInsert(RBTree *rbTree,Interval interval)
{
    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->interval.low=interval.low;      //设置区间低端点
        rbTree->root->interval.high=interval.high;    //设置区间高端点
        rbTree->root->max=interval.high;      //初始根的max设为自己的high

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

        rbTree->nil->parent=rbTree->root;
        rbTree->nil->left=rbTree->root;
        rbTree->nil->right=rbTree->root;
        rbTree->nil->interval.low=rbTree->nil->interval.high=SENTINEL;  //将nil的区间设为哨兵
        rbTree->nil->color=BLACK;     //nil结color也设为黑
        rbTree->root->max=0;          //nil节点的max设为0,便于其他节点max的维护

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

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

//插入后调整树,以维持红黑树的5条性质
void RBInsertFixUp(RBTree *rbTree,Node *z)   
{
    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的根的话,把根的颜色设为黑色

}

//查找与给定区间重叠的区间
Node* IntervalSearch(RBTree * rbTree,Interval interval)
{
    Node *x=rbTree->root;    //从根开始查找
    while(x!=rbTree->nil&&!(interval.low<=x->interval.high&&interval.high>=x->interval.low))
    {//若x不等于nil节点且x与interval不重叠,则进行判断
        if(x->left!=rbTree->nil&&x->left->max>=interval.low)
            x=x->left;       //到x的左子树中继续查找
        else
            x=x->right;      //左子树必查不到,到右子树查
    }
    return x;    //x=nil或者x与interval重叠
}

//递归在z的左右子树中查找与interval重叠的具有最小低端点的区间
Node* IntervalSearchMin(RBTree* rbTree,Node* z,Interval interval)  
{  
    Node *x = z, *y;    
    //先从左子树上找 
    if(x->left && x->left->max >= interval.low)  
    {  
        y = IntervalSearchMin(rbTree,x->left, interval);  
        if(y != rbTree->nil)  
            return y;  
    }  
    //如果x与i相交,就不需要找左子树了 
    if(interval.low<=x->interval.high&&interval.high>=x->interval.low)  
        return x;  
    //最后在右子树上找 
    if(x->right)  
        return IntervalSearchMin(rbTree,x->right, interval);  
    //如果找不到,返回nil
    return rbTree->nil;     
}  

//求树高
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)->interval.low!=SENTINEL) ? "/" : " ");
    *iter++;
    cout << setw(2*(branchLen+1)+7) << "" << ((*iter&&(*iter)->interval.low!=SENTINEL) ? "\\" : " ");
    //setw(2*branchLen+4)改为setw(2*branchLen+5)
    *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)->interval.low!=SENTINEL && (*iter)->left->interval.low!=SENTINEL ) ? setfill('_') : setfill(' '));
    if(*iter&&(*iter)->interval.low!=SENTINEL)
    {
        cout<<setw(branchLen+2)<<"["<<IntToString((*iter)->interval.low)<<","<<IntToString((*iter)->interval.high)<<"]";
        //cout<<(((*iter)->color==RED) ? "红":"黑");
    }
    else
    {
        cout << setw(branchLen+1)<<"";
        //cout << setw(branchLen+2)<<"";
    }
    cout << ((*iter && (*iter)->interval.low!=SENTINEL && (*iter)->right->interval.low!=SENTINEL ) ? setfill('_') : setfill(' ')) << setw(branchLen+1) << "" << setfill(' ');
    //setw(branchLen)改为setw(branchLen+1)
  }
  cout << endl;
}


//在控制台树形输出红黑树
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);  
    int nodeSpaceLen = 5 + (level+1)*(int)pow(2.0,h);  
    int startLen = branchLen + (3-level) + indentSpace; 

    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;
    char choose;
    Interval interval;
    while(true)
    {
        Display();
        cin>>choose;
        switch(choose)
        {
            case 'E':exit(0);break;   //选择0则跳出系统
            case 'I':     
                {//选择1则插入结点
                    cout<<"请输入区间的左右两个端点,中间以空格隔开:";
                    cin>>interval.low>>interval.high;
                    RBInsert(&tree,interval);
                    cout<<endl;
                    break;
                }
            case 'D':   
                {//选择2则在控制台以树的形式显示红黑树
                    cout<<endl<<"此时红黑树状态如下:"<<endl;
                    PrintPretty(&tree,2,3);
                    cout<<endl;
                    break;
                }
            case 'S':
                {//显示查找重叠区间的结果
                    cout<<"请输入待查找的区间:";
                    cin>>interval.low>>interval.high;
                    Node* x=IntervalSearch(&tree,interval);
                    if(x!=tree.nil)
                    {
                        Node* min_x=IntervalSearchMin(&tree,tree.root,interval);
                        cout<<"Output:最先找到的重叠区间为:"<<"["<<x->interval.low<<","<<x->interval.high<<"]"<<endl;
                        cout<<" 具有最小低端点的重叠区间为:"<<"["<<min_x->interval.low<<","<<min_x->interval.high<<"]"<<endl<<endl;
                    }
                    else
                        cout<<"Output:无重叠区间!"<<endl<<endl;
                }            
        }
    }
}
    原文作者:查找算法
    原文地址: https://blog.csdn.net/to_Baidu/article/details/50319513
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞