Dijkstra算法
特点:单源最短路径,无负权边。
算法思路:设置一个dis[i]数组记录i到源点的最小距离。一开始只有源点的dis为0,其他全为无限大。从源点出发,更新周围与它相连的点。进行顶点次数的循环,每次寻找与源点距离最近的点,标记为已访问,并从该点出发更新相连的点的状态。根据三角不等式dis[i]+map[i][j]跟dis[j]比较,如果小于,说明经过i再到j比原来源点到j的最短距离还要短,则更新之。最后能得到源点到任意点的最短路径。
代码如下:
//djikstra算法
void djikstra()
{
int i,j,k;
for(i=1;i<=n;i++)
{
dis[i]=inf;//一开始所有点到源点的距离为无限大
vis[i]=0;
}
dis[1]=0;//将源点到自身距离设置为0
for(i=1;i<=n;i++)
{
int min=inf;
for(j=1;j<=n;j++)
{
if(!vis[j]&&dis[j]<min)// 遍历所有点,如果点没有被访问过,并且dis[j]被更新过,则保留dis[j]最小的点的下标
{
min=dis[j];
k=j;
}
}
if(min==inf)break;
vis[k]=1;//将找出的dis[j]最小的点标记为已访问,这样首先一开始源点会被标记。从该点开始,进行下面的操作。
for(j=1;j<=n;j++)
{
if(map[k][j]!=inf&&!vis[j])//如果k点可以到达j点,即存在(k,j)边,则更新这条边
{
if(dis[k]+map[k][j]<dis[j])//如果经过k点可以到达j点,则判断源点到j的距离是否大于经过该点再到j的距离,如果是,则更新之。通过这样不断地寻找最短路径。
{
dis[j]=dis[k]+map[k][j];//到达j点的最短路径=到达k点的最短路径+(k,j)边的距离
pre[j]=k;//将j的前置设置为k
}
}
}
}
}
Head优化的djikstra算法
由于普通的dijkstra算法每次寻找与源点最近的点都要遍历整个dis数组,效率有点慢,可以使用堆进行优化。将dis数组看成是最小堆,用o(logn)的时间复杂度去调整一个堆,只需要O(1)的时间就可以取出最小值。效率得到明显提高。
const int maxn=50000+5;
const long long INF=0x3f3f3f3f3f;
int weight[maxn];
int cnt;
int head[maxn];
long long dis[maxn];
bool vis[maxn];
struct Edge
{
int to;//指示边的终点
int w;//边的权重
int next;//下一条边的起始点
} edge[maxn<<1];
struct Node
{
int u,dis;
bool operator <(const Node &a) const
{
return dis>a.dis;
// 当返回true时会更新堆,因此当新元素a的dis小于堆顶元素dis
// 的时候会返回true,同时会更新堆,故此堆为小顶堆
}
}
void Init()
{
cnt=0;
memset(head,-1,sizeof(head));
memset(vis,false,sizeof(vis));
for(int i=0; i<=n; i++)
dis[i]=INF;
}
void addEdge(int u,int to,int w)
{
edge[cnt].to=to;
edge[cnt].w=w;
edge[cnt].next=head[u];//将这条边插入以u开头的链表
head[u]=cnt++;//将u这个点的head指针指向最后插入的节点,可以看成是一个头插法的链表
}
void Dijkstra(int s)
{
Node now,next;
priority_queue<Node>q;//定义一个优先队列
now.dis=0;//将当前点的距离设置为0
now.u=s;
dis[s]=0;
q.push(now);//将源点加入到优先队列中
while(!q.empty())//如果优先队列非空
{
now=q.top();//取出队列中dis最小的元素.这里便是整个程序优化的地方,之前我们是通过遍历n个点来得到最小值,现在我们通过维护一个堆来取得。
q.pop();//将最小元素出队
if(vis[now.u])continue;//如果该点已经被访问,则跳过
int u=now.u;//否则从该点出发并将该点标记为已访问
vis[u]=true;
for(int i=head[u]; i!=-1; i=edge[i].next)//遍历以u开头的整个链表 ,更新与u相连的点
{
int to=edge[i].to;
if(!vis[to]&&dis[u]+edge[i].w<dis[to])
{
dis[to]=dis[u]+edge[i].w;
next.dis=dis[to];
next.u=to;
q.push(next);
}
}
}
}
Bellman-Ford算法
特点:单源最短路径,含负权边,可以找出负环,然而效率低下,一般使用队列优化过的SPFA方法.
思路:持续地进行松弛(即上文介绍的“三角不等式”),在每次松弛时把每条边都更新一下,若在n-1次松弛后还能更新,则说明图中有负环,因此无法得出结果,否则就完成。
//Bellman-Ford算法
struct Edge
{
int from;
int to;
int cost;
}edge[N*2];
int ednum;
bool Bellman(int start,int n)
{
int i,j,flag;
for(i=1;i<=n;i++)
{
dis[i]=inf;//一开始所有点到源点的距离为无限大
vis[i]=0;
}
dis[start]=0;//将源点到自身距离设置为0
for(i=1;i<n;i++)//进行|V|-1次循环
{
flag=0;
for(j=0;j<ednum;j++)
{
if(dis[edge[j].to]>dis[edge[j].from]+edge[j].cost)
{
dis[edge[j].to]=dis[edge[j].from]+edge[j].cost
flag=1;
}
}
if(!flag)break;//若没有松弛,则跳出
}
for(i=1;i<n;i++)//进行第|V|次循环
{
if(dis[edge[i].u]>dis[edge[i].v]+edge[i].w)//如果第V次还能进行松弛,说明存在负环
return true;
}
return false;//第|V|次没有松弛,说明不存在负环。
}
SPFA(Short PathFaster Algorithm)
特点:是bellman-ford的一种队列实现,可以处理负权边,但无法处理负环。只要最短路径存在,上述SPFA算法必定能求出最小值。效率很高。与BFS形式相似,当BFS中一个点出队后就不可能再次进入,而SPFA中一个点可以被反复迭代。如果某个点进入队列的次数超过N次则存在负环
思路:初始时将源加入队列,每次从队列中取出一个元素,并对所有与它相邻的点松弛,若松弛成功,则将其入队,直到队列为空时算法结束。
int SPFA(int n,int s)
{
int i,head=0,tail=0,p,t;
memset(vis,0,sizeof(vis));
for(i=1;i<=n;i++)dis[i]=inf;//初始化所有点的距离到源点为无限大
dis[s]=0;//将起点到起点距离设置为0
vis[s]=1;//将起点入队并标记为已访问
que[tail++]=s;
while(head<tail)
{
p=que[head];
for(i=1;i<=pnt[p][0];i++)
{
t=pnt[p][i];
if(dis[p]+map[p][t]<dis[t])
{
dis[t]=dis[p]+map[p][t];
if(!vis[t])
{
que[tail++]=t;
vis[t]=1;
flag[t]++;//记录每个点进入队列的次数
if(flag[t]>n)return 0;//如果某个点进入队列次数超过n次则存在负环
}
}
}
vis[p]=0;
head++;
}
return 1;
}
SPFA(dfs版本)
特点:判断负环更快
//SPFA dfs写法,判断负环更快
int spfa_dfs(int u)
{
vis[u]=1;
for(int k=head[u]; k!=0; k=edge[k].next)
{
int v=edge[k].v,w=edge[k].w;
if( d[u]+w < d[v] )
{
d[v]=d[u]+w;
if(!vis[v])
{
if(spfa_dfs(v))
return 1;
}
else
return 1;
}
}
vis[u]=0;
return 0;
}
floyd算法
特点:全源最短路径 时间复杂度o(n^3) 空间 o(n^2),边权可正可负,不适合
思路:利用动态规划的思路来寻找任意两点之间的最短路径,其状态转移方程为map[i,j]:=min{map[i,k]+map[k,j],map[i,j]}
void floyd()
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(dis[i][j]>dis[i][k]+dis[k][j])
dis[i][j]=dis[i][k]+dis[k][j];
}