如果HR再问红黑树,你就把《算法导论》拿出来批判一番

《算法导论》中第一次引入树这种数据结构是以二叉堆的形式出现的,并且在附录中在图的基础上给出了树在数学上的定义。如果从链表的观点出发,那么相当于是放宽了有序的要求,即允许两个不同位置的元素有相等的序。

对于序为nn的节点来说,可以指向多个序为n+1n+1的节点,相应的后者称为前者的孩子,前者称为后者的父节点。其最大序即为树的高度。(下图中数字表示的是节点的代数,而非值)

在这里插入图片描述

在上图中,0节点的左右两个节点分别为其左子节点和右子节点,反过来0节点也是这两个子节点的父节点;左侧的1节点的两个2节点也分别为其左子节点和右子节点,以此类推。

很容易发现,在一个树中,只有0节点没有父节点,这个节点被称为根节点。

二叉搜索树

二叉搜索树要求父节点大于等于其左子节点及其所有子节点,而小于等于其右子节点及其所有子节点,如下图所示

在这里插入图片描述

初始化

如果想要在这个树中查询任意一个值,其最坏的情况也无非是查询到最下面的点,进行的比较次数为树的高度。由于这是二叉树,设树的元素个数为nn,则理想情况下树的高度不大于log2n\log_2n

对于二叉搜索树中的每个父节点最多子节有两个子节点,树中任意节点有三个指针,分别指父向节点、左子节点和右子节点。其中,根节点没有父节点。C语言实现为

//cTrees.c
typedef struct TREENODE
{
    struct TREENODE *father;
    struct TREENODE *left;
    struct TREENODE *right;
    int value;
}tNode;

现在,我们迫切地希望能够实现一个二叉搜索树,但正如此前所接触的线性数据结构一样,生成树的过程也必然始于添加节点。

对于一个已有的二叉搜索树而言,当我们插入一个新节点的时候,应该比较新节点与当前节点的值,如果大于当前节点,则比较新节点与当前节点右子节点的值;如果小于当前节点,则比较新节点与当前节点左子节点的值。如果下一个将要比较的节点不存在,那么正好可以把新节点插进来。

void insertNode(tNode* root, int val){
    tNode* new = (tNode*)malloc(sizeof(tNode));
    new->value=val;
    new->left=NULL;
    new->right=NULL;
    while (TRUE){
        if (root->value<val) 
            if(root->right!=NULL)
                root=root->right;
            else{
                //若右子节点不存在,则新节点成为其右子节点
                new->father = root;
                root->right = new;
                return;         //赋值之后函数结束
            }
        else                    //左边的操作与右边相同
            if (root->left!=NULL)
                root=root->left;
            else{
                new->father=root;
                root->left=new;
                return;
            }
    }
}

当能够生成二叉搜索树之后,我们更迫切地想知道这个二叉搜索树生成的对不对,所以需要打印这个二叉搜索树

//打印二叉搜索树,输入为节点和节点序
void printBST(tNode* root,int start){
    printf("the %dth node is %d\n",start,root->value);
    //如果当前节有子节点,则继续打印这个子节点和节点序
    if (root->left!=NULL)
        printBST(root->left,start+1);
    if (root->right!=NULL)
        printBST(root->right,start+1);
}

最终在主函数中进行验证

int main(){
    tNode* root;
    root->left=NULL;
    root->right=NULL;
    root->value=10;     //以上初始化根节点

    int init[10]={1,11,5,12,2,4,19,11,8,7};
    for (int i = 0; i < 10; i++)
        insertNode(root,init[i]);
    
    printBST(root,0);
    return 0;
}

其结果为

PS E:\Code\AlgC> gcc .\cTrees.c
PS E:\Code\AlgC> .\a.exe       
the 0th node is 10
the 1th node is 1       //第一代节点,10的左子节点
the 2th node is 5       //第二代节点,1的右子节点
the 3th node is 2       //第三代节点,5的左子节点
the 4th node is 4       //第四代节点,2的右子节点
the 3th node is 8       //第三代节点,5的右子节点
the 4th node is 7       //第四代节点,8的左子节点
the 1th node is 11      //第一代节点,10的又子节点
the 2th node is 11      //第二代节点,11的左子节点
the 2th node is 12      //第二代节点,11的右子节点
the 3th node is 19      //第三代节点,12的右子节点
PS E:\Code\AlgC>

我们生成的树为

在这里插入图片描述

