priority_queue优先队列的底层就是堆
在一个长度为N的数组中,如果要删除一个最小的数,并且添加一个数N次。
如果采用
for(int i=1;i<=n;i++)
{
if(a[i]<min) min=a[i];
}
这样的方法,时间复杂度为O(n*n)。
但是如果使用堆结构,时间复杂O(n*log n)。
c++stl priority_queue
- 头文件<queue>
- 定义priority_queue<数据类型>que;
- 操作
数据类型为int时 priority_queue<int>que; 默认大顶堆
- 大顶堆
- 小顶堆
当数据类型为结构体时 >为小顶堆,<为大顶堆
以下是应用
P4779 【模板】单源最短路径(标准版)https://www.luogu.com.cn/problem/P4779
思路:dij邻接表+优先队列。先将所有源点出发的边存入优先队列,每次取队首去,将队首+队首所连接的点出发的每一条边 入队。均入队完之后,队首出队。
代码实现
#include<bits/stdc++.h>
using namespace std;
#define inf 1000000000;
int u[200000+5];
int v[200000+5];
int w[200000+5];
int first[100000+5];
int nex[200000+5];
int dis[100000+5];//源点到i的距离为dis[i]
bool book[100000+5];
struct heap{
int dis;
int pos;
bool operator<(const heap &b) const {
return this->dis>b.dis;
}
};//堆
priority_queue<heap>que;
void build(int s)//生成堆
{
int k=first[s];
heap temp;
while(k!=-1)//遍历与s相连所有的边
{
temp={w[k],v[k]};
que.push(temp);
k=nex[k];
}
}
int main()
{
int n,m,s;
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=n;i++)
{
first[i]=-1;
}
for(int i=1;i<=m;i++)//输入 边
{
scanf("%d%d%d",&u[i],&v[i],&w[i]);
nex[i]=first[u[i]];
first[u[i]]=i;
}
for(int i=1;i<=n;i++)
{
dis[i]=inf;
}
dis[s]=0;
build(s);//构造堆
book[s]=true;
int k;
int near=0;
book[0]=true;
int cnt=1;
while(!que.empty())
{
near=que.top().pos;//最近的那个点
if(book[near])
{
que.pop();
continue;
}
dis[near]=que.top().dis;//将这最小的边存入dis
cnt++;
if(cnt==n) break;
book[near]=true;
//接下来,通过这条 边 去松弛 顶点到其余的各点的距离
k=first[near];
heap temp;
while(k!=-1)//遍历与near相连所有的边
{
temp={que.top().dis+w[k],v[k]};
que.push(temp);
k=nex[k];
}
que.pop();
}
for(int i=1;i<=n;i++)
{
printf("%d ",dis[i]);
}
return 0;
}
同样的思路
#include<bits/stdc++.h>
using namespace std;
#define inf 1000000000;
int u[200000+5];
int v[200000+5];
int w[200000+5];
int first[100000+5];
int nex[200000+5];
int dis[100000+5];//源点到i的距离为dis[i]
int book[100000+5];
int tail=0;
struct heap{
int dis;
int pos;
}dist[200000+5];//堆
void build(int s)//生成堆
{
int k=first[s];
while(k!=-1)//遍历与s相连所有的边
{
dist[++tail].dis=w[k];
dist[tail].pos=v[k];
int now=tail;
while(now>1)
{
if(dist[now].dis>dist[now/2].dis) break;
heap temp=dist[now];
dist[now]=dist[now/2];
dist[now/2]=temp;
now/=2;
}
k=nex[k];
}
}
void push(heap t)
{
dist[++tail]=t;
int now=tail;
while(now>1)
{
if(dist[now].dis>dist[now/2].dis) break;
heap temp=dist[now];
dist[now]=dist[now/2];
dist[now/2]=temp;
now/=2;
}
}
void pop()//把队首的弹出去
{
dist[1]=dist[tail--];
int now=1;
while(now*2<=tail)
{
int ne=2*now;
if(ne<tail&&dist[ne+1].dis<dist[ne].dis) ne++;//取较小的那堆
if(dist[now].dis<=dist[ne].dis) return;//如果符合小顶堆,则返回
heap temp=dist[now];
dist[now]=dist[ne];
dist[ne]=temp;
now=ne;//这里走向较小的堆
}
}
int main()
{
int n,m,s;
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=n;i++)
{
first[i]=-1;
}
for(int i=1;i<=m;i++)//输入 边
{
scanf("%d%d%d",&u[i],&v[i],&w[i]);
nex[i]=first[u[i]];
first[u[i]]=i;
}
for(int i=1;i<=n;i++)
{
dis[i]=inf;
}
dis[s]=0;
build(s);//构造堆
book[s]=1;
int k;
int near=0;
book[0]=1;
while(tail>0)
{
near=dist[1].pos;//最近的那个点
if(book[near]==1)
{
pop();
continue;
}
dis[near]=dist[1].dis;//将这最小的边存入dis
book[near]=1;
//接下来,通过这条 边 去松弛 顶点到其余的各点的距离
k=first[near];
while(k!=-1)//遍历与near相连所有的边
{
push((heap){dist[1].dis+w[k],v[k]});
k=nex[k];
}
pop();
}
for(int i=1;i<=n;i++)
{
printf("%d ",dis[i]);
}
return 0;
}
手写堆
如有建议,求告知
订正
上面那份AC代码没有运用贪心思想,将许多比原来长的边入队了,耗费了无意义的时间与空间。
以下代码为贪心优化
#include<bits/stdc++.h>
using namespace std;
#define inf 1000000000;
int u[200000+5];
int v[200000+5];
int w[200000+5];
int first[100000+5];
int nex[200000+5];
int dis[100000+5];//源点到i的距离为dis[i]
bool book[100000+5];
struct heap{
int dis;
int pos;
bool operator<(const heap &b) const {
return this->dis>b.dis;
}
};//堆
priority_queue<heap>que;
void build(int s)//生成堆
{
int k=first[s];
heap temp;
while(k!=-1)//遍历与s相连所有的边
{
temp={w[k],v[k]};
que.push(temp);
k=nex[k];
}
}
int main()
{
int n,m,s;
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=n;i++)
{
first[i]=-1;
}
for(int i=1;i<=m;i++)//输入 边
{
scanf("%d%d%d",&u[i],&v[i],&w[i]);
nex[i]=first[u[i]];
first[u[i]]=i;
}
for(int i=1;i<=n;i++)
{
dis[i]=inf;
}
dis[s]=0;
build(s);//构造堆
book[s]=true;
int k;
int near=0;
book[0]=true;
int cnt=1;
while(!que.empty())
{
near=que.top().pos;//最近的那个点
if(book[near])
{
que.pop();
continue;
}
dis[near]=que.top().dis;//将这最小的边存入dis
cnt++;
if(cnt==n) break;
book[near]=true;
//接下来,通过这条 边 去松弛 顶点到其余的各点的距离
k=first[near];
heap temp;
while(k!=-1)//遍历与near相连所有的边
{
if(dis[v[k]]>dis[near]+w[k])//贪心
{
temp={que.top().dis+w[k],v[k]};
que.push(temp);
}
k=nex[k];
}
que.pop();
}
for(int i=1;i<=n;i++)
{
printf("%d ",dis[i]);
}
return 0;
}
时间对比 贪心(上)
终于,手打堆过了(哭死)
AC代码
#include<bits/stdc++.h>
using namespace std;
#define inf 1000000000;
int u[200000+5];
int v[200000+5];
int w[200000+5];
int first[100000+5];
int nex[200000+5];
int dis[100000+5];//源点到i的距离为dis[i]
int book[100000+5];
int tail=0;
int postion[100000+5];//postion[i]为i在队列中的位置
struct heap{
int dis;
int pos;//i->pos
}que[100000+5];//堆
void push(heap t)
{
que[++tail]=t;
int now=tail;
postion[que[now].pos]=now;
while(now>1)
{
if(que[now].dis>que[now/2].dis)
{
break;
}
heap temp=que[now];
que[now]=que[now/2];
que[now/2]=temp;
postion[que[now/2].pos]=now/2;
postion[que[now].pos]=now;
now/=2;
}
}
void build_que(int n)//生成堆
{
for(int i=1;i<=n;i++)
{
push((heap){dis[i],i});
}
}
void pop()//把队首的弹出去
{
que[1]=que[tail--];
postion[que[1].pos]=1;
int now=1;
heap temp;
while(now*2<=tail)
{
int ne=2*now;
if(ne<tail&&que[ne+1].dis<que[ne].dis) ne++;//取较小的那堆
if(que[now].dis<=que[ne].dis) return;//如果符合小顶堆,则返回
temp=que[now];
que[now]=que[ne];
que[ne]=temp;
postion[que[now].pos]=now;//更新postion
postion[que[ne].pos]=ne;
now=ne;//这里走向较小的堆
}
}
void renew(int now)//向上调整
{
heap temp;
while(now>1)
{
if(que[now].dis>que[now/2].dis)
{
break;
}
temp=que[now];
que[now]=que[now/2];
que[now/2]=temp;
postion[que[now].pos]=now;
postion[que[now/2].pos]=now/2;
now/=2;
}
}
void build_dis(int s)
{
int k=first[s];
while(k!=-1)//遍历与s相连所有的边
{
if(dis[v[k]]>w[k])
{
dis[v[k]]=w[k];
}
k=nex[k];
}
}
int main()
{
int n,m,s;
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=n;i++)
{
first[i]=-1;
}
for(int i=1;i<=m;i++)//输入 边
{
scanf("%d%d%d",&u[i],&v[i],&w[i]);
nex[i]=first[u[i]];
first[u[i]]=i;
}
for(int i=1;i<=n;i++)
{
dis[i]=inf;
postion[i]=i;
}
dis[s]=0;
build_dis(s);
build_que(n);//构造堆
book[s]=1;
int k;
int near=0;
while(tail>0)
{
near=que[1].pos;//最近的那个点
if(book[near]==1)
{
pop();
continue;
}
dis[near]=que[1].dis;//将这最小的边存入dis
book[near]=1;
pop();
//接下来,通过这条 边 去松弛 顶点到其余的各点的距离
k=first[near];
while(k!=-1)//遍历与near相连所有的边
{
if(dis[v[k]]>dis[near]+w[k])//贪心
{
dis[v[k]]=dis[near]+w[k];
que[postion[v[k]]].dis=dis[v[k]];
renew(postion[v[k]]);
}
k=nex[k];
}
}
for(int i=1;i<=n;i++)
{
printf("%d ",dis[i]);
}
return 0;
}
应该是要把pop提前,不能在最后才pop。因为边有0权值,新插入的可能会占队首。
至于为什么会比优先队列快,
这是因为优先队列是动态开辟内存,而开辟内存是集齐消耗时间的。