• 堆之左式堆和斜堆


    d-堆

    类似于二叉堆,但是它有d个儿子,此时,d-堆比二叉堆要浅很多,因此插入操作更快了,但是相对的删除操作更耗时。因为,需要在d个儿子中找到最大的,但是很多算法中插入操作要远多于删除操作,因此,这种加速是现实的。

    除了不能执行find去查找一般的元素外,两个堆的合并也很困难。

    左式堆

    左式堆可以有效的解决上面说的堆合并的问题。合并就涉及插入删除,很显然使用数组不合适,因此,左式堆使用指针来实现。左式堆和二叉堆的区别:左式堆是不平衡的。它两个重要属性:键值和零距离

    零距离(英文名NPL,即Null Path Length)则是从一个节点到一个没有两个儿子的节点(只有0个或1个儿子的节点)的路径长度。具有0个或1个儿子的节点的NPL为0,NULL节点的NPL为-1。

    • 节点的左孩子的NPL >= 右孩子的NPL。
    • 节点的NPL = 它的右孩子的NPL + 1。
    • 在有路径上有r个节点的左式堆必然至少有2^r - 1个节点。
    typedef int Type;
    
    typedef struct _LeftistNode{
        Type val;
        int npl;                    // 零路经长度(Null Path Length)
        struct _LeftistNode *left;    // 左孩子
        struct _LeftistNode *right;    // 右孩子
    }LeftistNode, *LeftistHeap;

    合并

    合并操作是左倾堆的重点。插入式合并的特殊情况。

    合并两个左倾堆(最小堆)的基本思想如下:

    • 如果一个空左倾堆与一个非空左倾堆合并,返回非空左倾堆。
    • 如果两个左倾堆都非空,那么比较两个根节点,取较小堆的根节点为新的根节点。将"较小堆的根节点的右孩子"和"较大堆"进行合并;该合并过程和上面的过程一样,这样递归合并下去,最终两个堆合并完成。
    • 但是新推可能不再满足左式堆的性质,需要调整:(调整的过程是在合并的同时完成的)
      • 如果新堆的右孩子的NPL > 左孩子的NPL,则交换左右孩子。
      • 设置新堆的根节点的NPL = 右子堆NPL + 1

    实现时,通过递归自底向上合并并调整使得满足左式堆的性质。

    LeftistNode* mergeLeftist(LeftistHeap x, LeftistHeap y){
        if (x == nullptr)return y;
        if (y == nullptr)return x;
    
        LeftistHeap l, r;//以l为根,l较小
        if (x->val < y->val){
            l = x;
            r = y;
        }
        else {
            l = y;
            r = x;
        }
        l->right = mergeLeftist(l->right,r);//合并l->right和r
    
        if (!l->left || l->left->npl < l->right->npl){//判断是否需要交换左右子树
            LeftistHeap temp = l->left;
            l->left = l->right;
            l->right = temp;
        }
        //更新npl
        if (!l->right || !l->left)l->npl = 0;
        else l->npl = l->left->npl > l->right->npl ? l->right->npl + 1 : l->left->npl + 1;
        return l;
    }

    合并左式堆的操作可以看出来,它的时间复杂度和有路径的长成正比,因此复杂度O(logn)

    添加节点就可以看做是一个左式堆和一个单点的左式堆合并;

    删除树根节点可以看做是删除树根后,左右子树的两个左式堆合并;

    因此,他们都可以通过合并来实现。它对应的复杂度也是O(logn)

    插入和删除的实现:

    斜堆

    斜堆是左式堆的自调节形式,左式堆和斜堆的关系类似于伸展树和AVL树的关系。斜堆具有堆序的性质,但是没有结构的限制,这样的话一次的操作最坏的情况时O(n),但是连续m次操作总的复杂度O(mlogn)。

    与左式堆相同,斜堆的基本操作也是合并操作。但是斜堆没有零距离的属性,合并的方法也有区别:

    • 如果一个空斜堆与一个非空斜堆合并,返回非空斜堆。
    • 如果两个斜堆都非空,那么比较两个根节点,取较小堆的根节点为新的根节点。将"较小堆的根节点的右孩子"和"较大堆"进行合并。
    • 合并后,交换新堆根节点的左孩子和右孩子。
      • 这一步是斜堆和左倾堆的合并操作差别的关键所在,如果是左倾堆,则合并后要比较左右孩子的零距离大小,若右孩子的零距离 > 左孩子的零距离,则交换左右孩子;最后,在设置根的零距离。

    斜堆的结构

    typedef int Type;
    
    typedef struct _SkewNode{
        Type val;
        struct _SkewNode *left;    // 左孩子
        struct _SkewNode *right;   // 右孩子
    }SkewNode, *SkewHeap;

    合并的实现

    SkewNode* mergeSkewHeap(SkewHeap x, SkewHeap y){
        if (x == nullptr)return y;
        if (y == nullptr)return x;
    
        SkewHeap l, r;//以l为根,l较小
        if (x->val < y->val){
            l = x;
            r = y;
        }
        else {
            l = y;
            r = x;
        }
        SkewNode* temp = mergeSkewHeap(l->right, r);//合并l->right和r
    
        l->right = l->left;//交换左右子树
        l->left = temp;
    
        return l;
    }

    同样的道理,插入和删除根节点的操作都可以使用合并来实现。

  • 相关阅读:
    [充电]多线程无锁编程--原子计数操作:__sync_fetch_and_add等12个操作
    [转]架构、框架、模式、模块、组件、插件、控件、中间件的含义
    [网络]网络爬虫
    PHP时间戳和日期转换
    两个input之间有空隙,处理方法
    去除输入框原始效果【原创】
    php做图片上传功能
    PHP获取随机数的函数rand()和mt_rand()
    PHP简单的图片上传
    基于layui的表格异步删除,ajax的简单运用
  • 原文地址:https://www.cnblogs.com/yeqluofwupheng/p/7450643.html
Copyright © 2020-2023  润新知