【序】

上节我们实现了数据结构中最简单的Vector,那么来到第三章,我们需要实现一个Set

set的特点是 内部有序且有唯一元素值;同时各种操作的期望操作时间复杂度在O(n·logn);

那么标准的C++ STL(Standard Template Library)  容器内部使用的是什么呢?

STL使用的是红黑树或者hash Tree ,由于笔者现在的水平和精力,没时间搞这个啦,于是我就

挑了一个稍微熟悉一点的数据结构:AVL 树;

github:https://github.com/KimAlittleStar/cstd

【1.介绍】

AVL 树是根据二叉查找树改进延伸过来的,我们都知道二叉查找树中只有一个规则,

那就是根节点的元素一定会大于左孩子,小于右孩子;如果是随机数,那么我们二叉查找树的高度无限

接近于 logN(完全二叉树)。但是由于其策略,在反复的插入和删除后,普通的二叉查找树将会非常

偏科(偏向右子树)如果树的高度非常大(height == N) 那么他和链表的操作时间没有什么区别,

为了避免普通二叉查找树的缺陷,因此引申出 AVL Tree -> 平衡二叉查找树;

AVL 和普通的二叉查找树只有一个规则限制:相同节点的两个孩子的高度最大相差为1,本身节点的高度

为两个孩子节点中大的那个高度加1;

【2.基础数据结构】

使用一个node 和 正常的tree 用于管理他的根和size;node 中有左孩子 右孩子和数据还有一个记录树的

高度字节u8(unsigned char),为什么我们使用u8,因为假设在满足AVL的规则要求下,坏的情况树的深度为

deep = logn *2 +1 ; 去除一个起始位0 ,后期可能会用到的错误位 -1,那么在最差的情况下 u8 类型的高度可以存储

大概多少个节点呢: 

N = 2^254 (个)而我们在tree 中 size 的类型是 u32 (unsigned int)完全够用了;

参考下图:(数据结构与算法C++.pdf)

 

 

 

typedef unsigned int typeClass;

typedef struct __SET_typeClass_node
{
    typeClass data;
    struct __SET_typeClass_node *left;
    struct __SET_typeClass_node *right;
    u8 heigh;
} SET_typeClass_node_t;

typedef struct __SET_typeClass_t
{
    SET_typeClass_node_t *root;
    u32 size;
} SET_typeClass_t;

 

【3 插入】

数据的插入还是依照我们普通的二叉树进行插入,递归判断我们的数据是否小于当前节点,

小于,递归往左走,大于,大于递归往右走,等于,不操作,真正的基础情况是判断是发现当前

节点为NULL 此时申请内存存储该元素;

 

 

SET_typeClass_node_t *SET_inserttypeClass_node_t(SET_typeClass_node_t *root,
                                                 u8 (*compare)(const typeClass *a, const typeClass *b),
                                                 const typeClass *value, u32 *size)
{
    if (root == NULL)
    {
        root = (SET_typeClass_node_t *)malloc(sizeof(SET_typeClass_node_t));
        if (root == NULL)
        {
            // Out of space!!!
        }
        else
        {
            root->data = (*value);
            root->left = root->right = NULL;
            (*size)++;
        }
        return root;
    }
    else if (compare(value, &root->data))
    {
        root->left = SET_inserttypeClass_node_t(root->left, compare, value, size);
    }
    else if (compare(&root->data, value))
    {
        root->right = SET_inserttypeClass_node_t(root->right, compare, value, size);
    }
    return root;
}
普通二叉树插入

 

当我们在做完这一切之后,还需要做的是维护AVL的性质:左右节点高度相差最大为1;且需要

递归查询哦。于是乎我们接下来第一步要做的是什么呢?

更新我们自己的节点的高度;

root->heigh = SET_Max(SET_heighttypeClass(root->left),
                          SET_heighttypeClass(root->right)) +
                  1;

 

更新之后节点之后我们就会发现,哎呀,有些时候(大多数时候)AVL树的规则被破坏了,那么该

就需要处理重新符合AVL树的规则;插入之后呢可能会出现四种情况,其中两两镜像,因此我们只讨论

两种;

 

 

  

 

 

 

 

 

以上两种情况分别为 LL ,LR,具体判断标准为:观察高度开始不符合的节点,8号节点和 K2节点,

这里说的高度不符的节点表示:左右孩子的高度相差 >1  那么他们偏离的子节点的方向分别是

8号:Left->Left  k2->Left->Right;  我们在处理LL情况的时候,只需要将7号变成5号的右孩子;

