高级数据结构-优先队列(堆)—(1)二叉堆

高级数据结构-优先队列(堆)—(1)二叉堆

1.导入——什么叫优先队列(堆)

我们都知道,在计算机的操作系统中,进程块(PCB)都是按队列的方式存储起来,排好队一个个地等待CPU运行。
可时常会出现由于某某进程更加“高级”,即更加急于去被运行,而需要给每一个进程设一个优先次序,来以此处理相对比较重要的进程块。
对此,称这种含有优先级的特殊队列为“优先队列(priority queue)”。
那么它的另一个名字“堆(heap)”又是怎么回事呢?
本质上,队列是一种线性的数据结构,但由于其本身的存储特点,若将优先队列也按简单的队列的方式存储,那么不但查找、移动时的时间开销都是特别大,而且线性结构很难体现其优先次序。以此,考虑到效仿二叉查找树的类似思路,将其按树形结构存放,借助叶子间的层次和左右次序,体现不同节点的优先级。同时,由于二叉树是递归建立的,在查找、移动时可以将时间复杂度降到
对数即别。
由于建立的树是一稞二叉树,我们便称之为二叉堆。通常情况下,二叉堆就是指的堆(heap)
也就是说,通常认为,优先队列=二叉堆=堆。

2.二叉堆

2.1二叉堆的性质

首先我们先来回顾一下完全二叉树:一稞完全被填满的树,比如下面这个例子:
在这里插入图片描述同时,如果以字母字典序表示其优先级,那么这即是一个堆。
我们通过这个二叉树可以看出堆的结构特点:
(1)越往下优先级越低;
(2)左儿子优先级大于右儿子,并以此不断递归下去。

而一稞高为 h h h的完全二叉树有 2 h 2^h 2h 2 h − 1 2^{h-1} 2h1个节点。所以当它的髙为 [ l o g N ] [log N] [logN],查找的时间复杂度为 O ( l o g N ) O(logN) O(logN)
另外,由于完全二叉树的规律性,在存储时可直接用数组来存储,而不是用二叉树常用的“结构体+指针”的方式,进一简化了操作难度。
当然,此文中为了便于讨论,我们暂时先用结构体来描述

const int Mindata=0;
const int MaxElements=100;
const int MinPQsize=10;
const int Maxlength=100;
typedef struct Heap *PriorityQueue;
typedef int ElementType;
typedef struct Heap{
	int capaticy;//优先级 
	int size;//队列的大小or长度 
	int Elements[Maxlength];
}Heap,*Element;
2.2二叉堆的常见操作

堆的大部分操作与二叉树类似,大多采用递归的思路,此处不作赘述。在此只说一下声明、插入和删除最小节点(元素)。

2.2.1堆的声明
void Intialize(Element &H){
	if(MaxElements<MinPQsize) cout<<"队列大小过小"<<endl;
	H=new Heap;
	if (H==NULL) cout<<"二叉堆为空"<<endl;
	else if(H->Elements==NULL) cout<<"二叉堆为空"<<endl;
	else{
	H->capaticy=MaxElements;
	H->size=0;
	H->Elements[0]=Mindata;
	}
} 
2.2.2节点的插入

即堆的创建。由于二叉堆中优先次序的引入,节点的插入需要先判断其在队列(树)中的优先级。前面已经提及,由于采用了树形结构,我们在查找时的时间复杂度大大降低。
通常,我们先考虑创建一个“空穴”,即在二叉树上先为待插入地子节点留出一个空位,然后依据其优先级调整位置:优先级高,则上移;优先级高,则下移。这种策略叫做“上滤”,其好处在于不会破坏堆的结构性。

bool IsFull(PriorityQueue H){
	if (H->size=Maxlength) return 1;
}
void Insert(int x,PriorityQueue H){
	int i;
	if(IsFull(H)){
		cout<<"队列已满"<<endl;
		return ; 
	}
	else{
		for(i=++H->size;H->Elements[i/2]>x;i/2) H->Elements[i]=H->Elements[i/2];//构建空节点
			H->Elements[i]=x; 
	}
}
2.2.3最小元素的删除

由于堆的特殊性质,删除最小元素是很容易的。麻烦的在于如何使得堆在被删除后依旧保持结构性。与前面申明时制造空位再补空的思路相类似,删除后产生了一个空穴,然后要对这个空穴进行补空操作。即堆中某个元素X必须要能移动到这个空位。
这里我们所采用的方法是:将X置入沿着从根开始包含最小儿子的一条路径上的一个正确位置。这一策略称之为“下滤”。

int DeleteMin(PriorityQueue H){
	int i,Child;
	int MinElement,LastElement;
	if(IsFull(H)){
		cout<<"二叉堆为空"<<endl;
		return H->Elements[0]; //空队列则直接返回头元素
	}
	else{
		MinElement=H->Elements[1];
		LastElement=H->Elements[H->size--];//删除最小元素
	}
	for(i=1;i<H->size/2;i=Child){
		Child=i*2;
		if(Child!=H->size&&H->Elements[Child+1]<H->Elements[Child]) Child++;
		if(LastElement>H->Elements[Child]) H->Elements[i]=H->Elements[Child];//下滤
		else break;
	}
	H->Elements[i]=LastElement;//修改指针,完成补空
	return MinElement;
}

总结

优先队列(堆)是一种特殊的数据结构,虽然本身具有线性关系,但在实际存储和操作中为了体现其特点和节省时间开销,采取完全二叉树的存储结构。由于完全二叉树递归的结构性质,操作的时间复杂度规模被降到了对数级别。堆的操作主要是“上滤”和“下滤”,即在删除或插入节点的同时,又要保持本身的结构特点,因此采用留空、补空的思路进行操作。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值