浙大数据结构-堆和优先队列

一、优先队列

优先队列(Prior Queue):特殊的“队列”,取出元素的顺序是以招元素的优先权(关键字)大小,而不是元素进入队列的先后顺序

 

数组或链表实现优先队列
    数组插入——元素总是插入尾部Θ(1)
删除——查找最大(或最小)关键字Θ(n)
               移动数组元素后删去O(n)
    链表插入——元素总是插入头部Θ(1)
删除——查找最大(或最小)关键字Θ(n)
               删除结点Θ(1)
有序数组插入——找到合适的位置O(n)/O(log2N)
               移动元素并插入O(n)
删除——删去最后一个元素Θ(1)
有序链表插入——找到合适的位置O(n)
               插入元素Θ(1)
删除——删除首元素或最后元素Θ(1)

二、堆的定义、性质及用途

堆是一种由完全二叉树(数组存储)表示的“优先队列”,不同于普通队列的进出队列先后次序优先,堆中元素按照其优先权的大小优先取出

堆的两大特性:

  • 结构性:用数组表示的完全二叉树
  • 有序性:最大堆(maxheap),大顶堆:任一结点的关键字是其子树所有结点的最大值;最小堆(minheap),小顶堆:任一结点的关键字是其子树所有结点的最小值

堆可应用于构建堆排序、查找集合最大(小)关键字、构建优先队列。从根结点到任意节点路径上结点序列都具备有序性。

而二叉排序树查找效率高(平衡二叉树效率更高,因为树矮),这是堆所没有的优势。此外,二叉排序树左子树<根<右子树或>,而堆只区分根和子树,子树间的大小未定。

三、堆的抽象数据类型

类型名称:最大堆(maxheap)

数据对象集:完全二叉树,每个结点的元素值不小于其子结点的元素值

操作集:最大堆H∈maxheap,元素item∈ElementType,主要操作:

  • MaxHeap Create( int MaxSize ):创建一个空的最大堆
  • Boolean IsFull( MaxHeap H ):判断最大堆是否已满
  • Insert( MaxHeap H, ElementType item ):将元素item插入最大堆
  • Boolean IsEmpty( Maxsize H ):判断最大堆H是否为空
  • ElementType DeleteMax( MaxHeap ):返回H中最大的元素(高优先级)

MaxHeap Create( int MaxSize ):创建一个空的最大堆

typedef struct HeapStruct *MaxHeap;
struct HeapStruct {
	ElementType *Elements;//为什么这里不定义成数组形式? 
	int Size;
	int Capacity;
};

MaxHeap Create( int maxsize ){
	MaxHeap H = malloc( sizeof( struct HeapStruct ) );
	H->Elements = malloc( (maxsize+1) * sizeof(ElementType));
	H->Size = 0;
	H->Capacity = maxsize;
	H->Elements[0] = maxdata;//将maxdata换成mindata,同样也适用于最小堆的创建
	
	return H;
	
}

Boolean IsFull( MaxHeap H ):判断最大堆是否已满

Boolean IsFull( MaxHeap H ){
	return H->Size;
} 

Boolean IsEmpty( Maxsize H ):判断最大堆H是否为空

Boolean IsEmpty( MaxHeap H ){
	return !H->Size;
}

Insert( MaxHeap H, ElementType item ):将元素item插入最大堆

插入结点:先将要插入的结点x放在最底层的最右边,插入后满足完全二叉树的特点,然后依次把x向上调整到合适的位置以满足父大子小的性质(找出一条x到根结点的路径,沿路径做“BubbleSort”)。

//算法:将新增结点插入到数组末尾,然后调整 
// T(n) = O(logN) 
void Insert( MaxHeap H, ElementType item ){
//将元素item插入最大堆H,其中 H->Elements[0]已经定义为哨兵 
	int i;
	if( IsFull(H) ){
		printf("最大堆已满");
		return; 
	} 
	i = ++H->Size;//i指向插入后堆中最后一个元素的位置
				  //换言之,在堆末尾插入		
	for(; H->Elements[i/2] < item; i /= 2)
		H->Elements[i] = H->Elements[i/2]; 
		//比较此时新结点和父结点的大小(完全二叉树),父结点较小,则父结点被换下去 
	//退出循环时,标志找到新结点的正确位置 
	//H->Elements[0]是哨兵元素,它不小于堆中最大元素,控制循环结束
	//否则for内第二个条件要改成 H->Elements[i/2] < item && i>1; 
	H->Elements[i] = item;
	
}

