博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
优先队列(堆)
阅读量:4878 次
发布时间:2019-06-11

本文共 4052 字,大约阅读时间需要 13 分钟。

优先队列:顾名思义,这个队列中的元素是有优先级的,它和普通的先进先出(FIFO)不一样。我们在很多场合可能都需要用到这种特殊的队列(例如,操作系统的进程调度)。

可以看出来,优先队列(priority queue)的核心操作有两个,分别是插入和删除。插入是显而易见的,删除就是找出这个队列中优先级最高的那个元素(可以是最大的,也可是最小的)。

一些简单的想法:我们可以采用一个简单的链表来实现,表头始终存放优先级最高的元素,这样删除操作的时间复杂度就是O(1),那么相应的插入操作就是O(n)。反过来,插入在表头进行,删除是找出优先级最高元素,这样插入就是O(1),删除就是O(n)。这两种想法都不能得到更好的时间复杂度了。另外,使用BST也可以实现这种操作,但是这通常会使的树变成一颗斜树。导致树的深度很深,也不怎么好用。

二叉堆:完全二叉树经常被用来实现优先队列,因此以至于堆(heap)不加修饰的出现的时候,都是指优先队列这种数据结构。完全二叉树的高度是O(log n)。它非常平衡。这点很重要。另外还有一点就是完全二叉树表现的很有规律。当使用一个数组来保存完全二叉树的时候,从下标为1的地方开始按照层序遍历的方式存储完全二叉树,那么下标为i的节点,它的左儿子在下标为2i的地方保存着,右儿子在下标为2i+1的地方保存着。唯一的缺点是最大的堆的大小需要事先有一个良好的估计。下图是一棵完全二叉树在数组中存储的关系。

我们想快速找出优先级最高的元素,那么优先级最高的放在根上。如果考虑任意的子树也是堆,那么任意节点的优先级都应该高于它的所有后裔。这就是堆序性。在这里我们的堆删除最小元素。因此根节点将比它所有的后裔都小,任意节点也应该小于它所有的后裔。下面给出堆的ADT。

