天天看点

二叉排序平衡树查找-动态查找

二叉排序平衡树查找-动态查找

更新:2013-12-02

//

需要掌握的前提知识是二叉排序树查找

名称:二叉排序平衡树查找

关键字:二叉平衡树,二叉排序树

在二叉排序树的不断插入查找中,有可能出现极端情况,最坏情况例如:

每棵树都只有右孩子。那样查找变成了顺序查找了。

为了使排序树的深度最小,需要对每棵树的左右孩子进行平衡处理,成为

二叉平衡树。

二叉平衡树:它是一棵空树,或者,它的左孩子或右孩子不为空的话也为

平衡二叉树。二叉平衡树的左孩子和右孩子的深度差绝对值不能超过1。

如果将平衡因子设为左子树的深度减去右子树的深度,那么值有-1,0,1。

讨论一下插入结点时的平衡操作。

插入结点可以分为两种:

一插入根结点,二插入叶子结点。

插入根结点较为简单,着重看插入叶子结点的情况。

(1)设有树tree1:

            a(2)                     b(0)

            /    \                     /    \

      b(1)      ar      =>    bl      a(0)

     /    \                          ^      /    \

   bl      br                      c    br     ar

   ^

   c

   或树tree1':

           a(2)                b(0)

          /                       /    \

      b(1)          =>    bl      a(0)

     /

    ^

    bl

因为插入新结点而使树的平衡因子变为2,在这种情况下,需要作

单向右旋平衡处理:

b和a交换位置,a接管b的右孩子br,那样树就平衡了,并且树的结点被中

序遍历的顺序不变。

(2)设有树tree2:

          a(-2)                             b(0)

          /    \                               /    \

        al      b(-1)     =>        a(0)     br

                 /     \                  /    \       ^

              bl       br             al      bl    c

                        ^

                        c

   或树tree2':

           a(-2)                            b(0)

                  \                            /    \

                b(-1)     =>        a(0)     br

                       \

                       ^

                       br

因为插入新结点而使树的平衡因子变为-2,在这种情况下,需要作

单向左旋平衡处理:

b和a交换位置,a接管b的左孩子bl,那样树就平衡了,并且树的结点被中序

遍历的顺序不变。因为不确定bl是否为NULL,所以a的平衡因子不确定。

(3)设有树tree3:

            a(2)                          a(2)                ____br(0)____

            /    \                           /    \               /                       \

     b(-1)      ar    =>       br(?)      ar   =>  b(?)                 a(?)

     /    \                          /     \                    /    \                 /     \

  bl      br(?)             b(?)    null\d          bl      c\null null\d      ar

           /     \              /    \

   c\null       null\d  bl      c\null

  或树tree3':

            a(2)                      a(2)               br(0)

            /                           /                     /     \

     b(-1)            =>    br(1)        =>   b(0)       a(0)

           \                     /

           ^               b(0)

           br

因为插入新结点或d结点而使树的平衡因子变为2,在这种情况下,需要作

双向旋转(先左后右)平衡处理:先b和br作单向左旋,然后br和a作单向

右旋,那样树就平衡了。并且树的结点被中序遍的顺序不变。

(4)设有树tree4:

           a(-2)                  a(-2)                     ____bl(0)____

           /    \                    /    \                      /                      \

        al      b(1)  =>    al      bl(?)   =>      a(?)                b(?)

                 /    \                   /     \              /    \                 /    \

          bl(?)      br      c\null       b(?)     al      c\null null\d      br

           /    \                               /    \

    c\null    null\d               null\d      br

   或树tree4':

           a(-2)               a(-2)                       bl(0)

               \                        \                        /     \

                b(1)  =>            bl(-1)   =>  a(0)       b(0)

               /                             \

              ^                              b(0)

             bl

因为插入新结点或d结点而使树的平衡因子变为-2,在这种情况下,需要作

双向旋转(先右后左)平衡处理:先b和bl作单向右旋,然后br和a作单向

左旋,那样树就平衡了。并且树的结点被中序遍的顺序不变。

讨论一下删除结点时的平衡操作。

因为要保持有序性,所以对二叉排序平衡树的结点的删除操作和对

二叉排序树的删除操作类似,不过二叉排序平衡树还需要作平衡操作。

对树进行中序遍历,就可以得到结点的从小到大的排序,