可见的确符合二叉搜索树的规则。那么接下来我们需要为这棵二叉树添加新的功能,即搜索节点与删除节点。其中,搜索功能与插入功能如出一辙,区别只是在于,我们不需要重复插入,我们只需将这个值的指针返回即可。同时,循环判定也变为while(root->value!=val)

//通过节点的值搜索节点地址,root为根节点
tNode* searchBST(tNode* root, int val){
    while (root->value!=val)
    {
        if (root->value<val && root->right!=NULL)
            root=root->right;
        else if (root->value>val && root->left!=NULL)
            root=root->left;
        else
            return FALSE;
    }
    return root;
}

删除节点

相比之下,删除节点显得更加复杂一些,因为此时将涉及到其父节点、左子节点、右子节点以及兄弟节点之间的大小关系。

如果被删除的节点没有子节点,那当然皆大欢喜,只需将其父节点指向被删除节点的指针变成NULL即可;如果只有一个子节点,也并不麻烦,只需指向被删除节点的指针指向这个子节点。

然而,如果有两个子节点,那么由于父节点必须大于左子节点而小于右子节点,所以取代被删除节点的一可以是左子节点,也可以是右子节点。区别在于,若是右子节点取代该节点,则左子节点为新父节点的左子节点;若是左子节点取代父节点,则右子节点仍为新父节点的右子节点。

所以,二选其一即可,交换当前节点与其右子节点,然后删除交换后的右子节点即可,如果交换后的右子节点仍然有两个子节点,则继续交换,直到能够删除为止。这里其实有一个巧妙的默认,即默认为待删除节点无论处于什么位置都是合法的,毕竟这个节点最终将被删除掉。

//删除节点的值,root为根节点,delNode为待删除节点
void deleteNode(tNode* delNode){
    if(delNode->left==NULL&&delNode->right==NULL){
        if(delNode->value>pNode->value)
            pNode->right=NULL;
        else
            pNode->left=NULL;
    }
    else if(delNode->left!=NULL&&delNode->right!=NULL){
        int val = delNode->value;
        //交换当前节点与右节点的值
        delNode->value = delNode->right->value;
        delNode->right->value=val;
        deleteNode(delNode->right);//删除右节点
    }
    else{
        tNode* pNode = (delNode->left==NULL) \
            ? delNode->right : delNode->left;
        delNode->value = pNode->value;
        delNode->right = pNode->right;
        delNode->left = pNode->left;
    }
}

验证一下

int main(){
    tNode* root;
    root->left=NULL;
    root->right=NULL;
    root->value=10;
    int init[10]={1,11,5,12,2,4,19,11,8,7};
    for (int i = 0; i < 10; i++)
        insertNode(root,init[i]);
    
    tNode* sNode=searchBST(root,5);
    deleteNode(sNode);
    printBST(root,0);
   return 0;
}

结果为

PS E:\Code\AlgC> gcc .\cTrees.c
PS E:\Code\AlgC> .\a.exe       
the 0th node is 10
the 1th node is 1
the 2th node is 8
the 3th node is 2
the 4th node is 4
the 3th node is 7
the 1th node is 11
the 2th node is 11
the 2th node is 12
the 3th node is 19

示意图为

在这里插入图片描述

旋转节点

二叉搜索树有一个问题,如果我们在对其进行初始化的时候,输入的是1,2,3,4,5,6,7,8,91,2,3,4,5,6,7,8,9,那么这个所谓的二叉树可能并不会产生叉,而是径直变成一个只有右子节点的链表。

由于二叉树的时间复杂度与其树高是成正比的,所以不分叉的二叉树也会失去时间复杂度上的优势。所以,如果能够控制二叉树的高度,使之横向分布尽量均匀,就能够有效提高其性能。

最直观的想法是,假设yyxx的右子节点,而xx的左子节点的家族人丁稀薄,yy的右子节点子系繁多,那么如果把yy的子系过继给xx,或者干脆取代xx,父子关系逆转,必定能使得整个树变得更加均匀。

在这里插入图片描述

现考虑x,xL,y,yL,yRx,x_L,y,y_L,y_R这五个节点,其中xL,yx_L,yxx的左右节点,yL,yRy_L,y_Ryy的左右节点。那么这些节点之间必然存在关系xLxyLyyRx_L\leqslant x\leqslant y_L\leqslant y\leqslant y_R。如果希望yy变为xx的父节点,那么xx必然是yy的左子节点。此时yy将多出一个节点,必须过继给xx,又因为xyx\leqslant y,所以只能过继左子节点yLyL

