最短路与最小生成树
来源:互联网 发布:讲课软件 编辑:程序博客网 时间:2024/06/01 08:27
最小生成树能够保证整个拓扑图的所有路径之和最小,但不能保证任意两点之间是最短路径。
最短路径是从一点出发,到达目的地的路径最小。
一句话概括:最小生成树是计算从一节点到另一节点的最小边集;最短路是带权路径,计算权值最小。
也就是说,最小生成树要经过每一个点,而最短路只需要能达到某两点,路径权值最小即可!**
最短路的算法有Floyd、Dijkstra、Bellman,SPFA
单源就是从一个点到所有其他点的最短路径,得到的结果是一个数组,表示某个点到其他点的最短距离。常用的算法有Dijkstra算法和Bellmanford算法。
多源最短路径计算所有点到其他点的最短距离,得到的是一个矩阵。常用的算法有Floyd算法
Dijkstra:适用于权值为非负的图的单源最短路径,用斐波那契堆的复杂度O(E+VlgV)
BellmanFord:适用于权值有负值的图的单源最短路径,并且能够检测负圈,复杂度O(VE)
SPFA:适用于权值有负值,且没有负圈的图的单源最短路径,论文中的复杂度O(kE),k为每个节点进入Queue的次数,且k一般<=2,但此处的复杂度证明是有问题的,其实SPFA的最坏情况应该是O(VE).
Floyd:每对节点之间的最短路径。
先给出结论:
(1)当权值为非负时,用Dijkstra。
(2)当权值有负值,且没有负圈,则用SPFA,SPFA能检测负圈,但是不能输出负圈。
(3)当权值有负值,而且可能存在负圈,则用BellmanFord,能够检测并输出负圈。
(4)SPFA检测负环:当存在一个点入队大于等于V次,则有负环
Floyd——多源最短路径
只有五行的算法——Floyd-Warshall
Floyd算法用来找出每对顶点之间的最短距离,它对图的要求是,既可以是无向图也可以是有向图,边权可以为负,但是不能存在负环(可根据最小环的正负来判定).
以所有定点为中转(k),求得任意两点之间的最短路径(i,j)
核心代码:
for(int k=1; k<=n; k++) for(int i=1; i<=n; i++) for(int i=1; j<=n; j++) if(e[i][j]>e[i][k]+e[k][j])
Dijkstra
每次找 距离源点最近的一个顶点,然后以该顶点为中心进行扩展,(在集合Q中的所有顶点中选择里源点s最近的顶点u,加入到集合P,
并考虑所有以u为起点的边,对每一条边进行松弛操作。重复此过程直到Q为空)最终得到源点到其余所有点的最短路径。
#include<cstdio>#include<cmath>#include<cstring>#include<iostream>#include<algorithm>using namespace std;#define inf 0x3f3f3f;int g[1005][1005];int vis[1005];int dis[1005];int x,y,len,n,t;int temp,minj;int main(){ while(~scanf("%d%d",&t,&n)) { memset(vis,0,sizeof(vis)); memset(dis,0,sizeof(dis)); memset(g,0,sizeof(g)); //初始化 for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { if(i==j) g[i][j]=0; else g[i][j]=inf; } } //读入边 for(int i=1;i<=t;i++) { scanf("%d%d%d",&x,&y,&len); if(g[x][y]>len) g[x][y]=g[y][x]=len; } for(int i=1;i<=n;i++) dis[i]=g[1][i]; vis[1]=1; for(int i=1;i<=n;i++) { minj=inf; for(int j=1;j<=n;j++) { if(vis[j]==0&&dis[j]<minj) { minj=dis[j]; temp=j; } } vis[temp]=1; for(int j=1;j<=n;j++) { if(vis[j]==0) dis[j]=min(dis[j],dis[temp]+g[temp][j]); } } printf("%d\n",dis[n]); }}
Bellman——解决负权边
Dijkstra虽然好,但是不能解决带负权边的图,每次松弛以上次的确定的某些最短路计算出下面的最短路。
核心代码:
for(int k=1; k<=n-1; k++)
for(int i=1; i<=m; i++)
if(dis[v[i]]>dis[u[i]]+w[i])
dis[v[i]]=dis[u[i]]+w[i];
外环循环了n(点的个数)次,内环循环了m(边的个数)次,
松弛操作只需进行n-1次就行了。因为在一个含有n个顶点的图中,任意两点之间的最短路径最多包含n-1边。
另外,Bellman算法可以检验一个图中是否含有负权边。
如果进行n-1轮松弛后,仍存在if(dis[v[i]]>dis[u[i]]+w[i]) dis[v[i]]=dis[u[i]]+w[i];的情况,
说明在进行n-1轮松弛后,仍然可以继续松弛成功,那么此图必然存在负权回路,
关键代码:
```//Bellman核心代码for(int k=1; k<=n-1; k++) for(int i=1; i<=m; i++) if(dis[v[i]]>dis[u[i]]+w[i]) dis[v[i]]=dis[u[i]]+w[i];//检测负权回路 flag=0;for(i=1;i<=m;i++) if(dis[v[i]]>dis[u[i]]+w[i]) flag=1;if(flag==1) printf("此图有负权回路");
SPFA 用两副代码帮助组理解运用,其中有用到 邻接表
邻接表理解可见
1.此代码理解起来比较顺利
include<stdio.h>using namespace std;int n,m,i,j,k;int u[8],v[8],w[8];int first[6];//比 n 大 1;int next[8]; //比m大1;int dis[6]= {0};int book[6]= {0}; //标记是否在队列中int que[101]= {0},head=1,tail=1;int inf=9999999;void bfs(){ //1号顶点入队 que[tail]=1; tail++; book[1]=1; while(head<tail)//队列不为空时 { k=first[que[head]];//当前需要处理的队首顶点 while(k!=-1)//扫描当前顶点所有的边 { if(dis[v[k]]>dis[u[k]]+w[k]) { dis[v[k]]=dis[u[k]]+w[k];//更新顶点1到顶点v[k]的路程 //book盘点顶点v[k]是否在队列中 if(book[v[k]]==0)//表示V[k]不在队中, { //入队 que[tail]=v[k]; book[v[k]]=1; tail++; } } k=next[k] ; } //出队; book[que[head]]=0; head++; }}int main(){ scanf("%d%d",&n,&m); for(int i=1; i<=n; i++) dis[i]=inf; dis[1]=0; for(int i=1; i<=n; i++) book[i]=0; for(int i=1; i<=n; i++) first[i]=-1;//-1表示1~n暂时没边 for(int i=1; i<=m; i++) { scanf("%d%d%d",&u[i],&v[i],&w[i]); next[i]=first[u[i]];//建立邻接表关键 first[u[i]]=i;// } bfs(); // for(int i=1; i<=n; i++) printf("%d ",dis[n]); printf("\n"); getchar(); getchar();}
2.此代码本人默认
#include<cstdio>#include<cstring>#include <iostream>#include<cmath>#include<algorithm>#include <queue>using namespace std;const long MAXN=10005;const long lmax=0x7FFFFFFF;typedef struct{ long v; long next; long cost;} Edge;Edge e[MAXN];long p[MAXN];long Dis[MAXN];bool vist[MAXN];queue<long> q;long m,n;//点,边void init(){ long i; long eid=0; memset(vist,0,sizeof(vist)); memset(p,-1,sizeof(p)); fill(Dis,Dis+MAXN,lmax); while (!q.empty()) { q.pop(); } for (i=0; i<n; ++i) { long from,to,cost; scanf("%ld %ld %ld",&from,&to,&cost); e[eid].next=p[from]; e[eid].v=to; e[eid].cost=cost; p[from]=eid++;//以下适用于无向图 swap(from,to); e[eid].next=p[from]; e[eid].v=to; e[eid].cost=cost; p[from]=eid++; }}void print(long End){//若为lmax 则不可达 printf("%ld\n",Dis[End]);}void SPF(){ init(); long Start,End; scanf("%ld %ld",&Start,&End); Dis[Start]=0; vist[Start]=true; q.push(Start); while (!q.empty()) { long t=q.front(); q.pop(); vist[t]=false; long j; for (j=p[t]; j!=-1; j=e[j].next) { long w=e[j].cost; if (w+Dis[t]<Dis[e[j].v]) { Dis[e[j].v]=w+Dis[t]; if (!vist[e[j].v]) { vist[e[j].v]=true; q.push(e[j].v); } } } } print(End);}int main(){ while (scanf("%ld %ld",&m,&n)!=EOF) { SPF(); } return 0;}
最小生成树 算法包括kruskal、prim
Kruskal算法按照边的权值的顺序从小到大查看一遍,如果不产生重边,就把当前这条边加入到生成树中。
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; }
补充一个便于理解的
#include<cstdio>struct node{ int u,v,w;}e[101];//范围比m大一int n,m;int f[7]={0};//范围比n大一int sum=0;int count=0;//快排void quicksort(int left,int right){ int i,j; struct node t; if(left>right) return; i=left; j=right; while(i!=j) { //顺序很重要,要先从右边开始找 while(e[j].w>e[left].w&&i<j) { j--; } while(e[i].w<e[left].w&&i<j) { i++; } //交换 if(i<j) { t=e[i]; e[i]=e[j]; e[j]=t; } } t=e[left]; e[left]=e[i]; e[i]=t; quicksort(left,i-1);//继续处理左边 quicksort(i+1,right);//继续处理右边 return;}int find(int v){ if(v==f[v]) return v; else {//路径压缩 f[v]=find(f[v]); return f[v]; }}int merge(int v,int u){ int t1,t2; t1=find(v); t2=find(u); if(t1!=t2)//判断两个点是否在一个集合中 { f[t2]=t1; return 1; } return 0;}int main(){ int i; scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w); quicksort(1,m);按权值从小到大排序 for(int i=1;i<=n;i++)//初始化 f[i]=i;//Kruskal算法核心 for(int i=1;i<=m;i++) {//判断一条边的两个顶点是否已经连通,即是否在一个集合中 if(merge(e[i].u,e[i].v)) { count++; sum+=e[i].w; } if(count==n-1)//直到选用了n-1条边智之后退出循环 break; } printf("%d\n",sum);}
Prim算法
用dis记录“生成树”到各个顶点的距离,与Dijkstra不同,不是每个顶点到1号的最短距离,
而是每个顶点到任意一个“树顶点”(已被选入生成树的顶点)的最短距离,
如果dis[k]>e[i][j]则更新dis[k]=e[i][j];在计算式更新最短路径的时候不用加上dis[j],
因为我们的目的非要靠近1号顶点,而是靠近“生成树”就可以了,
也就是说只要靠近生成树中的任意一个“树顶点”就行了。
重点步骤:从数组中选出离生成树最近的顶点j,加入到生成树中,再以j为中间点,
更新生成树到没一个非树顶点的距离(松弛), (即 如果dis[k]>e[i][j]则更新dis[k]=e[i][j];)
重复此步骤,直到生成树中有n个顶点。
#define INF 0x1f1f1f1f#define M 1000using 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;}
- 最短路与最小生成树
- 最短路(最小生成树)
- 最小生成树与最短路的区别
- 【区别】最短路&最小生成树
- BOJ 333 最小生成树+最短路
- 区别 最短路跟最小生成树
- 图论 最小生成树 和最短路
- 最小生成树&最短路模板
- 【模板】最短路&&最小生成树
- uva 10816 最小生成树 + 最短路
- 并查集&最小生成树、最短路
- 图论--最小生成树和最短路1
- POJ 1797 Heavy Transportation(最小生成树或最短路)
- BZOJ 4144: [AMPPZ2014]Petrol 最短路+最小生成树+倍增
- bzoj 4144 [AMPPZ2014]Petrol 最短路+最小生成树+倍增
- 【BZOJ 4144】[AMPPZ2014]Petrol 最短路+最小生成树
- 最小生成树 最短路 并查集
- NOIP2017 7.17模拟 Minimum (最短路+最小生成树)
- rm: cannot remove `dir-name': Directory not empty(文件夹里有.fuse_hidden0000007f00000002)
- CSS之flex需要知道的一切(一)
- Android Studio Default Activity not found 报错
- 算法导论第15章练习题 15.4-5
- python list 求交集 差集 并集
- 最短路与最小生成树
- iOS模式详解runtime
- TelecomService启动流程
- 1.深度学习-初识之历史发展
- python 类(下)
- spring bean循环引用问题
- 51nod 1717 好数
- Java:Redis安装(Windows),以及Spring MVC与Redis整合
- MyEclipse运行项目的内存溢出问题解决方案