如果被删结点只有一棵子树,直接用子树取代被删除结点即可。

如果被删结点有两棵子树,那么可以考虑用其前驱或后继来替代该结点。

在二叉排序树中假设有这么一棵树:                                                      

                         a

                       /   \

                     b       c

                   /   \

                 d       e

               /   \   /   \

              f     g h     i

                   /   \

                  j     k

但在二叉排序平衡树中不会出现这样的情况的,因为上面的树不平衡。

现在选定用前驱代替删除结点,所以现在重新假设有树:

                                 _a(?)_

                                /          \

                        b(?)              c(?)/null

                      /        \                ^

         d(?)/null          e(?)/null   ...

        /         \                 ^

  f/null      g(?)/null       ...

                  /

            j/null

在上图中假设c子树和e子树的高度能使a树平衡。

这样就可以分情况分析b点的删除对树平衡的影响了。

现在要删除的结点是b,对该树的中序遍历顺序为fdjgb...a...。

(1)如果b只有左孩子

   很明显,只要将b的左孩子直接连接到b的前驱,再将b结点删除。

   那么它此时的a的平衡因子就会减1,a树的高度也发生了变化,

   此时要回溯到最小不平衡树,进行平衡调整,因为调整可能导致

   最小不平衡树的父节点不平衡,所以要继续回溯调整,直到根结

   点。增加高度的旋转操作和降低高度的旋转操作对称。单左旋和

   右左旋刚好为对称操作。

(2)如果b只有右孩子

   同理,只要将b的右孩子直接连接到b的前驱,在将b结点删除。

   a树的高度发生变化,然后进行相应的旋转操作。

(3)如果b的左右孩子均不为空

   同BST树的删除操作,删除后的树为:

                                __a(?)__

                               /              \

                       g(?)               c(?)/null

                     /        \                 ^

         d(?)/null          e(?)/null   ...

         /         \               ^

  f/null           j/null       ...

  或当g为空时:

                               __a(?)__

                              /               \

                   __d(?)__           c(?)/null

                  /              \              ^

            f/null          e(?)/null     ...

                                 ^

                                 ...

  由以上看到,发生高度变化的树有d树,g树,a树。需要进行调整。

  平衡因子的变化:

  如果b的左孩子或右孩子为空,回溯调整a。

  如果g为空,删除b后,则d的平衡因子不变,回溯调整a。

  如果g不为空,删除b后,d的平衡因子加1,回溯调整g和a。

      g的平衡因子取b原来的平衡因子。

      因此树的平衡调整从d结点开始,到g,再到a。也就是说,

      是在替换删除结点的结点开始平衡调整的。为了代码方便,

      这种情况就只是用替换结点的值对删除结点的值进行替换,

      实际删除的结点是替换点(便于编程实现回溯)。但平衡

      因子不能被替换。

  源代码的测试暂未发现错误。

附源代码:

//树的平衡因子常量
#define LSZ_SEARCH_BALANCEDIFF 1
#define LSZ_SEARCH_LEFTHIGH 1
#define LSZ_SEARCH_EQUALHIGH \
    ((LSZ_SEARCH_LEFTHIGH)-(LSZ_SEARCH_BALANCEDIFF))
#define LSZ_SEARCH_RIGHTHIGH \
    ((LSZ_SEARCH_EQUALHIGH)-(LSZ_SEARCH_BALANCEDIFF))
//树的高度是否增加
#define LSZ_SEARCH_HIGHERTRUE 1
#define LSZ_SEARCH_HIGHERFALSE 0
//树的高度是否减少
#define LSZ_SEARCH_LOWERTRUE 1
#define LSZ_SEARCH_LOWERFALSE 0

//删除树结点的宏
#define LSZ_SEARCH_DESTROYNODE(node) (free((node)->element),free(node))
//初始化树头结点的宏
#define LSZ_SEARCH_TREEINIT(tree) ((tree).lChild=NULL)

//元素的关键字类型
typedef int LSZ_SearchK; //key

//元素类型
typedef struct LSZ_SearchE{ //element
    LSZ_SearchK key; //元素的键值,用于唯一标识
}LSZ_SearchE;
//带平衡因子的非顺序存储的查找树的结构元素类型
//二叉平衡树的结点类型
typedef struct LSZ_SearchBBTNode{ //binary,balance, tree
    int fBalance; //factor of balance
    LSZ_SearchE *element;
    struct LSZ_SearchBBTNode *lChild;
    struct LSZ_SearchBBTNode *rChild;
}LSZ_SearchBBTNode;
           