这里可以考虑插入一个中间步以便于理解

在这里插入图片描述

可见yy多了一个节点yLy_L正好可以过继给xx,成为其左子节点。则过继之后的节点关系变成

在这里插入图片描述

可见这个过程并没有改变二叉搜索树的性质,但是在yRy_R长于yLy_L的情况下,能够有效降低树的高度。

因为x,yx,y的转置过程就像旋转一样,所以这个操作叫做旋转,又因为父节点变成了子节点的左子节点,所以叫左旋,其逆过程就是右旋。但本文中并不提倡左旋右旋这种故作高深的提法,而是提倡旋转x,yx,y两个节点这种说法。

总之可能是翻译的脑补能力很强,所以起了这么个奇葩的名字。其实这个操作的本质就是这五个点的重新排布而已,硬把这种重新排布命名成旋转我也是醉了,rotationrotation就算翻译成转置\textbf{转置}也要比旋转更加贴切,也能减少理解上的困难。

旋转操作落实到算法上,其实并不需要考虑xL,yRxL,yR,但需要考虑考虑xx父节点指针的变化。即整个操作过程无非是x,y,yLx,y,y_L以及xx父节点的指针变化而已,传统的旋转实现为

#define RIGHT 1

#define LEFT 0

//树节点的经典旋转操作,flag为LEFT时左旋,RIGHT时右旋
void rotNode(tNode *xNode, int flag){
    tNode *yNode;
    if (flag==LEFT){
        yNode = xNode->right;
        xNode->father->right = yNode;//y成为x父节点的右子节点
    }else{
        yNode = xNode->left;
        xNode->father->left = yNode;
    }

    yNode->father = xNode->father; //x的父节点成为y的父节点
    xNode->father = yNode;         //y成为x的父节点

    if (flag == LEFT){
        yNode->left->father = xNode; //y左子节点过继给x
        xNode->right = yNode->left;
        yNode->left = xNode;
    }else{
        yNode->right->father = xNode;
        xNode->left = yNode->right;
        yNode->right = xNode;
    }
}

将主函数中删除操作代码换成rotNode(sNode,LEFT);,其结果为

PS E:\Code\AlgC> gcc .\cTrees.c
PS E:\Code\AlgC> .\a.exe       
the 0th node is 10
the 1th node is 5
the 2th node is 1
the 3th node is 2
the 4th node is 4
the 2th node is 8
the 3th node is 7
the 1th node is 11
the 2th node is 11
the 2th node is 12
the 3th node is 19

可见5的确成为了1的父节点,而2过继给了1,树高并没有变化,是因为原5的左右子族的长度相等。从代码的角度来说,替换意义上的旋转操作可以更加简洁,而且更有利于理解。

//替换意义上的旋转操作,sNode为子节点,pNode为父节点
void turnNode(tNode *sNode, tNode *pNode){
    if(sNode==pNode->right){
        sNode->left->father = pNode;
        pNode->right = sNode->left;
        sNode->left = pNode;
    }else{
        sNode->right->father = pNode;
        pNode->left = sNode->right;
        sNode->right = pNode;
    }
    sNode->father = pNode->father;
    pNode->father = sNode;
}

红黑树

调整节点

有了旋转操作,那么问题便成了何时旋转。最直观的方案是,让每个节点都包含辈分信息,然后想办法让每一个家族的辈分相差不要太过悬殊。这种方案的问题是,如果改变一个父节点的辈分,那么这个父节点的所有子孙,将都会受到影响。

所以,我们需要找到某中共衡量树高的某个参数,并且这个参数易于保持。由于树的节点数目并不固定,所以不同子孙所构成链表的长度也必然不等,要求每个家族的最小辈分完全相等是不现实的,唯一能够做到的是,让每个家族在抽离一些特殊的子女之后,达到辈分相等。

红黑树便是本着这样一种思维,这里要求任意一个父节点到其最后一代节点的所有简单路径中,包含相同数目的黑色节点。考虑到父节点到其后低啊的所有简单路径不可能包含相同的节点,所以要在黑色节点之间插入红色节点,以保证黑色节点数目相等。

但是,红色节点不能乱插,并且必须要少于黑色节点,所以要求红色父节点的两个子节点都为黑色

首先,定义一下红黑树的节点。

#define RED 0
#define BLACK 1
typedef struct rbNODE
{
    struct rbNODE* father;
    struct rbNODE* left;
    struct rbNODE* right;
    int value;
    int color;
}rbNode;

