您的位置:首页 > 理论基础 > 数据结构算法

[数据结构]第九章-优先队列

2016-12-26 17:09 267 查看
优先队列:以集合为基础的抽象数据类型

· 支持运算

1.Min(H):返回优先队列H中具有最小优先级的元素

2.Insert(x,H):将元素x插入优先队列

3.DeleteMin(H):删除并返回优先队列中具有最小优先级的元素

· 与字典的区别:字典插入运算仅当要插入的x与当前所有元素线性序值都不同时才插入。而优先队列中不同的元素可以有相同的优先级。

· 优先队列的实现

如果用字典实现有限队列:若用有序链表,查询最小元和删除最小元都为O(n),而插入需要O(n);若用二叉搜索树,最坏情况下插入删除O(n),平均情况O(logn)(若是AVL树最坏情况O(logn));若用无序链表,插入O(1),而删除需要O(n)。

二叉搜索树有一性质,中序列表是元素按照优先级从小到大排列,这一性质在优先队列中并不需要,因此可对二叉搜索树作适当修改,引入优先级树。

优先级树是满足下面的优先性质的二叉树:

1.树中每一结点存储一个元素

2.任一结点中存储的元素的优先级不大于其儿子结点中存储的元素的优先级

由此,优先级树的根节点中存储的元素具有最小优先级,从根到叶的任一路径上,结点中元素按优先级的非增序排列。

与二叉搜索树一样,优先级树可能退化成一个线性表。由于在优先级树中执行增删操作与树高有关,所以希望用平衡的优先级树来表示优先队列。当一棵优先级树是近似满二叉树时,称其为或偏序树。

用堆来实现优先队列可以获得较高的效率,增删都只要O(logn)的时间。

·堆的实现

(书上一堆优先级小于优先级的有点麻烦,下面全部按照优先级是数值大小的极小化堆来说(也即根元素最小,树中结点都比根大的堆))

删除:不是简单的删去树根,而是取出根节点,然后删去堆中最底层最右边的叶节点,用它取代树根,之后将这个元素不断地与比它小的儿子交换位置,直到它的两个儿子都不小于它或者它已经降到叶节点为止。

因为从树根到树叶的任一路径上最多只有1+logn个结点,所以删除运算只需O(logn)的时间。

插入:首先将新元素结点添加在堆的最底层,使它仍为一棵近似满二叉树。然后不断的与它的父亲比较,如果小于父亲则交换位置,直到新元素不小于父节点或者已经升到根节点为止。同理上升经过结点数不超过1+logn,所以插入在最坏情况下也只用O(logn)时间。

由于堆是一棵近似满二叉树数,所以可以方便的用数组实现堆。元素a[1] a[2] a[3] … a
是堆中元素的按层序列表,即从根节点开始往下每层从左到右将元素列出的一串序列。根节点存放在a[1]中,a[i]的左儿子(若存在)存放在a[2i]中,右儿子在a[2i+1]中。换句话说当i>1时a[i]的父节点在a[i/2]中。

·可并优先队列——左偏树

用堆实现合并两优先队列的效率不高,下面讨论的左偏树结构不但能在O(logn)时间内支持同一队列中增删的基本运算,还能有效支持两不同优先队列的合并运算。

左偏树是一类特殊的优先级树。(下面讨论的都为极小化左偏树)

一棵优先级树是左偏高树的条件:在该树的每个内结点处,其左儿子结点的高大于等于右儿子节点的高;

一棵优先级树是左偏重树的条件:在该树的每个内结点处,其左儿子结点的重大于等于右儿子节点的重;

高度s(x) = min{ s(L},s(R) } +1;

重量w(x) = w(L) + w(R) + 1;

左偏树图解博客:http://www.cnblogs.com/yc_sunniwell/archive/2010/06/28/1766756.html

·优先队列应用实例哈夫曼编码

//–作业题

11.2 girgirieye







