几种堆结构比较

对于堆大家都不陌生,无非就是最大堆和最小堆之分,堆的使用很广泛,优先队列、求大叔组的前k个数都可以用堆实现,且时间复杂度低。但是对于堆的具体实现存在几种不同的方式,它们各有优势。

根据堆底层的实现可分为顺序存储堆和链式存储堆,链式存储又分为左式堆、斜堆以及二项堆。

1、顺序存储堆就是我们经常接触的堆,即使用数组实现,逻辑上相当于完全二叉树。它的实现以及堆排序大家已经很熟悉了,这里不再赘述,顺序堆的缺点是合并两个堆的时间复杂度比较高,通常从一个堆中逐次取出元素插入到另一个堆中,时间复杂度为O(NlogM)注:假设两个堆元素分别为N,M。或者使用一个M+N的数组,将所有元素存储在数组中然后进行一次初始堆操作,这样虽然降低了时间复杂度,但是需要额外内存开销。

2、链式堆很好的解决了上面的缺陷,同时堆的所有操作可以转化为合并操作。

2.1、左式堆:在介绍左式堆之前先介绍一个概念——零路径长:节点到一个不具有两个儿子的节点   的最短路径长,NULL节点零路径为-1。左式堆要求左子树零路径长不小于右子树,合并两个左式堆时先比较堆顶元素,如果是最小堆(最大堆相反),则将堆顶值小的堆的右子树与另一个堆进行合并,递归进行,直到其中一个堆为NULL,返回,合并后判断左右子树的零路径长,若右子树比左子树长则交换左右子树,同时修改节点零路径值为右子树+1。核心代码下:

struct heapNode{
		int data;
		int zeroPath;
		heapNode* left;
		heapNode* right;
		heapNode(){

		}
		heapNode(int key,int zero = 0,heapNode* l = NULL,heapNode* r = NULL):data(key),zeroPath(zero),left(l),right(r){

		}
};
int getzeroPath(heapNode* root){//获取零路径长
		return root == NULL ? -1 : root->zeroPath;
}
void setzeroPath(heapNode* root){//设置零路径长
                //对于左式堆可以直接root->zeroPath = getzeroPath(root->right)+1,但斜堆求//零路径长时必须比较左右子树,这里为了代码复用,故不做区分
		root->zeroPath = min(getzeroPath(root->left),getzeroPath(root->right)) + 1;
}
void swap(heapNode* &t1,heapNode* &t2){//交换左右子树
		heapNode* t = t1;t1 = t2;t2 = t;
}
heapNode* mergeHeap(heapNode* r1,heapNode* r2){//堆合并算法
		if(r1 == NULL) return r2;
		if(r2 == NULL) return r1;
		if(r1-> data > r2->data){
				r2->right = mergeHeap(r1,r2->right);
				if(getzeroPath(r2->left) < getzeroPath(r2->right)){
						swap(r2->left,r2->right);
				}
				setzeroPath(r2);
				return r2;
		}else{
				r1->right = mergeHeap(r1->right,r2);
				if(getzeroPath(r1->left) < getzeroPath(r1->right))
						swap(r1->left,r1->right);
				setzeroPath(r1);
				return r1;
		}

}

2.2、斜堆:斜堆与左式堆类似,唯一不同在于合并后不管左右子树零路径大小都必须交换左右子树,不同代码如下:

heapNode* mergeHeap(heapNode* r1,heapNode* r2){
		if(r1 == NULL) return r2;
		if(r2 == NULL) return r1;
		if(r1-> data > r2->data){
				r2->right = mergeHeap(r1,r2->right);
				swap(r2->left,r2->right);//与左式堆不同之处
				setzeroPath(r2);
				return r2;
		}else{
				r1->right = mergeHeap(r1->right,r2);
				swap(r1->left,r1->right);//与左式堆不同之处
				setzeroPath(r1);
				return r1;
		}

}

2.3、二项堆:二项堆实际是一个森林,用一个数组或者链表保存所有二叉树的根节点,可以有序, 合并方式与左式堆有点不同,如果是最小堆(最大堆相反),合并时将相同度的二叉树合并,且堆顶值大的堆作为堆顶值小的堆的子树,二项堆不允许有相同度的树存在。

  • 度数为0的二项树只包含一个结点
  • 度数为k的二项树有一个根结点,根结点下有个子女,每个子女分别是度数分别为的二项树的根
  • 度数为k的二项树共有个结点,高度为。在深度d处有二项式系数)个结点。
  • 度数为k的二项树可以很容易从两颗度数为k-1的二项树合并得到:把一颗度数为k-1的二项树作为另一颗原度数为k-1的二项树的最左子树。这一性质是二项堆用于堆合并的基础。
时间: 2024-11-14 12:50:47

几种堆结构比较的相关文章

探秘堆结构

