最短路问题
一般都表现为求两点之间的某种最小代价(如距离、时间、花费、成本等),可具体分为单源最短路和多源最短路,往往还要附加一个前驱数组记录路径。
单源最短路:
就是求某一点到其它所有点的最短路,这种问题我一般直接采用spfa+邻接表,至于前向星优化的spfa没写过,写过一次spfa+链式前向星可是wa掉了,所以我一般只采用链表写,而且写多了也不觉得麻烦。Spfa时间效率很高,为O(ke),可证明在绝大多数情况下k是一个不超过2的常数,所以呢spfa算法很快的,唯一的缺点我想应该就是不能做带负环的图(其它经典算法都不能做负环的图>~~<)。Spfa与bfs很像,所以我经常写错,spfa与bfs的最大的区别就是每个点可能会多次进队(因为某种次序的原因导致每个点会出现多次被更新的情况),而bfs每个点只能进一次队,因此spfa具体实现时,一定要把刚出队的点重新标志为未访问的点。Spaf其实是bellman-ford的队列优化,是直接在点上的松弛。
q[rear=front=1]=s;
p[s]=1;
d[s]=0;
while (front<=rear)
{
j=q[front];
p[j]=0;
for (i=1;i<=n;++i)
if (map[j][i]&&d[i]>d[j]+map[j][i])
{
d[i]=d[j]+map[j][i];
if (!p[i])
{
p[i]=1;
q[++rear]=i;
}
}
++front;
}
至于bellman-ford算法,我一般不用它来求最短路,但它可以方便的判断一个图中是否存在负环,bellman-ford算法可以处理负权图,但同样不能做带负环的图。它是在边上的松弛,多趟对所有边进行松弛判断,有些边不用松弛致使了大量冗余判断,这就是为啥其速度比不上spaf。具体实现时,因为是在边上的松弛,所以最好采用边集数组存图,而且这里有个优化,就是如果某趟松弛判断中没有任何边可松弛,即表明了最终结果已求出,可结束bellamn-ford的运行。
(问题背景:nyoj115 城市平乱~~)
#include
#include
#define MAX 1000000000
struct EdgeNode{
int u,v;
int cost;
}e[100000];
int num;
void addedge(const int &u,const int &v,const int &w)
{
e[num].u=u;
e[num].v=v;
e[num++].cost=w;
}
int main()
{
int i,j,t,n,m,p,q,u,v,w,a[100],d[1001];
scanf("%d",&t);
while (t--)
{
num=0;
scanf("%d%d%d%d",&n,&m,&p,&q);
for (i=0;id[e[j].u]+e[j].cost)
{
d[e[j].v]=d[e[j].u]+e[j].cost;
flag=1;
}
}
int min=MAX;
for (i=0;i
Dijkstra算法就更少写了,其一其使用范围窄,连负权的都不能处理,其二是朴素的dijkstra效率不高,大图就挂了。。而采用最小堆优化或者优先队列优化的dijkstra效率很高,能达到O(nlogn),最适合稠密图了。
这里指贴上最小堆优化的dijstra代码:
#include
#include
#include
#define MAXN 50000
#define INF 0x7fffffff
struct EdgeNode{
int adjvex;
int weight;
EdgeNode *next;
};
struct VexNode{
//int data;
EdgeNode *firstedge;
}g[MAXN];
struct Heap{
int x;
int dis;
};
Heap heap[MAXN*10];
int t,n,m,q,dis[MAXN],len;
void insert(const Heap &in)
{
int u=++len;
while (u>1&&heap[u>>1].dis>in.dis)
{
heap[u]=heap[u>>1];
u>>=1;
}
heap[u]=in;
}
const Heap& pop()
{
int i=1,child;
Heap first=heap[1],last=heap[len--];
while (2*i<=len)
{
child=i<<1;
if (childheap[child+1].dis){
++child;
}
if (heap[child].disnext)
if (!used[p->adjvex])
{
in.dis=min.dis+p->weight;
in.x=p->adjvex;
if (in.disadjvex=v;
s->weight=w;
s->next=g[u].firstedge;
g[u].firstedge=s;
s=(EdgeNode*)malloc(sizeof(EdgeNode));
s->adjvex=u;
s->weight=w;
s->next=g[v].firstedge;
g[v].firstedge=s;
}
for (i=0;iadjvex=v;
s->weight=w;
s->next=g[u].firstedge;
g[u].firstedge=s;
}
dijstra();
for (i=1;i<=t;++i)
if (dis[i]==INF) printf("NO PATH\n");
else printf("%d\n",dis[i]);
// while (1);
return 0;
}
多源最短路:
可以采用spfa+前向星多次求单源最短路,时间效率为O(VE),还是很可以的~
Flyod呢就很少用,除非图比较小。我认为floyd最重要的是其中的思想,即dp。
Flyod的dp方程是d[k,i,j]=min(d[k,I,j],d[k-1,i,k]+d[k-1,k,j]),
初始态为:
map[i,j] (i,j)∈E
d[0,i,j]=
map[i,j] (I,j)∈E
如果图map后面不用的话,可以直接把d当成map用。由于阶段k在状态方程中是连续的,所以在具体实现时可采用二维数组来迭代。
//普通的flyod
for (k=1;k<=n;++k)
for (i=1;i<=n;++i)
for (j=1;j<=n;++j)
if (d[i][j]>d[i][k]+d[k][j])
d[i][j]=d[i][k]+d[k][j];
//如果图为无向图的话,具体实现可以如下:
for (k=1;k<=n;++k)
for (i=1;id[i][k]+d[k][j])
d[i][j]=d[i][k]+d[k][j];
最短路径拓展:
拓展1:就是利用flyod求最小环问题。思想还是dp思想,假设环的最大点编号为k,那么对于无向图来说,一个环至少需要三个点,那么假设与k直接相连的点编号为i、j,那么
以k为最大编号的环的最小值是
circle[k,i,j]=min(circle[k,i,j],d[k-1,i,j]+map[k,i]+map[k,j])
拓展2:求出两点最短路径的数目
设p[i,j]表示i和j最短路径数目,
那么在用k来松弛后,p[i,j]=p[i,k]*p[k,j];
拓展3:
如何巧妙的判断一个点是否在i和j的最短路径上,利用
If (d[i,k]+d[k,j]==d[I,j])在;
Else 不在;