//二叉排序平衡树查找
//
//tree:其左孩子指向树的根结点。
//element:当往树中插入结点时,其指向空间存放传入值,
//          其它情况指向空间用于记录查找到元素的值。
//mode:仅查找,或带插入的查找,或带删除的查找。
int LSZ_search_treeBinarySortedBalanced(LSZ_SearchBBTNode *tree,
                                        LSZ_SearchE *element,
                                        int mode)
{
    int depth = LSZ_SEARCH_HIGHERFALSE; //标记树的深度的变化
    LSZ_SearchBBTNode *treeTemp = tree->lChild; //取得根结点

    switch(mode){
        case LSZ_SEARCH_ONLY:
            while(treeTemp != NULL){
                if(treeTemp->element->key == element->key){
                    *element = *(treeTemp->element);
                    return 0; //查找成功
                }
                else{
                    treeTemp = element->key < treeTemp->element->key
                        ? treeTemp->lChild : treeTemp->rChild;
                }
            }
            break;
        case LSZ_SEARCH_INSERT: //插入查找需要堆栈
            return LSZ_search_insertBSBT(&(tree->lChild), element, &depth); //这里没有初始化右子树
            break;
        case LSZ_SEARCH_DELETE: //删除查找需要堆栈
            return LSZ_search_deleteBSBT(&(tree->lChild), element, &depth);
            break;
    }
    return -1; //出错
}
           
//二叉排序平衡树带插入的查找
//
//(*tree):指向需要被查找的树的树根的指针的地址。
//       注意(*tree)必须在原来的树结构中并指向树根。
//element:指向空间存放要插入结点的值,或已存在结点的传出值。
//isHigher:在执行插入函数后,返回树是否长高。
int LSZ_search_insertBSBT(LSZ_SearchBBTNode **tree,
                            LSZ_SearchE *element,
                            int *isHigher)
{
    int result; //记录调用插入函数的返回值,判断是否执行了插入操作

    if((*tree) == NULL){ //空树
        if((*tree = (LSZ_SearchBBTNode*)malloc(
                sizeof(LSZ_SearchBBTNode))) == NULL){
            return -1;
        }
        if(((*tree)->element = (LSZ_SearchE*)malloc(
                sizeof(LSZ_SearchE))) == NULL){
            free(*tree);
            return -1;
        }
        (*tree)->lChild = (*tree)->rChild = NULL;
        *((*tree)->element) = *element;
        (*tree)->fBalance = LSZ_SEARCH_EQUALHIGH; //新插入结点的平衡因子
        *isHigher = LSZ_SEARCH_HIGHERTRUE; //树的高度发生变化
        return 1;
    }
    else{ //树非空
        if((*tree)->element->key == element->key){
            *element = *((*tree)->element);
            *isHigher = LSZ_SEARCH_HIGHERFALSE;
            return 0; //已存在结点,查找成功
        }
        if((*tree)->element->key > element->key){ //往左子树中插入
            if((result = LSZ_search_insertBSBT(&((*tree)->lChild), element, isHigher)) == -1){
                return -1; //插入失败
            }
            if(*isHigher == LSZ_SEARCH_HIGHERTRUE){ //左子树变高,需要修改平衡因子和判断整棵树是否长高
                switch((*tree)->fBalance){ //原来树根的平衡因子
                    case LSZ_SEARCH_LEFTHIGH:
                        LSZ_search_lBalanceBSBT(tree); //需要进行左平衡处理
                        *isHigher = LSZ_SEARCH_HIGHERFALSE;
                        break;
                    case LSZ_SEARCH_EQUALHIGH:
                        (*tree)->fBalance = LSZ_SEARCH_LEFTHIGH;
                        *isHigher = LSZ_SEARCH_HIGHERTRUE;
                        break;
                    case LSZ_SEARCH_RIGHTHIGH:
                        (*tree)->fBalance = LSZ_SEARCH_EQUALHIGH;
                        *isHigher = LSZ_SEARCH_HIGHERFALSE;
                        break;
                }
            }
        }
        else{ //往右子树插入
            if((result = LSZ_search_insertBSBT(&((*tree)->rChild), element, isHigher)) == -1){
                return -1; //插入失败
            }
            if(*isHigher == LSZ_SEARCH_HIGHERTRUE){ //右子树变高,需要修改平衡因子和判断整棵树是否长高
                switch((*tree)->fBalance){ //原来树根的平衡因子
                    case LSZ_SEARCH_LEFTHIGH:
                        (*tree)->fBalance = LSZ_SEARCH_EQUALHIGH;
                        *isHigher = LSZ_SEARCH_HIGHERFALSE;
                        break;
                    case LSZ_SEARCH_EQUALHIGH:
                        (*tree)->fBalance = LSZ_SEARCH_RIGHTHIGH;
                        *isHigher = LSZ_SEARCH_HIGHERTRUE;
                        break;
                    case LSZ_SEARCH_RIGHTHIGH:
                        LSZ_search_rBalanceBSBT(tree); //需要进行右平衡处理
                        *isHigher = LSZ_SEARCH_HIGHERFALSE;
                        break;
                }
            }                                                                                         
        }
        return result;
    }
}
           