一.概述 此处所说的堆为数据结构中的堆,而非内存分区中的堆.堆通常可以被看做是树结构,满足两个性质:1)堆中任意节点的值总是不大于(不小于)其子节点的值:2)堆是一棵完全树.正是由于这样的性质,堆又被称为优先队列.根据性质一,将任意节点不大于其子节点的堆称为最小堆或最小优先队列,反之称为最大堆或最大优先队列.优先队列在操作系统作业调度的设计中有着举足轻重的作用.之前写了一篇优先队列的文章,详见算法导论第六章优先队列. 常见的堆结构,有二叉堆.左倾堆.斜堆.二项堆.斐波那契堆等.斐波那契堆在前文算

树-堆结构练习——合并果子之哈夫曼树

树-堆结构练习——合并果子之哈夫曼树 Time Limit: 1000MS Memory limit: 65536K 题目描述 在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆.多多决定把所有的果子合成一堆. 每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和.可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了.多多在合并果子时总共消耗的体力等于每次合并所消耗体力之和. 因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节

SDUT 堆结构练习——合并果子之哈夫曼树(丧心病狂heap)

树-堆结构练习--合并果子之哈夫曼树 Time Limit: 1000ms   Memory limit: 65536K  有疑问?点这里^_^ 题目描述 在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆.多多决定把所有的果子合成一堆. 每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和.可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了.多多在合并果子时总共消耗的体力等于每次合并所消耗体力之和. 因为还要花大力气把这些果子搬回家,所以

《Thinking in Algorithm》16.堆结构之斐波那契堆

堆的变体: 二叉堆 二项堆 斐波那契堆 前面的博客中我们讲到的堆的两种变体,二叉堆和二项堆,今天我们要讲的就是著名的斐波那契堆. 依然首先列出了三种堆的时间复杂的比较. 从上面能发现斐波那契堆的时间复杂度在很多操作上有优化,如insert, minimum, union , decrease-key,而extreact-min,delete没有变化. 可能看到这很多人会说为什么有这么好的斐波那契堆,我们还要去研究其他堆呢,确实,理论上来讲,对于extreact-min和delete操作想对于其他

重量平衡树之Treap:以随机优先级来维护堆结构,并满足BST性质

关于重量平衡树的相关概念可以参考姊妹文章:重量平衡树之替罪羊树 Treap是依靠旋转来维护平衡的重量平衡树中最为好写的一中,因为它的旋转不是LL就是RR 对于每一个新的节点,它给这个节点分配了一个随机数,用作优先级,然后以这个优先级来维护一个堆结构 由于堆本身就是完全二叉树结构,这样维护之后的树就无限接近于完全二叉树,所以还是很神奇的 这棵树满足BST的一切性质,除了不能处理序列问题之外已经无敌了 应该说,抛去动态树问题之外,这是实战最好用的树了 我们还是先看定义: struct Tree {

实现堆结构

总时间限制:  3000ms 内存限制:  65535kB 描述 定义一个数组,初始化为空.在数组上执行两种操作: 1.增添1个元素,把1个新的元素放入数组. 2.输出并删除数组中最小的数. 使用堆结构实现上述功能的高效算法. 输入 第一行输入一个整数t,代表测试数据的组数.对于每组测试数据,第一行输入一个整数n,代表操作的次数.每次操作首先输入一个整数type.当type=1,增添操作,接着输入一个整数u,代表要插入的元素.当type=2,输出删除操作,输出并删除数组中最小的元素.1<=n<

最最最最最最最最基础的C---数据类型与运算符,三种基本结构,语句

算法处理的对象是数据,数据是以某种特定的形式存在的 数据类型 1字节(byte)=8位 基本数据类型: 整型  (short2字节, int 4字节, long 4字节) 浮点型(float 4字节.double 8字节,long double 16字节) 字符型(char 8字节) 布尔型(bool true(1)&flase(0)) 枚举类型(enum) 构造数据类型:数组类型,    结构体类型(struct)  共用体类型(union) 其他:        指针类型(*)    空类型

C语言程序的三种基本结构

1.程序结构:在C语言程序中,一共有三种程序结构:顺序结构.选择结构(分支结构).循环结构: 顺序结构:从头到尾一句接着一句的执行下来,直到执行完最后一句: 选择结构:到某个节点后,会根据一次判断的结果来决定之后向哪一个分支方向执行: 循环结构:循环结构有一个循环体,循环体里是一段代码.对于循环结构来说,关键在于根据判断的结果,来决定循环体执行多少次: 注:在逻辑上有一种bool类型(也叫boolean类型,布尔类型),只有两个值,即真和假.C语言的判断表达式最终的值就是一个bool类型,这个判

GO学习笔记 - Go 只有一种循环结构—— for 循环。

一,Go 只有一种循环结构-- for 循环. 官方教程:https://tour.go-zh.org/flowcontrol/1 Go 只有一种循环结构-- for 循环. 基本的 for 循环包含三个由分号分开的组成部分: 初始化语句:在第一次循环执行前被执行 循环条件表达式:每轮迭代开始前被求值 后置语句:每轮迭代后被执行 初始化语句一般是一个短变量声明,这里声明的变量仅在整个 for 循环语句可见. 如果条件表达式的值变为 false,那么迭代将终止. 注意:不像 C,Java,或者 J