8号变成7号的右孩子即可,相当于6~7~8号顺时针旋转了一下,我们把这个定义 ”左单旋“,旋转后

变成如下图:

 

相对应的也有 ”右单旋“咯。大家自己推导啦;

接下来看我们的LR情况,如果仅仅对K2执行一次左单旋,那么结果是:

 

 但是这样依旧没有满足 AVL 树的性质;K2的高度会比 X大超过1;此时我们需要引进一个k1的右节点进行双旋转;

 

我们首先把 K1 k2 B 进行一次右单旋;得到

 

 然后我们在将C~k2~k3进行一次左旋:

 

 相对应的我们也会有 RL的情况,镜像情况我们就不赘述咯;

下面代码是单旋、双旋的实现:

SET_typeClass_node_t *SET_doubleRotateLefttypeClass(SET_typeClass_node_t *s)
{
    s->left = SET_singleRotateRighttypeClass(s->left);
    return SET_singleRotateLefttypeClass(s);
}
SET_typeClass_node_t *SET_doubleRotateRighttypeClass(SET_typeClass_node_t *s)
{
    s->right = SET_singleRotateLefttypeClass(s->right);
    return SET_singleRotateRighttypeClass(s);
}

SET_typeClass_node_t *SET_singleRotateLefttypeClass(SET_typeClass_node_t *s)
{
    SET_typeClass_node_t *s1;
    s1 = s->left;
    s->left = s1->right;
    s1->right = s;

    s->heigh = SET_Max(
                   SET_heighttypeClass(s->left),
                   SET_heighttypeClass(s->right)) +
               1;
    s1->heigh = SET_Max(
                    SET_heighttypeClass(s1->left),
                    s->heigh) +
                1;
    return s1;
}

SET_typeClass_node_t *SET_singleRotateRighttypeClass(SET_typeClass_node_t *s)
{
    SET_typeClass_node_t *s1;
    s1 = s->right;
    s->right = s1->left;
    s1->left = s;

    s->heigh = SET_Max(
                   SET_heighttypeClass(s->left),
                   SET_heighttypeClass(s->right)) +
               1;
    s1->heigh = SET_Max(
                    SET_heighttypeClass(s1->right),
                    s->heigh) +
                1;
    return s1;
}

 

 

综合以上,我们最后insert的代码就成了:

SET_typeClass_node_t *SET_inserttypeClass_node_t(SET_typeClass_node_t *root,
                                                 u8 (*compare)(const typeClass *a, const typeClass *b),
                                                 const typeClass *value, u32 *size)
{
    if (root == NULL)
    {
        root = (SET_typeClass_node_t *)malloc(sizeof(SET_typeClass_node_t));
        if (root == NULL)
        {
            // Out of space!!!
        }
        else
        {
            root->data = (*value);
            root->left = root->right = NULL;
            root->heigh = 1;
            (*size)++;
        }
        return root;
    }
    else if (compare(value, &root->data))
    {
        root->left = SET_inserttypeClass_node_t(root->left, compare, value, size);
        if (SET_heighttypeClass(root->left) - SET_heighttypeClass(root->right) == 2)
        {
            if (compare(value, &root->left->data))
                root = SET_singleRotateLefttypeClass(root);
            else
                root = SET_doubleRotateLefttypeClass(root);
        }
    }
    else if (compare(&root->data, value))
    {
        root->right = SET_inserttypeClass_node_t(root->right, compare, value, size);
        if (SET_heighttypeClass(root->right) - SET_heighttypeClass(root->left) == 2)
        {
            if (compare(&root->right->data, value))
                root = SET_singleRotateRighttypeClass(root);
            else
                root = SET_doubleRotateRighttypeClass(root);
        }
    }
    root->heigh = SET_Max(SET_heighttypeClass(root->left),
                          SET_heighttypeClass(root->right)) +
                  1;
    return root;
}
AVL inser操作

 

然后我们使用 tree 将其包装,并且返回是否插入成功;插入不成功有两种情况(内存空间不足和元素已存在)

u8 SET_inserttypeClass_t(SET_typeClass_t *set, const typeClass ele)
{
    if (set == NULL || set->compare == NULL)
        return 0;
    u32 cursize = set->size;
    set->root = SET_inserttypeClass_node_t(set->root, set->compare, &ele, &set->size);
    return (cursize < set->size);
}
AVL insert 封装

 

 

【4 删除】

删除的操作呢,比插入要更加复杂一些;但是我们依旧是从基础的二叉树删除来入手;普通的二叉树首先

