以下为最短路总结:
最短路问题可分为:
二、每对顶点间的最短路径算法:Floyd;
(1).Dijkstra算法:
(经典的算法,可以说是最短路问题的首选事例算法,但是不能处理带负权的边,因为该算法要遍历的点过多,效率低下,用时长,仅限于小数据,不常用)
基本思想:
Dijkstra算法,本质上是利用贪心思想来不停的来进行贪心选择,查找最优解,开辟一个s[],用来存放这些点,
dis[]用来存放所能经过的每个点的最短距离,并进行dis[]的更新操作。注意:只能处理无负权的边
模版如下:
int m,n,map[max][max],dis[max],s[max];
//int prev[max];//当前点的上一个节点
void Dijkstra(int v0)
{
for(int i = 0;i<n;i++) //初始化dis[]
{
s[i] = 0;
dis[i] = map[v0][i];
/* if(i!=v0 && map[v0][i]<inf)
prev[i] = v0
else
prev[i] = -1; */
}
s[v0] = 1;
dis[v0] = 0;
for(int i = 2;i<=n;i++)
{
int u = v0,min = inf;
for(int j = 0;j<n;j++)//贪心查找还未存储的最优解的点,并储存
{
if(!s[j] && dis[j] < min)//当前最短距离
{
u = j;//记录下标
min = dis[j];
}
}
s[u] = 1;
for(int j = 0;j<n;j++)//更新dis[]数组
{
if(!s[j] && map[u][j] < inf && dis[u] + map[u][j] < dis[j])
{
dis[j] = map[u][j] + dis[u];
//prev[j] = u;
}
}
}
}
优先队列的Dijkstra
HDU 1874
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <queue>
#include <vector>
const int N = 10001;
const int INF = 1e7;
using namespace std;
struct node{
int x,d;
node(){}
node(int a,int b)
{
x=a; d=b;
}
bool operator <(const node & a)const
{
if(d == a.d)
return x < a.x ;
else
return d > a.d;
}
};
vector <node> ma[N];
int dis[N],n;
void Dijkstra(int s)
{
for(int i=0;i<=n;i++)
dis[i]=INF;
dis[s]=0;
priority_queue<node> q;
q.push(node(s,dis[s]));
while(!q.empty())
{
node p=q.top();
q.pop();
int s = ma[p.x].size();
for(int i=0;i<s;i++)
{
node pp=ma[p.x][i];
if(dis[pp.x]>p.d+pp.d)
{
dis[pp.x]=p.d+pp.d;
q.push(node(pp.x,dis[pp.x]));
}
}
}
}
int main()
{
int a,b,c,m,s,e;
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=0;i<=n;i++)
ma[i].clear();
while(m--)
{
scanf("%d%d%d",&a,&b,&c);
ma[a].push_back(node(b,c));
ma[b].push_back(node(a,c));
}
scanf("%d%d",&s,&e);
Dijkstra(s);
if(dis[e]!=INF)
printf("%d\n",dis[e]);
else
puts("-1");
}
return 0;
}
// 题意: 有n*m矩阵,从起点(sx,sy)出发,可以上下左右四个方向移动,
// 若两个位置上是相同字符,则花费为0,否则为1,求到终点的最短距离
// 用Dijkstra算法解决,但会 TLE ,需要用 优先队列 优化时间
#include <iostream> // 邻接矩阵+优先队列实现Dijkstra算法
#include <stdio.h>
#include <queue>
#include <cstring>
using namespace std;
const int INF=300000;
const int MAXN=250000;
int n,m,distD[MAXN],done[MAXN];
int sx,sy,tx,ty; //起点(sx,sy)和终点(tx,ty)
int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
char data[500][500];
typedef pair<int,int> pii;
void Dijkstra(int st) //从源点st到其余各顶点的最短路径长度
{
priority_queue<pii, vector<pii>, greater<pii> > q;
fill(distD,distD+n*m,INF); //结点下标从0开始,共有 m * n 个结点
distD[st]=0;
fill(done,done+n*m,0);
q.push(make_pair(distD[st], st));
while(!q.empty())
{
pii u = q.top();
q.pop();
int p = u.second;
if(done[p])
continue;
done[p] = 1;
if( p == tx * m + ty ) // 如果探测到终点即可退出
{
printf("%d\n",distD[p]);
break;
}
for(int d=0;d<4;++d)
{
int newx=p/m+dx[d],newy=p%m+dy[d];
int newp=newx*m+newy;
if(newx>=0&&newx<n&&newy>=0&&newy<m&& !done[newp] )
{
int w = ( data[p/m][p%m] == data[newx][newy] ) ? 0 : 1 ;
if( distD[p] + w < distD[newp] )
{
distD[newp]=distD[p]+w;
q.push(make_pair(distD[newp], newp));
}
}
}
}
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF) // n 行 m 列
{
for(int i=0;i<n;++i)
{
scanf("%s",data[i]);
}
scanf("%d%d%d%d",&sx,&sy,&tx,&ty);
Dijkstra(sx*m+sy); // 顶点坐标[x,y],则在队列对应的下标是 x * m + y
}
return 0;
}
(用来判断是否有回环,可处理带负权边 时间复杂度O(V*E).)
基本思想:(DP思想,查找最优解,并不断的进行松弛操作,但是算法浪费了许多时间做冗杂的松弛操作,效率降低) 无向图
void add()
{
scanf("%d%d%d",&u,&v,&w);
edge[l].u = u; edge[l].v = v; edge[l++].w = w;
edge[l].u = v; edge[l].v = u; edge[l++].w = w;
}
int Bellman(int cnt)
{
int i,j,flag;
for(i = 1;i <= N - 1;i++)
{
flag = 0;
for(j = 0;j <= count - 1;j++)
{
if(dis[edge[j].u]> dis[edge[j].v] + edge[j].w)
{ //松弛计算
dis[edge[j].u] = dis[edge[j].v] + edge[j].w;//更新dis[]数组,使其存储 源点->当前点的最短距离
flag = 1;
}
}
if(flag==0)//如果查找完,或者有些点达不到,跳出->优化
break;
}
for(i = 0;i < count;i++)//判断是否有负环路
{
if(dis[edge[i].u] > dis[edge[i].v] + edge[i].w)//更新完dis[]后,如果还存在 该成立条件,则存在负环路
return 1;
} // 如1->2 权值2
return 0; // 2->3 权值5
// 3->1 权值-8
// 1->2->3->1 一次循环为-1 二次-2 三次....这就叫负环
}
(2)FLOYD
这个实在没什么好说的,做题目时,只要确定的 时间不超,就可以用,前提是记下,时间复杂度O(n*n*n).
ma[u][v] 储存的就是u->v的距离
void init()
{
for(i=0;i<n;i++)
for(j=0;j<n;j++)
{
map[i][j] = (i==j)?0:inf;
}
}
void floyd()
{
for(k=0;k<n;k++)
{
for(i=0;i<n;i++)
{
for(j=0;j<n;j++)
{
if(map[i][k]!=inf && map[k][j]!=inf && map[i][j]>map[i][k] + map[k][j] )
{
map[i][j]=map[i][k]+map[k][j];
}
}
}
}
}
(4)SPFA
书上说是在bellman-ford的基础上,添加一个队列操作,减少了其不必要的松弛操作。
我个人认为是在BFS搜索的基础上加了一步所谓的松弛操作,至于为什么叫松弛,不懂。
但是SPFA优化的很棒,以后最短路问题,尽量用它。无向图
矩阵式
void SPFA(int s,int e) S点 到e点
{
int l,r,i;
l=r=0;
memset(vt,0,sizeof(vt));
for(i=s;i<e+1;i++)
dis[i]=inf;
dis[s]=0;
q[r++]=s;//进队列
vt[s]=1;//标记 进队列1
//不在队列为0
while(l<r)
{
int p=q[l++];//出队列
for(i=0;i<n;i++)//枚举与p相连的点
{
if(dis[i]>dis[p] + map[p][i])//松弛
{
dis[i] = dis[p] + map[p][i];
if(!vt[i])
{
q[r++] = i;
vt[i] = 1;
}
}
}
vt[p] = 0;
}
if(dis[e]!= inf)
printf("%d\n",dis[e]);
}
前向星式
struct node{
int u,v,w;
int next;
}edge[40000];
int head[40000];
void init()
{
memset(vis,0,sizeof(vis));
memset(head,0,sizeof(head));
for(int i = 0;i<n;i++)
{
dis[i] = INF;
}
t = 1;
}
int s,e;
void add(int a,int b,int c)
{
edge[t].v = b;
edge[t].w = c;
edge[t].next = head[a];
head[a] = t++;
}
void SPFA()
{
queue<int>q;
q.push(s);
vis[s] = true;
dis[s] = 0;
while(!q.empty())
{
int p = q.front();
q.pop();
vis[p] = 0;
if(head[p])
{
for(int pp = head[p];pp!=0;pp = edge[pp].next)
{
if(dis[p] + edge[pp].w < dis[edge[pp].v])
{
dis[edge[pp].v] = dis[p] + edge[pp].w ;
if(!vis[edge[pp].v])
{
q.push(edge[pp].v);
vis[edge[pp].v] = true;
}
}
}
}
}
if(dis[e]<INF)
printf("%d\n",dis[e]);
else
printf("-1\n");
}
邻接链表式
struct node{
int u,v,w;
node *next;
}*head[40000];
void init()
{
memset(vis,0,sizeof(vis));
memset(head,NULL,sizeof(head));
for(int i = 0;i<n;i++)
{
dis[i] = INF;
}
t = 1;
}
int s,e;
void add(int a,int b,int c)
{
node *p = new node;
p->v = b;
p->w = c;
p->next = head[a];
head[a] = p;
}
void SPFA()
{
queue<int>q;
q.push(s);
vis[s] = true;
dis[s] = 0;
while(!q.empty())
{
int p = q.front();
q.pop();
vis[p] = 0;
node *pp;
for(pp = head[p];pp!=NULL;pp = pp->next)
{
if(dis[p] + pp->w < dis[pp->v])
{
dis[pp->v] = dis[p] + pp->w;
if(!vis[pp->v])
{
q.push(pp->v);
vis[pp->v] = true;
}
}
}
}
if(dis[e]<INF)
printf("%d\n",dis[e]);
}