//二叉排序平衡树带删除的查找
//
//(*tree):指向需要被查找的树的树根的指针的地址。
//       注意(*tree)必须在原来的树结构中并指向树根。
//element:指向空间存放要删除结点的值。
//isHigher:在执行插入函数后,返回树是否长高。
int LSZ_search_deleteBSBT(LSZ_SearchBBTNode **tree,
                            LSZ_SearchE *element,
                            int *isLower)
{
    int result; //记录调用删除函数的返回值,判断是否执行了删除操作
    LSZ_SearchBBTNode *nodeDelete, **nodeFind;
    LSZ_SearchE *elementTemp; //用于替换结点和删除结点的交换

    if((*tree) == NULL){ //空树
        *isLower = LSZ_SEARCH_LOWERFALSE; //树的高度未发生变化
        return 0;
    }
    else{ //树非空
        if((*tree)->element->key == element->key){ //找到删除结点
            nodeDelete = *tree;
            *element = *((*tree)->element);
            //下面的删除要按照二叉排序树的删除方法
            if(nodeDelete->lChild == NULL || nodeDelete->rChild == NULL){
                if(nodeDelete->lChild == NULL){
                    *tree = nodeDelete->rChild;
                    if(*tree != NULL){ //相当于少一右孩子,防止删除根结点平衡因子不更新出错
                        (*tree)->fBalance += LSZ_SEARCH_BALANCEDIFF;
                    } //因为要操作平衡因子,防止两结点都为空的情况
                }
                else{ //删除结点无右孩子
                    *tree = nodeDelete->lChild;
                    if(*tree != NULL){ //相当于少一左孩子,防止删除根结点平衡因子不更新出错
                        (*tree)->fBalance -= LSZ_SEARCH_BALANCEDIFF;
                    } //因为要操作平衡因子,防止两结点都为空的情况
                }
                *isLower = LSZ_SEARCH_LOWERTRUE; //高度变化
                LSZ_SEARCH_DESTROYNODE(nodeDelete);
                return 1; //删除成功
            }
            else{ //删除结点同时有左右孩子,注意平衡因子的调整
                nodeFind = &(nodeDelete->lChild);
                if((*nodeFind)->rChild == NULL){ //删除结点的后继的特殊情况
                    *tree = (*nodeFind);
                    (*tree)->fBalance -= 1; //相当于少一左孩子,防止删除根结点平衡因子出错
                    (*tree)->rChild = nodeDelete->rChild;
                    LSZ_SEARCH_DESTROYNODE(nodeDelete);
                    *isLower = LSZ_SEARCH_LOWERTRUE; //高度变化
                    return 1;
                }
                else{ //删除结点后继的一般情况
                    while((*nodeFind)->rChild != NULL){
                        (*nodeFind) = (*nodeFind)->rChild;
                    }
                    //删除结点的值赋予替换结点的值,但不包括平衡因子
                    (*nodeFind)->fBalance = nodeDelete->fBalance;
                    elementTemp = nodeDelete->element; //交换元素
                    nodeDelete->element = (*nodeFind)->element;
                    (*nodeFind)->element = elementTemp;
                    goto ToDeleteLChild; //为了利用函数递归的堆栈,强制转跳,最终要删除的结点为替换结点
                } //一般情况
            } //删除结点同时有左右孩子
        } //找到删除结点
        if((*tree)->element->key > element->key){ //往左子树中删除
ToDeleteLChild:   result = LSZ_search_deleteBSBT(&((*tree)->lChild), element, isLower);
            if(*isLower == LSZ_SEARCH_LOWERTRUE){ //左子树变矮,需要修改平衡因子和判断整棵树是否变矮
                switch((*tree)->fBalance){ //原来树根的平衡因子
                    case LSZ_SEARCH_LEFTHIGH:
                        (*tree)->fBalance = LSZ_SEARCH_EQUALHIGH;
                        *isLower = LSZ_SEARCH_LOWERTRUE;
                        break;
                    case LSZ_SEARCH_EQUALHIGH:
                        (*tree)->fBalance = LSZ_SEARCH_RIGHTHIGH;
                        *isLower = LSZ_SEARCH_LOWERFALSE;
                        break;
                    case LSZ_SEARCH_RIGHTHIGH:
                        LSZ_search_rBalanceBSBT(tree); //需要进行右平衡处理
                        *isLower = LSZ_SEARCH_LOWERTRUE;
                        break;
                }
            }
        } //往左子树删除
        else{ //往右子树删除
            result = LSZ_search_deleteBSBT(&((*tree)->rChild), element, isLower);
            if(*isLower == LSZ_SEARCH_LOWERTRUE){ //右子树变矮,需要修改平衡因子和判断整棵树是否变矮
                switch((*tree)->fBalance){ //原来树根的平衡因子
                    case LSZ_SEARCH_LEFTHIGH:
                        LSZ_search_lBalanceBSBT(tree); //需要进行左平衡处理
                        *isLower = LSZ_SEARCH_LOWERTRUE;
                        break;
                    case LSZ_SEARCH_EQUALHIGH:
                        (*tree)->fBalance = LSZ_SEARCH_LEFTHIGH;
                        *isLower = LSZ_SEARCH_LOWERFALSE;
                        break;
                    case LSZ_SEARCH_RIGHTHIGH:
                        (*tree)->fBalance = LSZ_SEARCH_EQUALHIGH;
                        *isLower = LSZ_SEARCH_LOWERTRUE;
                        break;
                }                                                                                        
            }
        } //往右子树删除
        return result;
    } //树非空
}
           
