最短路自学总结

最短路问题

 

一般都表现为求两点之间的某种最小代价(如距离、时间、花费、成本等),可具体分为单源最短路和多源最短路,往往还要附加一个前驱数组记录路径。

 

单源最短路:

 

就是求某一点到其它所有点的最短路,这种问题我一般直接采用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<stdio.h>
#include<string.h>

#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;i<n;++i) scanf("%d",&a[i]);
	    for (i=0;i<p;++i)
		{
			scanf("%d%d%d",&u,&v,&w);
			addedge(u,v,w);
			addedge(v,u,w);
		}

		//memset(d,10,sizeof(d));
		for (i=1;i<=m;++i) d[i]=MAX;
		d[q]=0;
		int flag=1;
		for (i=1;i<m&&flag==1;++i)
		{
			flag=0;
			for (j=0;j<num;++j)
				if (d[e[j].v]>d[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<n;++i)
		{
			//printf("%d ",d[a[i]]);
			if (d[a[i]]<min){
					min=d[a[i]];
				}
		}
		printf("%d\n",min);
		//while (1);
	}
	return 0;
}



Dijkstra算法就更少写了,其一其使用范围窄,连负权的都不能处理,其二是朴素的dijkstra效率不高,大图就挂了。。而采用最小堆优化或者优先队列优化的dijkstra效率很高,能达到O(nlogn),最适合稠密图了。

 

这里指贴上最小堆优化的dijstra代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#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 (child<len&&heap[child].dis>heap[child+1].dis){
			++child;
			}
		if (heap[child].dis<last.dis)
		{
			heap[i]=heap[child];
		}
		else break;
		i=child;
	}
	heap[i]=last;

	return first;
}

void dijstra()
{
	int i,used[MAXN]={0};
	Heap in,min;
	EdgeNode *p;

	for (i=1;i<t;++i) dis[i]=INF;	
	dis[q]=0;

	in.x=q;
	in.dis=0;
	len=0;

	insert(in);
	while (len)
	{
		min=pop();
		if (used[min.x]) continue;
		used[min.x]=1;
		//printf("ddddd\n");
		for (p=g[min.x].firstedge;p;p=p->next)
			if (!used[p->adjvex])
				{
					in.dis=min.dis+p->weight;
					in.x=p->adjvex;
					if (in.dis<dis[in.x])
					{
					dis[in.x]=in.dis;
					insert(in);
					
					}
				}
	}
}

int main()
{
	int i,u,v,p,w,r,a[MAXN];
	EdgeNode *s;
		scanf("%d%d%d%d",&t,&r,&p,&q);
		
		for (i=0;i<r;++i)
		{
			scanf("%d%d%d",&u,&v,&w);
			s=(EdgeNode*)malloc(sizeof(EdgeNode));
			s->adjvex=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;i<p;++i)
		{
			scanf("%d%d%d",&u,&v,&w);
			s=(EdgeNode*)malloc(sizeof(EdgeNode));
			s->adjvex=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;i<n;++i)
         for (j=1;j<i;++j)
              if (d[i][j]>d[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 不在;

      


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值