现在,就可以尝试生成一棵红黑树。像二叉搜索树一样,首先要有一个根节点,由于根节点对于所有子孙节点都是唯一的,所以选则黑色即可。

便于指导后面的操作,将红黑树的两点要求列在此处

  1. 任意父节点到其最后一代孙节点的所有简单路径中,黑色节点相同数目。
  2. 红节点的左右子节点均为黑色。

第一条性质也可以写为等价形式:任何一个末代孙节点到根节点的简单路径中,黑色节点数目相同。或者说,任何两个末代孙节点抵达任意一个相同的祖节点的简单路径中,黑色节点数目相同。

  • 叔节点与父节点都为红色

然后,如果向已有的红黑树中插入新的节点NN,由于第一条规则,我们优先考虑红色。如果这个NN的父节点PP也是红色,那就尴尬了,违反了第二条规则,所以需要把PP变成黑色。但变成黑色之后,这条路径就比其他路径多了一条黑色节点。

这时如果PP的兄弟节点、NN的叔叔QQ是红色节点就好办了,可以将QQ也变成黑色,然后将P,QP,Q的父节点GG变成红色。这样,GG的所有子系就得到了统一,从而整棵树都得到了统一。唯一可能麻烦的是,GG和其父节点可能会违反第二条规则,但这已经是GG的父辈和祖辈之间的事情了,重复调用即可。

  • 叔节点为黑色

然而,如果QQ是黑色的,那么问题会有些麻烦,这里可以考虑一下旋转操作,如下图所示

在这里插入图片描述

假设xL,yL,yRx_L,y_L,y_R的子系均符合红黑树的要求,比较旋转前后的各条子系

旋转前 旋转后
x,y,yLx,y,y_L y,x,yLy,x,y_L
x,xLx,x_L y,x,xLy,x,x_L
x,y,yRx,y,y_R y,yRy,y_R

可见,如果x,yx,y均为红色,则旋转前后黑点的数目并不会发生变化;如果xx为黑色,则y,yRy,y_R这条子系减少一个黑节点;如果yy为黑色,则y,x,xLy,x,x_L这条子系增加一个黑节点。

总结一下,即两个红色节点的旋转操作不会改变子系的黑色节点数目;红父与右黑子的旋转,会使红父的左子节点的子系增加一个黑色节点;黑父与右红子的旋转,会使红子的右节点减少一个黑色节点。这意味着当父子节点均为红色时,我们就可以大胆地使用旋转操作而不必担心出乱子

当父节点PP和子节点NN都为红色,且NN的叔节点QQ为黑色时,我们可以尝试旋转一下节点P,NP,N,但P,NP,N旋转之后并不会改变二者的颜色,二者仍旧不满足第二条规则。但由于PP是红色,那么PP的父亲GG一定为黑色,而PP的兄弟节点QQ也为黑色,所以只需PP变成黑色,让GG变成红色就能够满足第二条规则了。

但这里又出现了新的问题,满足第二条规则之后,GGQQ子系必然因为GG的变色而少了一个黑色节点。考虑到P,GP,G二者的颜色,现在将这两个节点再旋转一次,正好能够使得QQ子系增加一个节点,至此红黑树又重新满足了要求。

总结一下,如果

  1. 叔节点QQ存在且为红色,则将父节点PP和叔节点QQ同时设为黑色,将祖父节点GG设为红色,然后将指针指向祖父节点。

若叔节点QQ不存在或为黑色,则

  1. 若插入节点与父节点在同侧(例如,插入节点为左节点,父节点也为左节点),则将父节点PP设为黑色,将祖父节点GG设为红色,旋转P,GP,G
  2. 若插入节点与父节点在异侧,则旋转PP和插入节点,然后将指针移向PP,此时PP与其父节点成为同侧节点。
//调整红黑树的节点
//调整红黑树的节点
void adjustRBT(rbNode *node){
    rbNode *pNode = node->father; //父节点
    rbNode *qNode;                //叔节点
    while (pNode->color==RED){
        int flag = pNode == pNode->father->left 
                 ? LEFT : RIGHT;
        qNode = flag==LEFT ? pNode->father->right 
                           : pNode->father->left; //叔节点
        //如果叔节点存在且为红色
        if (qNode!=NULL || qNode->color==RED){
            pNode->color = BLACK;
            qNode->color = BLACK;
            pNode->father->color = RED;
            node = pNode->father;
            pNode = node->father;
        }
        else{
            if(flag != (node==pNode->left ? LEFT : RIGHT)){
                turnRbNode(node,pNode);//此时插入节点与父节点在异侧
                node = pNode;
                pNode = node->father;
            }//执行完此操作后,变为同侧
            pNode->color=BLACK;
            pNode->father=RED;
            turnRbNode(pNode,pNode->father);
        }
    }
}

