堆之左式堆和斜堆

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;
}

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

时间: 2024-10-16 03:28:15

堆之左式堆和斜堆的相关文章

结构之美——优先队列基本结构(四)——二叉堆、d堆、左式堆、斜堆

实现优先队列结构主要是通过堆完成,主要有:二叉堆.d堆.左式堆.斜堆.二项堆.斐波那契堆.pairing 堆等. 1. 二叉堆 1.1. 定义 完全二叉树,根最小. 存储时使用层序. 1.2. 操作 (1). insert(上滤) 插入末尾 26,不断向上比较,大于26则交换位置,小于则停止. (2). deleteMin(下滤) 提取末尾元素,放在堆顶,不断下滤: (3). 其他操作: 都是基于insert(上滤)与deleteMin(下滤)的操作. 减小元素:减小节点的值,上滤调整堆. 增大

转载:数据结构 左式堆

简介 设计一种堆结构像二叉堆那样高效的支持合并操作而且只使用一个数组似乎很困难.原因在于,合并似乎需要把一个数组拷贝到另一个数组中去,对于相同大小的堆,这将花费O(N).正因为如此,所有支持高效合并的高级数据结构都需要使用指针. 像二叉堆那样,左式堆也有结构性和堆序性.不仅如此,左式堆也是二叉树,它和二叉堆之间的唯一区别在于:左式堆不是理想平衡的,而实际上是趋向于非常不平衡. 左式堆性质 把任意节点X的零路径长(null path length, NPL)Npl(X)定义为从X到一个没有两个儿子

My集合框架第六弹 左式堆

左式堆(Leftist Heaps)又称作最左堆.左倾堆.左式堆作为堆的一种,保留了堆的一些属性. 第1,左式堆仍然以二叉树的形式构建: 第2,左式堆的任意结点的值比其子树任意结点值均小(最小堆的特性).但和一般的二叉堆不同,左式堆不再是一棵完全二叉树(Complete tree),而且是一棵极不平衡的树. package com.wpr.collection; /** * 左式堆:二叉堆缺点,首先,只能查找最小元素:其次,将两个堆合并的操作很麻烦 * 注意:所有支持有效合并的高级数据结构都需要

11、【堆】斜堆

一.斜堆的介绍 斜堆(Skew heap)也叫自适应堆(self-adjusting heap),它是左倾堆的一个变种.和左倾堆一样,它通常也用于实现优先队列.它的合并操作的时间复杂度也是O(lg n). 相比于左倾堆,斜堆的节点没有"零距离"这个属性.除此之外,它们斜堆的合并操作也不同.斜堆的合并操作算法如下: (1) 如果一个空斜堆与一个非空斜堆合并,返回非空斜堆. (2) 如果两个斜堆都非空,那么比较两个根节点,取较小堆的根节点为新的根节点.将"较小堆的根节点的右孩子&

P3377 【模板】左偏树(可并堆) 左偏树浅谈

因为也是昨天刚接触左偏树,从头理解,如有不慎之处,跪请指教. 左偏树: 什 么是(fzy说)左偏树啊? 前置知识: 左偏树中dist:表示到右叶点(就是一直往右下找,最后一个)的距离,特别的,无右节点的为0. 堆:左偏树是个堆. 关于左偏性质:可以帮助堆合并(研究深了我也不懂的,看代码理解) 对于任意的节点,dist[leftson]>=dist[rightson],体现了左偏性质. 同理可得:对于任意右儿子的父亲节点的dist自然等于右儿子的dist+1喽 关于各种操作: merge: 是插入

左式堆

零路径长:从X到一个不具有两个儿子的结点的最短路径的长. 性质: 任一结点的零路径长比他的诸儿子结点的零路径长的最小值多1 父节点属性值小于子节点属性值: 堆中的任何节点,其左儿子的零路径长>=右儿子的零路径长:的二叉树. 下面是左式堆的类型声明: 1 template <typename Comparable> 2 class LeftistHeap 3 { 4 public: 5 LeftistHeap(); 6 LeftistHeap(const LeftistHeap &

数据结构--左式堆的思想和代码

左式堆也是实现优先列队的一种数据结构,和二叉堆一样,左式堆也具有堆序性和结构性. 堆序性: 一个节点的后裔都大于等于这个节点. 结构性:左式堆也是二叉树,和二叉堆的唯一区别在于左式堆不是理想平衡的,实际上是趋于非常不平衡,对于堆中每一个节点X,左儿子的零路径长至少与右儿子的零路径长一样大,零路径长Npl的定义为:节点到一个没有两个儿子的节点的最短路径长,因此具有0个或者1个儿子节点的Npl为0,Npl(NULL) = -1. 左式堆可以高效的支持合并操作,而插入和删除操作都可以通过合并操作来实现

左式堆的实现与详解

定义:左式堆(Leftist Heaps)又称作最左堆.左倾堆,是计算机语言中较为常用的一个数据结构.左式堆作为堆的一种,保留了堆的一些属性.第1,左式堆仍然以二叉树的形式构建:第2,左式堆的任意结点的值比其子树任意结点值均小(最小堆的特性).但和一般的二叉堆不同,左式堆不再是一棵完全二叉树(Complete tree),而且是一棵极不平衡的树. 性质: 零路径长:从X到一个不具有两个儿子的结点的最短路径的长. 1. 任一结点的零路径长比他的诸儿子结点的零路径长的最小值多1 2. 父节点属性值小

[BZOJ1455]罗马游戏-斜堆/左偏树-并查集(+数据生成器)

Problem 遗产 题目大意 罗马皇帝很喜欢玩杀人游戏. 他的军队里面有n个人,每个人都是一个独立的团.最近举行了一次平面几何测试,每个人都得到了一个分数. 皇帝很喜欢平面几何,他对那些得分很低的人嗤之以鼻.他决定玩这样一个游戏. 它可以发两种命令: 1. Merger(i, j).把i所在的团和j所在的团合并成一个团.如果i, j有一个人是死人,那么就忽略该命令. 2. Kill(i).把i所在的团里面得分最低的人杀死.如果i这个人已经死了,这条命令就忽略. 皇帝希望他每发布一条kill命令