ElementType DeleteMax( MaxHeap ):返回H中最大的元素(高优先级)

删除结点:删除某个结点后完全二叉树会出现一个空洞,把最底层最右边的叶子结点的值赋给该孔并下调至合适的位置(只需要调整拿来补洞的结点相关的位置,其余已经有序、不受影响的结点不必移动,从上向下依次比较孩子,小的换下去)

//T(N) = O(logN)
//删除堆顶元素,将最后一个元素放到堆顶,和insert一样调整 
ElementType DeleteMax( MaxHeap H ){
	int Parent, Child;
	ElementType MaxItem, temp;
	if( IsEmpty(H) ){
		printf("最大堆已空");
		return;
	}
	MaxItem = H->Elements[1];
	temp = H->Elements[H->Size--];
//此处数组顺序是按照从下标1开始生成二叉树,否则Child=Parent*2+1
	for(Parent = 1; Parent*2<=H->Size; Parent = Child ){
		Child = Parent * 2;
		//找到较大的孩子 
		if( Child < H->Size && H->Elements[Child] < H->Elements[Child+1])
			Child ++;
		//Parent是待插入位置
		//如果temp的值大于Child,那么此时Parent位置正确
		//反之,Child的值上移,Parent的值下移 
		if( temp >= H->Elements[Child] ) break;
		else 
			H->Elements[Parent] = H->Elements[Child];
	}
	//退出循环时找到了合适的位置插入 
	H->Elements[Parent] = temp;
	return MaxItem; 
} 

最大堆的建立

建立最大堆:将n个已经存在的元素按照最大堆的要求存放在一维数组内

方法1:先建立空堆,通过Insert操作,将N个元素一个个相继插入到空堆中,其时间代价最大为O(N logN)。

方法2:在O(N)下建立最大堆

  1. 将N个元素按照输入顺序存入,先满足完全二叉树的结构特性
  2. 调整各个结点位置,以满足最大堆的有序特性

 将序列存入一个数组中,得到顺序存储的完全二叉树。

建堆从[n/2]开始,即编号最大的第一个非叶结点开始(因为叶子节点没有左右孩子,已然满足堆的定义),然后从下向上依次调整:

四、堆排序

堆排序思想:将一个无序序列调整成一个堆,可以找出这个序列的最值,然后见这个值交换到序列的最后或最前。这样,有序序列关键字增加一个,无序序列关键字减少一个,对新的无序序列重复这样操作,最终实现排序。

最关键的操作是将序列调整为堆,整个排序过程是通过不断调整,使得不符合堆定义的完全二叉树变为符合堆定义的完全二叉树。

时间复杂度O(N logN):函数Sift()中,j走了一条从当前结点到叶子结点的路径,完全二叉树的高度为\left\lceil\log _{2}(n+1)\right\rceil,即对每个节点调整的时间复杂度为O(logN)。对于函数heapSort(),基本操作次数是两个并列的for循环的基本操作次数之和。

空间复杂度O(1):只申请了一个temp。

堆排序在最坏情况下的时间复杂度也是O(N logN),这是它相对于快速排序的最大优点,此外,堆排序的空间占用也小于快速排序的O(logN)。但是快速排序在同级别时间复杂度的算法中,系数是最小的,因此叫做快速排序。

//在数组R[low]到R[high]的范围内对在位置low上的结点进行调整 
void Sift( int R[], int low, int high ){//从数组下标1开始存储 
	int i = low, j = 2*i;
	int temp = R[i];
	//找到插入位置i 
	while( j <= high ){
		if( j < high && R[j] < R[j+1] ) ++j;//j指向较大的孩子
		//如果当前j的值仍大于temp,那么交换位置,j指向其孩子结点 
		if( temp < R[j] ){
			R[i] = R[j];
			i = j;
			j *= 2;
		} 
		else break;
	}
	//temp找到合适的插入位置i 
	R[i] = temp;
} 

void heapSort( int R[], int n ){
	int i;
	int temp;
	//从最后一个非叶子结点开始建立最大堆 
	for( i = n/2; i >= 1;--i ) Sift(R, i, n );
	
	//不断将堆顶结点换到序列末尾,新的无序序列结点数目减少,最终得到
	//一个递增序列 
	for( i = n; i >= 1;--i ){
		//换出根结点中的关键字,将其放入最终位置 
		temp = R[1];
		R[1] = R[i];
		R[i] = temp;
		//在减少了一个关键字的无序序列中调整 
		Sift( R,1,i-1); 
	}
	 
} 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值