#ifndef HEAP#define HEAP#include
#include
#include
struct HeapNode{ int *Heap; int size; //当前已用容量 int capacity; //总容量};typedef struct HeapNode *priorityqueue;priorityqueue InitHeap(int max); //初始化堆,需要给出堆的最大容量void DestoryHeap(priorityqueue H); //free掉堆void Insert(priorityqueue H,int x); //核心操作,插入int FindMin(priorityqueue H); //这是个附加操作,很容易实现int DeleteMin(priorityqueue H); //核心操作,删除最小元int IsFull(priorityqueue H); //数组实现的弊端,必须判断满不满int IsEmpty(priorityqueue H); //是否空#endif // !HEAP

首先,给出堆的初始化代码,注意,这里是用数组实现的堆。

priorityqueue InitHeap(int max){	priorityqueue H = NULL;	H = (priorityqueue)malloc(sizeof(struct HeapNode));	if (NULL == H)	{		printf("创建堆失败!");		exit(0);	}	else	{		H->Heap = (int *)malloc(sizeof(int)*(max + 1));	//因为从下标为1开始放		if (NULL == H->Heap)		{			printf("创建堆失败!");			exit(0);		}		else		{			H->Heap[0] = INT_MIN;    //限位器作用			H->capacity = max;			H->size = 0;		//初始化没有元素		}	}	return H;}

插入操作:堆的两种基本操作,删除和插入的实现都是简单的,只需要始终保持堆序性即可。首先,插入是在下一个空闲位置创建一个空穴(保证它是完全二叉树)。如果X放在这个空穴处不影响堆序性,那么插入完成。否则将空穴父节点上的元素移动到空穴。一直这样下去,空穴就一直向树根方向移动。直到X能放入该空穴,而不影响堆序性为止。

void Insert(priorityqueue H, int x){	if (IsFull(H))	{		printf("堆已满,无法插入!");        //另一种处理方式是realloc()	}	else	{		//++H->size,创建空穴,判断是否影响堆序性。		int i;		for (i = ++H->size; H->Heap[i / 2] > x; i /= 2)		{			H->Heap[i] = H->Heap[i / 2];	//若影响,将父节点的值复制到空穴。		}		H->Heap[i] = x;					//若不影响堆序性,则将元素放入空穴。	}}

需要说明的是,在H->Heap[0]中,放入了一个极小的元素,保证x不会比他更小。那么我们就省去了如果新插入的元素是最小值的时候,当i == 1时就要跳出循环的判断了。这就是限位器的作用。这种想法类似于链表添加一个头结点。省去了这个判断,因此程序也能更快点。这种插入在最坏情形下需要O(log n),在平均情形下则要好得多。

删除操作:DeleteMin类似于插入操作,当删除最小元素的时候,那么根将为空。我们需要一个新根。这个新根可以取左右子儿子中小的那一个,这样空穴就被向下退了一层,接着继续比较空穴的左右儿子,选其中小的那个放入空穴,将空穴继续下推,直到将堆中最后一个元素放入空穴为止。

int DeleteMin(priorityqueue H){	int i, child;	int min, last_element;	if (IsEmpty(H))	{		printf("堆为空,无法执行删除操作!");		return H->Heap[0];		//返回这个极限值	}	min = H->Heap[1];			//保存最小值	last_element = H->Heap[H->size];   	//下滤	for  (i = 1; 2 * i <= H->size ; i = child)		//i每次更新为下一个空穴的位置	{		child = 2 * i;		//左儿子的位置		if (child != H->size && H->Heap[child] > H->Heap[child + 1])			{	//这里需要保证child有另外一个兄弟,所以判断child != H->size			child++;		//找出更小的孩子节点		}		if (last_element > H->Heap[child])	//若最后一个元素比这个最小元素还大,那么接着下滤		{			H->Heap[i] = H->Heap[child];		}		else		//最后一个不比这个小元素大,那么将其放入空穴,删除完毕。		{			H->Heap[i] = last_element;			break;		}	}    H->size--;    //堆中元素数目减1	return min;}

删除的时间复杂度是O(log n)。求最小元的堆在求最大元的时候没有任何帮助,唯一知道的信息是最大元在叶子节点上。但是如果一棵树很大的话,这个信息也没有任何帮助,因为,此时近乎一般的元素在树叶上。所以想知道最大值很困难。

我们可以通过插入操作来构建一个堆,但是这样的时间复杂度高达O(n logn).因此一般的构建堆的方法是将N个元素直接放入数组中,形成一个完全二叉树。然后把这个不满足堆序性的完全二叉树改造成堆。代码实现如下。

priorityqueue BuildHeap(priorityqueue H){	int temp, i = 1;	int start = 1;	while (EOF != scanf("%d",&temp))	{		H->Heap[i++] = temp;		//无序数组	}	H->size = i - 1;		//堆中元素个数	int parent, child;	int x;	for ( i = H->size / 2; i > 0; i--)	//从最后一个元素的父节点开始构建堆	{		//考虑到最后一个元素可能没有兄弟,也可能有兄弟。		 x = H->Heap[i];		for (parent = i; parent * 2 <= H->size; parent = child) 		{			child = parent * 2;		//左儿子一定存在			if ((child != H->size) && (H->Heap[child] > H->Heap[child + 1]))			{				child++;  //找到其中最小的那个儿子			}			if (x <= H->Heap[child])		//满足堆序性			{				break; //找到合适位置			}			else			//下滤			{				H->Heap[parent] = H->Heap[child];			}			}		H->Heap[parent] = x;	}	return H;}

堆的其他操作都很简单。

 

 

 

转载于:https://www.cnblogs.com/zy666/p/10504281.html

你可能感兴趣的文章
小说Symbian的签名
查看>>
Objective-C中ORM的运用:实体对象和字典的相互自动转换
查看>>
高级java面试宝典
查看>>
声明,本博客文章均为转载,只为学习,不为其他用途。感谢技术大牛的技术分享,让我少走弯路。...
查看>>
centos7.1下 Docker环境搭建
查看>>
c# 导出Excel
查看>>
Status: Checked in and viewable by authorized users 出现在sharepoint 2013 home 页面
查看>>
python数据预处理
查看>>
Python之路,Day21 - 常用算法学习
查看>>
Android安全-代码安全1-ProGuard混淆处理
查看>>
部署core
查看>>
mysql 时间设置
查看>>
如何在 Xcode 中修改应用的名字
查看>>
[BZOJ5334][TJOI2018]数学计算(exgcd/线段树)
查看>>
[BZOJ4340][BJOI2015]隐身术(后缀数组)
查看>>
有关交换机——熟悉原理是必须的【转载】
查看>>
ACM(数学问题)——UVa202:输入整数a和b(0≤a≤3000,1≤b≤3000),输出a/b的循环小数表示以及循环节长度。...
查看>>
【转】Android 读取doc文件
查看>>
js 数据绑定
查看>>
jsp的C标签一般使用方法以及js接收servlet中的对象及对象数字
查看>>