//左子树(长高)导致失衡时的左平衡调整函数
//
//(*tree):指向最小的失衡树的树根。
//       注意(*tree)必须在原来的树结构中。
void LSZ_search_lBalanceBSBT(LSZ_SearchBBTNode **tree)
{
    LSZ_SearchBBTNode **lChild;

    lChild = &((*tree)->lChild); //最小非平衡树的左孩子
    switch((*lChild)->fBalance){ //检查左子树,确定单向旋转还是双旋转
        case LSZ_SEARCH_LEFTHIGH: //新结点插入在左左孩子上,只需单向右旋
            (*tree)->fBalance = (*lChild)->fBalance = LSZ_SEARCH_EQUALHIGH;
            LSZ_search_rRotateBSBT(tree); //经过旋转,(*tree)已改变
            break;
        case LSZ_SEARCH_RIGHTHIGH: //新结点插入在左右孩子上,需要进行双旋转
            switch((*lChild)->rChild->fBalance){ //需要判断和调整平衡因子
                case LSZ_SEARCH_LEFTHIGH: //新结点插入在左右孩子的左孩子上
                    (*tree)->fBalance = LSZ_SEARCH_RIGHTHIGH;
                    (*lChild)->fBalance = LSZ_SEARCH_EQUALHIGH;
                    break;
                case LSZ_SEARCH_EQUALHIGH:
                    (*tree)->fBalance = (*lChild)->fBalance = LSZ_SEARCH_EQUALHIGH;
                    break;
                case LSZ_SEARCH_RIGHTHIGH:
                    (*tree)->fBalance = LSZ_SEARCH_EQUALHIGH;
                    (*lChild)->fBalance = LSZ_SEARCH_LEFTHIGH;
                    break;
            }
            (*lChild)->rChild->fBalance = LSZ_SEARCH_EQUALHIGH;
            LSZ_search_lRotateBSBT(lChild);
            LSZ_search_rRotateBSBT(tree);
            break;
    }
}
           