递归寻找到数据,然后将数据分成两种情况:该元素有两个孩子和该元素没有两个孩子;没有两个孩子的逻辑就

很简单,判断当前左孩子是否为空,是:将元素的指针指向他的右孩子,否则指向左孩子;如果两个孩子都为NULL,

指向谁都一样;

如果是有两个孩子的呢?那么我们就将这个元素下寻找到他最小的一个子辈(可能是他的孩子、孙子、曾孙、玄孙。。。)

然后把他的子辈赋值给他,然后删除他的那个子辈;因为他的子辈一定是左孩子为NULL的;

以下为实现:

SET_typeClass_node_t *SET_removetypeClass_node_t(SET_typeClass_node_t *root,
                                                 u8 (*compare)(const typeClass *a, const typeClass *b),
                                                 void (*deleteSub)(const typeClass *ele),
                                                 const typeClass *value, u32 *size)
{
    if (root == NULL)
    {
        // no has this value
    }
    else if (compare(value, &root->data))
    {
        root->left = SET_removetypeClass_node_t(root->left, compare, deleteSub, value, size);
    }
    else if (compare(&root->data, value))
    {
        root->right = SET_removetypeClass_node_t(root->right, compare, deleteSub, value, size);
    }
    else
    {
        /*real delete option*/
        if (root->right != NULL && root->left != NULL)
        {
            /* has two child */
            SET_typeClass_node_t *temp = root;
            while (temp->left != NULL)
            {
                temp = temp->left;
            }
            if (deleteSub != NULL)
                deleteSub(&root->data);
            root->data = temp->data;

            /* deleteSub == NULL because this min not to free ,just become root->data; */
            root->left = SET_removetypeClass_node_t(root->left, compare, NULL, &root->data, size);
        }
        else
        {
            /* has only child or no child */
            SET_typeClass_node_t *t = (root->right == NULL) ? (root->left) : (root->right);
            if (deleteSub != NULL)
                deleteSub(&root->data);
            free(root);
            (*size)--;
            root = t;
        }
    }

    return root;
}
普通二叉树remove节点

 删除完节点之后,我们依旧需要考虑的问题是平衡的问题;删除会出现什么问题呢?我们上述的删除到最后

一定以删除一个在末梢的节点(孩子最多只有一个),因此我们只需要考虑此情况即可;

我们可以换一个思路,其实删除带来的影响就是在树的另一边插入了一个值;那么相对应的,那边高度高了我

就往哪边旋转就好了;只不过我们判断的时候如果是删除左边,那么我们就要判断当前是不是右边高度-左边高度 > 2 就好了。

嗯~很简单是不是?

不不不,在insert中我们判断是否需要使用双旋转的依据是:

root->right = SET_inserttypeClass_node_t(root->right, compare, value, size);
if (SET_heighttypeClass(root->right) - SET_heighttypeClass(root->left) == 2)
{
  if (compare(&root->right->data, value))
    root = SET_singleRotateRighttypeClass(root);
  else
    root = SET_doubleRotateRighttypeClass(root);
}

 

因为在插入时,如果出现的不平衡,那么假设插入的值比当前root的右孩子要大,那么就会插入在右孩子的左边;

就会出现下图所示 (插入了 14 )此时需要双旋转,是因为k1的右孩子高度高且右孩子的左侧比右侧要高;这句话有

点绕口,我们分成两步,

第一步:k1节点破坏了AVL树的规则且需要旋转的是边;

第二步:在k1的右孩子k3上,虽然没有违法AVL的规则,但是他的左孩子高度要比右孩子高度高;这就是我们所说

的RL情况,

所以需要双旋;

 

 

 

那么在删除是,也应遵循此规则,也就是说删除的时候我们就不要比较 删除值的大小啦,因为删除完值之后,

相当于我们需要知道另一侧的情况是不是需要双旋,这个时候怎么办呢?我们就依照上述一二步的原理,判断

左边高度高高于右边高度 >1 了,那么我就看左边孩子的两个孩子的高度是不是右边 > 左边,如果是->双旋转,如果

不是,单旋转;由于有两个孩子的节点到最后也会是删除一个叶子节点,故不需要考虑;

以下是remove判断是否需要双旋转的代码:

if (compare(value, &root->data))
{
    root->left = SET_removetypeClass_node_t(root->left, compare, deleteSub, value, size);
    if (SET_heighttypeClass(root->right) - SET_heighttypeClass(root->left) == 2)
    {
        if (SET_heighttypeClass(root->right->right) > SET_heighttypeClass(root->right->left))
            root = SET_singleRotateRighttypeClass(root);
        else
            root = SET_doubleRotateRighttypeClass(root);
    }    
}
remove是否需要双旋转

 

 

