1.图的邻接表 模板
#include <stdio.h>
int main()
{
int u[10],v[10],w[10],first[10],next[10];
int n,m,i,j;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
{
first[i] = -1;
}
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&u[i],&v[i],&w[i]);
next[i] = first[u[i]]; // first[u[i]] 保存定点u[i] 的第一条边的编号
first[u[i]] = i; //next[i] 存储编号为i的边的下一条边的 编号
}
printf("\n\n\n");
int k;
for(i=1;i<=n;i++)
{
k = first[i];
while(k!=-1)
{
printf("%d %d %d\n",u[k],v[k],w[k]);
k = next[k];
}
}
return 0;
}
2 Dijkstra 算法
时间复杂度为O((M+N)log N),空间复杂度为O(m),
适合稠密图,不可以处理负权问题
其基本原理是:每次新扩展一个距离最短的点,更新与其相邻的点的距离。当所有边权都为正时,由于不会存在一个距离更短的没扩展过的点,所以这个点的距离永远不会再被改变,因而保证了算法的正确性。不过根据这个原理,用Dijkstra求最短路的图不能有负权边,因为扩展到负权边的时候会产生更短的距离,有可能就破坏了已经更新的点距离不会改变的性质。
算法步骤:
a.初始时,S只包含源点,即S={v},v的距离为0。U包含除v外的其他顶点,即:U={其余顶点},若v与U中顶点u有边,则<u,v>正常有权值,若u不是v的出边邻接点,则<u,v>权值为∞。
b.从U中选取一个距离v最小的顶点k,把k,加入S中(该选定的距离就是v到k的最短路径长度)。
c.以k为新考虑的中间点,修改U中各顶点的距离;若从源点v到顶点u的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值的顶点k的距离加上边上的权。
d.重复步骤b和c直到所有顶点都包含在S中。
#include <stdio.h>
#include <string.h>
int main()
{
int e[10][10],dist[10],book[10],i,j,n,m,t1,t2,t3,u,v,min;
int inf = 0x3f3f3f3f;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
if(i==j)
{
e[i][j] = 0;
}
else
{
e[i][j] = inf;
}
}
}
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&t1,&t2,&t3);
if(e[t1][t2]>t3)
{
e[t1][t2] = e[t2][t1] = t3;
}
}
memset(dist,inf,sizeof(dist));
for(i=1;i<=n;i++)
{
dist[i] = e[1][i];
}
for(i=1;i<=n;i++)
{
book[i] = 0;
}
book[1] = 1;
for(i=1;i<=n-1;i++)
{
min = inf;
for(j=1;j<=n;j++)
{
if(!book[j] && dist[j]<min)
{
min = dist[j];
u = j;
}
}
book[u] = 1;
for(v=1;v<=n;v++)
{
if(e[u][v]!=inf && dist[v] > dist[u] + e[u][v])
{
dist[v] = dist[u] + e[u][v];
}
}
}
for(i=1;i<=n;i++)
{
printf("%d ",dist[i]);
}
return 0;
}
/*
6 9
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4
0 1 8 4 13 17
*/
3.Bellman_ford 算法
/*
Bellman_ford 算法时间复杂度 O(N*M) ,空间复杂度O(m),适合稀疏图,可以解决负权问题。
*/
算法描述:
输入:图 和 源顶点src
输出:从src到所有顶点的最短距离。如果有负权回路(不是负权值的边),则不计算该最短距离,
没有意义,因为可以穿越负权回路任意次,则最终为负无穷。
算法步骤:
1.初始化:将除源点外的所有顶点的最短距离估计值 d[v] ← +∞, d[s] ←0;
2.迭代求解:反复对边集E中的每条边进行松弛操作,使得顶点集V中的每个顶点v的最短距离估计值逐步逼近其最短距离;(运行|v|-1次)
3.检验负权回路:判断边集E中的每一条边的两个端点是否收敛。如果存在未收敛的顶点,则算法返回false,表明问题无解;否则算法返回true,并且从源点可达的顶点v的最短距离保存在 d[v]中。
#include <stdio.h>
int main()
{
int dist[10],bak[10],i,k,n,m,u[10],v[10],w[10],check,flag;
int inf = 0x3f3f3f3f;
scanf("%d%d",&n,&m);
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&u[i],&v[i],&w[i]);
}
for(i=1;i<=n;i++)
{
dist[i] = inf;
}
dist[1] = 0;
for(k=1;k<=n-1;k++)
{
for(i=1;i<=n;i++)
{
bak[i] = dist[i];//把记录距离的数组备份一下
}
for(i=1;i<=m;i++)
{
if(dist[v[i]]>dist[u[i]]+w[i])
{
dist[v[i]]=dist[u[i]]+w[i];
}
}
check = 0; //松弛后检测dist数组是否有更新
for(i=1;i<=n;i++)
{
if(bak[i]!=dist[i])
{
check = 1;
break;
}
}
if(check==0) //如果没有更新,提前退出循环结束算法
{
break;
}
}
flag = 0; //判断是否有负权回路
for(i=1;i<=m;i++)
{
if(dist[v[i]]>dist[u[i]]+w[i])
{
flag = 1;
}
if(flag)
{
printf("此图含有负权回路\n");
}
}
for(i=1;i<=n;i++)
{
printf("%d ",dist[i]);
}
printf("\n");
return 0;
}
/*
5 5
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
0 -3 -1 2 4
*/
4 Floyed算法
/*
Floyed算法,空间复杂度为o(n^2),时间复杂度为o(n^3),适合稠密图,可以解决带负权问题
*/
1)算法思想原理:
Floyd算法是一个经典的动态规划算法。用通俗的语言来描述的话,首先我们的目标是寻找从点i到点j的最短路径。从动态规划的角度看问题,我们需要为这个目标重新做一个诠释(这个诠释正是动态规划最富创造力的精华所在)
从任意节点i到任意节点j的最短路径不外乎2种可能,1是直接从i到j,2是从i经过若干个节点k到j。所以,我们假设Dis(i,j)为节点u到节点v的最短路径的距离,对于每一个节点k,我们检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,我们便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。
2).算法描述:
a.从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。
b.对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比己知的路径更短。如果是更新它。
#include <stdio.h>
int main()
{
int e[10][10],k,i,j,n,m,t1,t2,t3;
int inf = 0x3f3f3f3f;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
{
if(i==j)
{
e[i][j] = 0;
}
else
{
e[i][j] = inf;
}
}
}
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&t1,&t2,&t3);
e[t1][t2] = t3;
}
for(k=1;k<=n;k++)
{
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
if(e[i][j]>e[i][k]+e[k][j])
{
e[i][j] = e[i][k] + e[k][j];
}
}
}
}
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
printf("%8d ",e[i][j]);
}
printf("\n");
}
return 0;
}
/*
4 8
1 2 2
1 3 6
1 4 4
2 3 3
3 1 7
3 4 1
4 1 5
4 3 12
*/
5 spfa算法
/*
spfa算法 空间复杂度O(m),时间复杂度最坏也是O(m*n),适合稀疏图,可以解决负权
判断负权,如果某个定点入队次数超过n次,那个这个图肯定存在负权。
算法大致流程。初始化将源点加入队列,每次从队首(head)中取出一个顶点,并对与
其相邻的所有定点进行松弛尝试,若某个相邻的顶点松弛成功,且这个相邻的顶点不在队列中
(不在head和tail之间),则将它加入到队列中。对当前顶点处理完毕后立即出队,并对下一个
新队首进行如下操作,直到队列为空时算法结束。
该算法与广度优先遍历算法十分相似,不同的是广度优先遍历的时候一个顶点出队后通常就不会再重新
进入队列, 而这里一个顶点出队后,可能会再重新进入队列。
*/
#include <stdio.h>
int main()
{
int n,m,i,j,k;
int u[8],v[8],w[8];//要比m的最大值大一
int first[6],next[8];//first数组要比n的最大值大一,next数组要比m的最大值大一
int dist[6]={0};
int book[6]={0};//book数组记录哪些定点已经在队列中
int que[101]={0},head=1,tail=1;
int inf = 0x3f3f3f3f;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
{
dist[i] = inf;
book[i] = 0;
first[i] = -1;
}
dist[1] = 0;
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&u[i],&v[i],&w[i]);
next[i] = first[u[i]];
first[u[i]] = i;
}
que[tail] = 1; //1号顶点入队
tail ++ ;
book[1] = 1;
while(head<tail)
{
k = first[que[head]];
while(k!=-1)
{
if(dist[v[k]]>dist[u[k]]+w[k])
{
dist[v[k]] = dist[u[k]] + w[k];
if(book[v[k]]==0) //判断v[k]是否在队列中,不在,加入队列中
{
que[tail] = v[k];
tail ++ ;
book[v[k]] = 1;
}
}
k = next[k];
}
book[que[head]] = 0;
head++;
}
for(i=1;i<=n;i++)
{
printf("%d ",dist[i]);
}
printf("\n");
return 0;
}
/*
5 7
1 2 2
1 5 10
2 3 3
2 5 7
3 4 4
4 5 5
5 3 6
0 2 5 9 9
*/