//右子树(长高)导致失衡时的左平衡调整函数
//
//(*tree):指向最小的失衡树的树根。
//       注意(*tree)必须在原来的树结构中。
void LSZ_search_rBalanceBSBT(LSZ_SearchBBTNode **tree)
{
    LSZ_SearchBBTNode **rChild;

    rChild = &((*tree)->rChild); //最小非平衡树的右孩子
    switch((*rChild)->fBalance){ //检查左子树,确定单向旋转还是双旋转
        case LSZ_SEARCH_LEFTHIGH: //新结点插入在右左孩子上,需要进行双旋转
            switch((*rChild)->lChild->fBalance){ //需要判断和调整平衡因子
                case LSZ_SEARCH_LEFTHIGH:
                    (*tree)->fBalance = LSZ_SEARCH_EQUALHIGH;
                    (*rChild)->fBalance = LSZ_SEARCH_RIGHTHIGH;
                    break;
                case LSZ_SEARCH_EQUALHIGH:
                    (*tree)->fBalance = (*rChild)->fBalance = LSZ_SEARCH_EQUALHIGH;
                    break;
                case LSZ_SEARCH_RIGHTHIGH:
                    (*tree)->fBalance = LSZ_SEARCH_LEFTHIGH;
                    (*rChild)->fBalance = LSZ_SEARCH_EQUALHIGH;
                    break;
            }
            (*rChild)->lChild->fBalance = LSZ_SEARCH_EQUALHIGH;
            LSZ_search_rRotateBSBT(rChild);
            LSZ_search_lRotateBSBT(tree);
            break;
        case LSZ_SEARCH_RIGHTHIGH: //新结点插入在右右孩子上,只需单向左旋
            (*tree)->fBalance = (*rChild)->fBalance = LSZ_SEARCH_EQUALHIGH;
            LSZ_search_lRotateBSBT(tree); //经过旋转,(*tree)已改变
            break;
    }
}
           
//单向左旋平衡操作函数
//
//(*tree):指向需要旋转的树的树根。
//      注意(*tree)必须在原来的树结构中并指向树根。
void LSZ_search_lRotateBSBT(LSZ_SearchBBTNode **tree)
{
    LSZ_SearchBBTNode *axesNode; //旋转轴结点

    axesNode = (*tree)->rChild; //取得旋转轴结点
    (*tree)->rChild = axesNode->lChild; //旋转结点接管轴结点的左孩子
    axesNode->lChild = *tree; //旋转轴结点指向原根结点
    *tree = axesNode; //旋转轴作为新根结点
}
           
//单向右旋平衡操作函数
//
//(*tree):指向需要旋转的树的树根。
//      注意(*tree)必须在原来的树结构中并指向树根。
void LSZ_search_rRotateBSBT(LSZ_SearchBBTNode **tree)
{   
    LSZ_SearchBBTNode *axesNode; //旋转轴结点
    
    axesNode = (*tree)->lChild; //取得旋转轴结点
    (*tree)->lChild = axesNode->rChild; //旋转结点接管轴结点的右孩子
    axesNode->rChild = *tree; //旋转轴结点指向原根结点
    *tree = axesNode; //旋转轴作为新根结点
}
           
//销毁二叉排序平衡树
//
//tree:指向树的头结点的指针,树的销毁用后续遍历。
//     这里用递归实现后根遍历。
void LSZ_search_destroyBSBT(LSZ_SearchBBTNode *tree)
{
    if(tree->lChild != NULL)
        LSZ_search_doDestroyBSBT(tree->lChild);
    tree->lChild = NULL; //重新设置空指向
}
void LSZ_search_doDestroyBSBT(LSZ_SearchBBTNode *tree)
{
    if(tree->lChild != NULL){ //先销毁左子树
        LSZ_search_doDestroyBSBT(tree->lChild);
    }
    if(tree->rChild != NULL){ //再销毁右子树
        LSZ_search_doDestroyBSBT(tree->rChild);
    }
    //最后销毁根结点
    LSZ_SEARCH_DESTROYNODE(tree);
}
           

本博客的版权声明 点击打开链接

继续阅读