初始化

红黑树的根节点颜色并不会影响红黑树的第一条性质,但如果红黑树的根是红色的,那么其左子节点和右子节点必须同时为黑色。所以,当根节点为黑色时显然对其后代颜色的影响更小,所以选取根色为黑。

此前所定义的二叉树的插入操作对红黑树完全有效,但是需要额外添加节点颜色。为此,可以从二叉树的插入函数中提取出新节点的指针,并为这个指针赋予颜色,然后对这个指针的颜色进行调整。

当然,在真正启动初始化程序之前,最好还是检查一下此前的算法的漏洞。红黑树的核心算法adjustRBT中所引用的旋转操作其实隐藏着一个很大的bug,即其默认在树中间进行操作,所涉及到的所有的节点元素都不为NULL,所以一旦涉及到根节点或者末代节点,就必然会引发灾难。所以,必须在变化之前进行节点判断

//打印红黑树,使之能够显示红黑特性
void printRBT(rbNode *root, int start){
    printf("the %dth node : %d with %d\n", start, root->value, root->color);
    if (root->left != NULL)
        printRBT(root->left, start + 1);
    if (root->right != NULL)
        printRBT(root->right, start + 1);
}
//旋转红黑树的节点,sNode是被旋转的子节点
//root为根节点,输出为旋转后的根节点
rbNode* turnRbNode(rbNode *root, rbNode *sNode){
    rbNode* pNode = sNode->father;      //被旋转的父节点
    if(sNode==pNode->right){            //s为右子节点
        if(sNode->left!=NULL)
            sNode->left->father = pNode;//s的左子节点过继给pNode
        pNode->right = sNode->left;     //p接收s的左子节点
        sNode->left = pNode;            //p成为s的左子节点
    }else{
        if (sNode->right!=NULL)
            sNode->right->father = pNode;
        pNode->left = sNode->right;
        sNode->right = pNode;
    }
    sNode->father = pNode->father;  //sNode过继给pNode的父节点
    pNode->father = sNode;          //pNode和sNode父子逆转
    
    if (sNode->father==NULL)        //若pNode为根节点
        return sNode;
    
    if (pNode==pNode->father->right)
        sNode->father->right = sNode;
    else
        sNode->father->left = sNode;
    return root;
}
//红黑树的插入算法
rbNode* insertRbNode(rbNode *root, int val){
    rbNode *new = (rbNode *)malloc(sizeof(rbNode));
    new->value = val;
    new->left = NULL,new->right = NULL;
    new->color = RED;

    rbNode *tmpRoot = root;         //保护root
    rbNode *temp;
    while (temp = root->value < val 
            ? root->right : root->left,temp!=NULL)
        root = temp;
    new->father = root;
    if (root->value < val)
        root->right = new;
    else
        root->left = new;

    return adjustRBT(tmpRoot,new);
}

主函数为

//红黑树插入算法
int main(){
    rbNode Root = {NULL,NULL,NULL,11,1};
    rbNode* root = &Root;
    int init[7]={2,14,1,7,15,5,8};
    for (int i = 0; i < 7; i++){
        root = insertRbNode(root,init[i]);
    }
    root = insertRbNode(root,4);
    printRBT(root,0);
    return 0;
}

输出为

PS E:\Code\AlgC> .\a.exe       
the 0th node : 7 with 1
the 1th node : 2 with 1
the 2th node : 1 with 1
the 2th node : 5 with 1
the 3th node : 4 with 0
the 1th node : 11 with 1
the 2th node : 8 with 1
the 2th node : 14 with 1
the 3th node : 15 with 0

这里选取的参数与《算法导论》中一致,可以通过调试看出节点以及节点颜色的变化过程,与《算法导论》中所列出的基本一致,所以就不画图了。

在这里插入图片描述

删除节点

回顾一下二叉搜索树的节点删除操作,可以发现,如果被删除节点有两个节点,则这个节点将与其子节点的值进行交换。这种交换终止于交换后的子节点不多于一个子节点。

当然,这个过程可以改得更加激进一些,即只要被删除节点仍有子节点,那么就将该节点与子节点的值进行交换,然后将指针指向子节点,直到指针指向末代节点,然后删除。

