一句话概括:最小生成树是计算从一节点到另一节点的最小边集;最短路是带权路径,计算权值最小。也就是说,最小生成树要经过每一个点,而最短路只需要能达到某两点,路径权值最小即可!
两个算法具有相当大的相似性,而且都用到了贪心思想,所以把他们放到一起。
【最短路】常用的算法有dijkstra,bellman-ford,floyd,而【最小生成树】则是prim和kruskal。下面是各个算法的模板。
【Dijkstra复杂度O(n^2)】
#include<stdio.h> //最短路
#define maxsum 0x3fffffff
int map[101][101],dist[101],s[101];
void Dijkstra(int n,int x) //n,1,递推实现
{
int mindis,u,i,j;
for(i=1;i<=n;i++)
{
dist[i]=map[i][x];//map[x][i] is OK,dist表示i到原点的最小距离!
s[i]=0; //printf("%d/n",dist[i]);
}
s[x]=1;// x=1
for(i=1;i<=n;i++)
{
mindis=maxsum;
u=-1;
// 找出当前未使用的点j的dist[j]最小值
for(j=1;j<=n;j++)
if(s[j]==0 && dist[j]<mindis)//------------(2)
{
u=j; //保存当前邻接点中最小的
mindis=dist[j];
}
s[u]=1;//u点已存入s集合,最小的!所以放外面
//更新dist
for(j=1;j<=n;j++) //-------------------(3)
if(s[j]==0)
if( dist[u]+map[u][j]<dist[j] && map[u][j]<maxsum) //从u点发散出去寻找,map[u][j]<maxsum存在权值 ,map[j][u] is OK
dist[j]=dist[u]+map[u][j];
}
}
int main()
{
int n,m,a,b,c,i,j;
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==0&&m==0) break;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
map[i][j]=maxsum;//初始化邻接矩阵
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
map[a][b]=map[b][a]=c;//构造邻接矩阵,对称的!无向!
}
Dijkstra(n,1);
printf("%d/n",dist[n]);
}
return 0;
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
【最小生成树的prim模板:复杂度O(n^2)】
#include<iostream>//最小生成树
#define INF 0x1f1f1f1f
#define M 1000
using namespace std;
double dis[M],map[M][M];
bool flag[M];
int prim(int s,int n) //s为起点,n为点的个数
{
int i,j,k,temp,md,total=0;
for(i=1;i<=n;i++)
dis[i]=map[s][i]; //与最短路不同,而是将dis置为map[s][i]
memset(flag,false,sizeof(flag));
flag[s]=true; //将起点加入集合
for(i=1;i<n;i++){ //依旧进行n-1次迭代,每次找到不在集合的最小边(n个点有n-1条边)!!!!!!
md=INF;
for(j=1;j<=n;j++){
if(!flag[j]&&dis[j]<md){
md=dis[j];
temp=j;
}
}
flag[temp]=true; //将找到的最小边的点加入集合
total+=md; //并将这个边的权值加到total中
for(j=1;j<=n;j++) //松弛操作,注意与最短路不同
if(!flag[j]&&dis[j]>map[temp][j])
dis[j]=map[temp][j];
}
return total;
}
【Kruskal最小生成树模板 复杂度O(E*logE)】
typedef struct edge
{
int a;
int b;
int value;
}edge;
edge edges[earraysize];
int final[narraysize]; //存储父节点 中括号里面是儿子,外面是父亲
int nodecount[narraysize]; //存储该节点孩子结点的个数
bool cmp(edge a,edge b)
{
return a.value<b.value;
}
int findp(int x) //寻找父亲
{
while(x!=fa[x])
x=fa[x];
return x;
}
bool Union(int x,int y) //合并
{
int rootx=findp(x); /*为什么要找父亲?因为要判是否有回路,假如父亲相同,而x跟y连通,那么就形成了回路*/
int rooty=findp(y);
if(rootx==rooty)
return false;
else if(nodecount[rootx]<=nodecount[rooty]) //优化,把深度小的子树加到深度大的子树,减少树的高度
{
final[rootx]=rooty; /*其实不优化也可以直接final[rootx]=rooty或者final[rooty]=rootx也ok */
nodecount[rooty]+=nodecount[rootx];
}
else
{
final[rooty]=rootx;
nodecount[rootx]+=nodecount[rooty];
}
return true;
}
int main ()
{
//freopen("a.txt","r",stdin);
int num=0;
int n,m;
int i,j;
while ( scanf ( "%d%d", &n, &m ) != EOF )
{
num=0; //记录生成树中的边的数目
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&edges[i].a,&edges[i].b,&edges[i].value);
}
for(i=1;i<=n;i++) //初始化
{
final[i]=i;
nodecount[i]=1;
}
sort(edges+1,edges+m+1,cmp); //排序
for(i=1;i<=m;i++) //遍历所有的边
{
if(Union(edges[i].a,edges[i].b)) //合并
{
num++;
}
if(num==n-1) //找到了最小生成树
break;
}
}
return 0;
}
*****************************************************************************************************************************************************************************************
*****************************************************************************************************************************************************************************************
下面是最短路的bellmen-ford算法,与dijkstra不同,bellman-ford可以运用于有负权值的图,不过复杂度很高,O(VE )... 慎用~(可以用SPFA,它bwllman-ford的扩展)
Bellman-ford算法同样是对每条边进行N-1次松弛,当有权值为负时,对所有边进行N-1次松弛,如果dis还能更新,说明有负环。
ps:名词解释【负环】 在一个图里每条边都有一个权值(有正有负)
如果存在一个环(从某个点出发又回到自己的路径),而且这个环上所有权值之和是负数,那这就是一个负权环,也叫负权回路
存在负权回路的图是不能求两点间最短路的,因为只要在负权回路上不断兜圈子,所得的最短路长度可以任意小。
Bellman-ford原理:
1.如果最短路存在,则每个顶点最多经过一次,因此不超过n-1条边;
2.长度为k的路由长度为k-1的路加一条边得到;
3.由最优性原理,只需依次考虑长度为1,2,…,k-1的最短路。
Bellman-ford模板:
#include<stdio.h>//最短路
#include<string.h>
#define INF 0x1f1f1f1f
#define MAX 102
#define MAXM 20008
int dist[MAX];
struct Edge{ //边结构体定义
int u, v, w;
Edge(){}
Edge(int a, int b, int c):u(a), v(b), w(c){}
}edge[MAXM];
int bellman_ford(int n, int m, int s) //n个点、m条边、s为起点
{
memset(dist, 0x1f, sizeof(dist)); //初始化距离很大
dist[s] = 0;
int i, j, u, v, f;
for (i = 1; i < n; ++i) //迭代 n - 1 次,对每条边进行n-1次松弛
{
f = 0;
for (j = 0; j < m; ++j)
{
u = edge[j].u;
v = edge[j].v;
if (dist[v] > dist[u] + edge[j].w) // 松弛操作
{
dist[v] = dist[u] + edge[j].w;
f = 1;
}
}
if (!f) return 1; //如果其中一次迭代没改变,停止
}
for(j = 0; j < m; ++j) //再进行一次迭代
{
u = edge[j].u;
v = edge[j].v;
if (dist[v] > dist[u] + edge[j].w) //若还能松弛, 则存在负环
return -1; //存在负环返回 -1
}
return 1; //没有负环返回 1
}
算法结束后dist数组已经是最短路径。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
【SPFA模板: 期望的时间复杂度O(KE), 其中K为所有顶点进队的平均次数,可以证明K一般小于等于2。】
#include<iostream>//最短路
#include<string.h>
#include<cstdio>
#include <vector>
#include <queue>
using namespace std;
#define N 50001int
INF = 0x7fffffff;
int n,m;
typedef struct edge
{
int to;
int w;
}edge,temp;
vector<edge> adjmap[N]; //vector实现邻接表
int d[N];
bool vis[N]; //记录顶点是否在队列中,SPFA算法可以入队列多次
int cnt[N]; //记录顶点入队列次数
void SPFA()
{
queue<int>
myqueue;
int i;
for(i=1;i<=n;++i)
d[i] = INF; //将除源点以外的其余点的距离设置为无穷大
memset(vis,0,sizeof(vis));
memset(cnt,0,sizeof(cnt));
d[1]=0; //源点的距离为0
vis[1] = true;
cnt[1]++; //源点的入队列次数增加
myqueue.push(1);
int topint;
while(!myqueue.empty())
{
topint = myqueue.front();
myqueue.pop();
vis[topint] = false;
for(i=0;i<adjmap[topint].size();++i)
{
int to = adjmap[topint][i].to;
if(d[topint]<INF && d[to]>d[topint]+ adjmap[topint][i].w)
{
d[to] = d[topint]+ adjmap[topint][i].w;
if(!vis[to])
{
vis[to] = true;
cnt[to]++;
if(cnt[to]>=n) //当一个点入队的次数>=n时就证明出现了负环。
return ;
myqueue.push(to);
}
}
}
} printf("%d/n",d[n]);
}
int main()
{
//freopen("a.txt","r",stdin);
scanf("%d%d",&n,&m);
int i;
int s,e,w;
edge temp;
for(i=1;i<n+1;++i) //此处特别注意对邻接表清空
adjmap[i].clear();
for(i=0;i<m;++i) //双向
{
cin>>s>>e>>w;
temp.to = e;
temp.w = w;
adjmap[s].push_back(temp);
temp.to = s;
adjmap[e].push_back(temp);
}
SPFA();
return 0;
}