本文借优先队列,重点讲解二叉堆
优先队列:
能完成以下操作的数据结构叫做优先队列:
1.插入一个数值
2.取出最小的数值(获得数值,并且删除)
二叉堆:
能够使用二叉树有效地解决上述问题(优先队列),是一种叫做“堆”的数据结构,即二叉堆。
所以二叉堆是二叉树和优先队列结合的数据结构。
二叉堆示意图:
像这样的二叉堆特点:
1.儿子结点的值一定不小于父结点的值
2.结点按从上到下、从左到右的顺序紧凑排列
我们称这样的二叉堆为最小堆
当然我们还可以有这样的二叉堆:
像这样的二叉堆特点:
1.父结点的值一定不小于儿子结点的值
2.结点按从上到下、从左到右的顺序紧凑排列
我们称这样的二叉堆为最大堆
二叉堆插入和取值操作(最大/小值):
以第一种二叉堆为例
插入操作:
在向堆中插入数值时,首先在堆的末尾插入该数值,然后向上不断提升,直到没有大小颠倒为止。
插入完成之后:
删除操作:
删除堆中的最小值,首先把堆的最后一个结点的数值复制到根结点上,并且删除最后一个结点。然后不断向下交换直到没有大小颠倒为止。向下交换过程中,如果有两个儿子结点,那么和较小的儿子结点交换。
删除操作结果:
用C实现上述二叉堆操作:
#include <stdio.h>
#include <stdlib.h>
int heap[100],sz;//sz为数值个数
//把数值放入二叉堆中
void push(int x){
int i=sz++;
while(i>0){
//父亲结点的编号(无论是左儿子还是右儿子都用该公式)
int p=(i-1)/2;
//如果已经没有大小颠倒就退出
if(heap[p]<=x) break;
//把父结点的数值放下来,把自己的数值提上去
heap[i]=heap[p];
i=p;
}
heap[i]=x;
}
//将最小值取出二叉堆
int pop(){
//最小值
int ret=heap[0];
//该数值要提到根
int x=heap[--sz];
//从根开始向下交换
int i=0;
while(i*2+1<sz){
//比较儿子的值
int a=i*2+1,b=i*2+2;
if(b<sz&&heap[b]<heap[a])a=b;//操作右儿子
//没有大小颠倒则退出
if(heap[a]>=x)break;
//把儿子的数值提上来
heap[i]=heap[a];
i=a;//根结点下移
}
heap[i]=x;
return ret;
}
int main(int argc, char *argv[]) {
int i;
printf("请输入二叉堆中的有几个数:\n");
scanf("%d",&sz);
printf("请输入这几个数值:\n");
for(i=0;i<sz;i++){
scanf("%d",&heap[i]);
}
int x;
printf("请输入要放入二叉堆中的值:\n");
scanf("%d",&x);
push(x);
printf("这时数组为:\n");
for(i=0;i<sz;i++){
if(i>0) printf(" ");
printf("%d",heap[i]);
}
printf("\n------------\n");
printf("将最小值取出:\n");
printf("%d\n",pop());
printf("这时数组为:\n");
for(i=0;i<sz;i++){
if(i>0) printf(" ");
printf("%d",heap[i]);
}
return 0;
}
实际上,大部分情况下不需要我们自己实现二叉堆。在许多编程语言中,都包含了优先队列的高效实现。例如C++中,STL里的priority_queue就是其中之一。
而其实现的是我们所讲的第二种,即取出数值时得到的是最大值。
C++优先队列:
#include <iostream>
#include <queue>
#include <cstdio>
using namespace std;
int main(int argc, char** argv) {
//声明
priority_queue<int> pque;
//插入元素
pque.push(3);
pque.push(5);
pque.push(1);
//不断循环直到为空
while(!pque.empty()){
//获取最大值
printf("%d ",pque.top());
//删除最大值
pque.pop();
}
return 0;
}