需要我们注意的是在删除有两个孩子的节点的时候,虽然我们是删除,但是我实际上还是调用的递归,一次我们还

是需要判断一次平衡规则;

最后remove的代码如下:

SET_typeClass_node_t *SET_removetypeClass_node_t(SET_typeClass_node_t *root,
                                                 u8 (*compare)(const typeClass *a, const typeClass *b),
                                                 void (*deleteSub)(const typeClass *ele),
                                                 const typeClass *value, u32 *size)
{
    if (root == NULL)
    {
        // no has this value
    }
    else if (compare(value, &root->data))
    {
        root->left = SET_removetypeClass_node_t(root->left, compare, deleteSub, value, size);
        if (SET_heighttypeClass(root->right) - SET_heighttypeClass(root->left) == 2)
        {
            if (SET_heighttypeClass(root->right->right) > SET_heighttypeClass(root->right->left))
                root = SET_singleRotateRighttypeClass(root);
            else
                root = SET_doubleRotateRighttypeClass(root);
        }
    }
    else if (compare(&root->data, value))
    {
        root->right = SET_removetypeClass_node_t(root->right, compare, deleteSub, value, size);
        if (SET_heighttypeClass(root->left) - SET_heighttypeClass(root->right) == 2)
        {
            if (SET_heighttypeClass(root->left->left) > SET_heighttypeClass(root->left->right))
                root = SET_singleRotateLefttypeClass(root);
            else
                root = SET_doubleRotateLefttypeClass(root);
        }
    }
    else
    {
        /*real delete option*/
        if (root->right != NULL && root->left != NULL)
        {
            /* has two child */
            SET_typeClass_node_t *temp = root;
            while (temp->left != NULL)
            {
                temp = temp->left;
            }
            if (deleteSub != NULL)
                deleteSub(&root->data);
            root->data = temp->data;

            /* deleteSub == NULL because this min not to free ,just become root->data; */
            root->left = SET_removetypeClass_node_t(root->left, compare, NULL, &root->data, size);
            if (SET_heighttypeClass(root->right) - SET_heighttypeClass(root->left) == 2)
            {
                if (SET_heighttypeClass(root->right->right) > SET_heighttypeClass(root->right->left))
                    root = SET_singleRotateRighttypeClass(root);
                else
                    root = SET_doubleRotateRighttypeClass(root);
            }
        }
        else
        {
            /* has only child or no child */
            SET_typeClass_node_t *t = (root->right == NULL) ? (root->left) : (root->right);
            if (deleteSub != NULL)
                deleteSub(&root->data);
            free(root);
            (*size)--;
            root = t;
        }
    }
    if (root != NULL)
        root->heigh = SET_Max(SET_heighttypeClass(root->left),
                              SET_heighttypeClass(root->right)) +
                      1;
    return root;
}
AVL remove 代码

 

同理使用一个tree对他进行封装,同时返回是否remove成功,remove不成功只有一种可能,那就是set中不存在此元素;

u8 SET_removetypeClass_t(SET_typeClass_t *set, const typeClass ele)
{
    if (set == NULL || set->compare == NULL)
        return 0;
    u32 cursize = set->size;
    set->root = SET_removetypeClass_node_t(set->root, set->compare, set->deleteSub, &ele, &set->size);
    return (cursize > set->size);
}

 

在AVL树中最重要的就是插入和删除啦;

其他的像 delete 遍历呀,find findMax之类的太简单我就不说啦。基本上就是使用遍历或者一直往右找或往左找;

最后我考虑到上文中SET_typeClass_t是一个类型,所以所有的比较都抽象化了compare函数,还有有可能在删除的时候

需要释放SET_typeClass_t中指向的空间,所以抽象化成为函数指针deleteSub;

 

【结束语】

下一节就是将我们今天所实现的这些函数变成宏,完成 STL 数据容器 SET

 

 

目录

 

1.引言

 

2.1 C语言_实现简单基础的vector

 

2.2 C语言_实现数据容器vector(排序功能)

 

3.1 C语言_实现AVL平衡二叉树

 

3.2 C语言_实现数据容器set(基础版)

 

4 C语言_实现简单基础的map

 

 

 

 参考资料 : 数据结构与算法C++实现.pdf;

 

 

 

 

 

 

posted on 2019-10-25 21:16  Kim_小星兴  阅读(827)  评论(0编辑  收藏  举报