如果将这种操作挪用到红黑树上,那么在值交换的过程中,并不必交换节点颜色,于是只有最终删除末代节点的时候,才需要考虑节点颜色。

然而,末代节点被删除将导致末代节点这条世系彻底消失,所以,无论末代节点的颜色如何,都不会改变其他世系的黑高,所以我们惊奇地发现,别穿得难乎其难的红黑树删除节点操作,竟然简单的让人难以置信。

//红黑树查询,root为根节点,val为待查询值
//返回值为节点的指针
rbNode* searchRBT(rbNode *root, int val){
    if(root->value==val)
        return root;
    if(root->value<val && root->right!=NULL)
        return searchRBT(root->right,val);
    else if(root->value>val && root->left!=NULL)
        return searchRBT(root->left,val);
    else
        return FALSE;
}
//红黑树删除节点,输入为待删除节点指针
void deleteRbNode(rbNode* dNode){
    rbNode *pNode = dNode->father;
    if (dNode->left == NULL && dNode->right == NULL){
        if (dNode==pNode->right)
            pNode->right = NULL;
        else
            pNode->left = NULL;
    }
    else{
        //如果左子节点存在,则pNode为dNode的左子节点,否则为右子节点
        pNode = (dNode->left==NULL) ? dNode->right : dNode->left;
        int val = dNode->value;
        dNode->value = pNode->value;
        pNode->value = val;
        deleteRbNode(pNode);
    }
}
//主函数
int main()
{
    rbNode Root = {NULL,NULL,NULL,11,1};
    rbNode* root = &Root;
    int init[10]={2,14,1,7,15,5,8,4,13,6};
    for (int i = 0; i < 10; i++){
        root = insertRbNode(root,init[i]);
    }
    
    rbNode* delNode = searchRBT(root,11);
    printRBT(root,0);
    deleteRbNode(delNode);
    printf("after delete node 11\n");
    printRBT(root,0);
   return 0;
}

结果为

PS E:\Code\AlgC> gcc .\cTrees.c
PS E:\Code\AlgC> .\a.exe       
the 0th node : 7 with 1
the 1th node : 2 with 1
the 2th node : 1 with 1
the 2th node : 5 with 1
the 3th node : 4 with 0
the 3th node : 6 with 0
the 1th node : 11 with 1
the 2th node : 8 with 1
the 2th node : 14 with 1
the 3th node : 13 with 0
the 3th node : 15 with 0
after delete node 11
the 0th node : 7 with 1
the 1th node : 2 with 1
the 2th node : 1 with 1
the 2th node : 5 with 1
the 3th node : 4 with 0
the 3th node : 6 with 0
the 1th node : 8 with 1
the 2th node : 14 with 1
the 3th node : 13 with 0
the 3th node : 15 with 0

顺序统计树

顺序统计树是基于红黑树的一种数据扩张,除了红黑属性之外,其节点还包含子系个数的信息sizesizesizesize即以当前节点为根的子树的所有节点个数。

在经历了实现红黑树的折磨之后,这种扩张显得轻而易举。首先在节点结构体中添加一个成员size。然后修改插入操作,当插入新节点时,新节点的size值为1,途中经历的所有指针指向的节点,其size值都加1。

//rbNode* insertRbNode(rbNode *root, int val);
//...
    while (temp = root->value < val 
            ? root->right : root->left,temp!=NULL){
        root->size += 1;
        root = temp;
    }
//...

删除操作时,记录最终被删除的节点指针,其所有父辈的size均减一。

    if (dNode->left == NULL && dNode->right == NULL){
        if (dNode==pNode->right)
            pNode->right = NULL;
        else
            pNode->left = NULL;
        while(pNode.size-=1,pNode->father!=NULL){
            pNode = pNode->father;
        }
    }

size值一方面给出了当前节点子系的体量,另一方面也是对当前节点在所有节点中的大小排名的一个标记。当指针从根节点依次下沉时,顺带也继承了当前节点的区间信息,其实现为

rbNode* searchRBTN(rbNode *root, int n){
    int low = 0;    //左开右闭
    int high = root->size;
    if(n>high)
        return NULL;

    while (1){
        //左子节点存在且size<n-low
        if (root->left!=NULL && root->left->size<n-low){
            root = root->left;
            high = low + root->size;
        }else{
            root = root->right;
            low = high - root->size;
        }
        if (root->right!=NULL && root->right->size==high-root->size)
            return root;
        if(root->left!=NULL && root->left->size == low+root->size-1)
            return root;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章