(有n架靶机,每架会在t时刻飞出范围并且有一个价值v,问能打落靶机的最大总价值。简单的贪心比如每个时刻都打最大的价值是错误的,如1 9,1 8,2 1,3 10 这时候要是打了3 10这架,第二个时刻只能打2 1了,这样总和只有11。想象一个时间轴,每个时间点都可以填上一个靶机,或者后来读进来更优的靶机把更差的替换掉。那么靶机填补进来的时候,到底填在哪个时间点上呢?我们知道,至少有一点是可以确定的,对于所有靶机,价值越小越差,同一价值下飞离的越早越差,那我们就有限填掉差的位置。所以可以开个优先队列,按照价值从小到大,飞离时间从小到大排。按照时间先后顺序把所有靶机填进时间点中去,如果有相隔的时间点则用0价值的填补,如样例中的4和5时间点都用0补上(这样可以保证在后飞离的靶机优先填补空位)。填补操作也即看优先队列队首元素价值是否比当前元素小,若比当前元素小,则弹出并把当前元素扔进优先队列。)

#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#define MAX 1000000+7
using namespace std;

struct Bullet
{
int t;
int v;
bool operator < (const Bullet &a) const
{
if(a.v == v) return a.t < t;
return a.v < v;
}
}bul[MAX],tmp;

bool cmp(const Bullet &a,const Bullet &b)
{
if(a.t == b.t) return a.v > b.v;
return a.t < b.t;
}

int i,j;
priority_queue<Bullet> que;
int main()
{
int n;
scanf("%d",&n);
for(i = 0 ; i < n; i++)
scanf("%d%d",&bul[i].t,&bul[i].v);

sort(bul,bul+n,cmp);
int time = 0;
for(i = 0; i < n; i++)
{
if(bul[i].t != time)
{
for(j = time+1; j < bul[i].t; j++)
{
tmp.t = j;
tmp.v = 0;
que.push(tmp);
}
que.push(bul[i]);
time = bul[i].t;
continue;
}
tmp = que.top();
que.pop();
if(bul[i].v > tmp.v)
tmp = bul[i];
que.push(tmp);
}

int ans = 0;
while(!que.empty())
{
tmp = que.top();
que.pop();
ans += tmp.v;
}

printf("%d\n",ans);
return 0;
}


11.1 融合软泥怪





(经典的优先队列题,每次取出两个最小元素加起来再扔回优先队列中去,正确性的证明和哈夫曼树一致。)

#include<cstdio>
#include<iostream>
#include<queue>
#define MAX 1000000+7
using namespace std;

int i;
priority_queue<int, vector<int>, greater<int> >que;

int main()
{
int n,num,tmp,tmp2;
scanf("%d",&n);
for(i = 0; i < n; i++)
{
scanf("%d",&num);
que.push(num);
}

int ans = 0;
while(!que.empty())
{
tmp = que.top();
que.pop();
if(que.empty()) break;
tmp2 = que.top();
que.pop();
ans += tmp+tmp2;
que.push(tmp+tmp2);
}

printf("%d\n",ans);
return 0;
}


//–练习题

11.3 山治的婚约





#include<cstdio>
#include<iostream>
#include<queue>
using namespace std;
#define MAX 10000+7

struct Monkey
{
int id;
int seat;
int move;
}mon,tmp;

bool operator < (Monkey m1, Monkey m2)
{
if(m1.seat == m2.seat)
return m1.id > m2.id;
return m1.seat > m2.seat;
}

priority_queue<Monkey> que;
int main()
{
int n;
scanf("%d",&n);
for(int i = 1; i <= n; i++)
{
scanf("%d%d",&mon.seat,&mon.move);
mon.id = mon.seat;
que.push(mon);
}

while(!que.empty())
{
tmp = que.top();
que.pop();
if(que.empty()) break;
tmp = que.top();
que.pop();
tmp.seat += tmp.move;
que.push(tmp);

}

printf("%d\n",tmp.seat);
return 0;
}


11.4 fresh meat





#include<cstdio>
#include<iostream>
#include<queue>
using namespace std;
#define MAX 10000+7
#define LL __int64
LL now;
LL a[MAX];
LL minu;
LL sum[MAX];
int ans = 0;

priority_queue<LL> que;
int main()
{
int n;
LL m;
scanf("%d%I64d",&n,&m);
now = m;
for(int i = 0; i < n; i++)
{
scanf("%I64d",&a[i]);
que.push(a[i]);
minu += a[i];
if(minu >= now)
{
now+=que.top();
minu-=que.top();
ans++;
que.pop();
}
}

printf("%d\n",ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: