Prim
来源:互联网 发布:手机扫描识字软件 编辑:程序博客网 时间:2024/05/20 05:26
Prim算法
1.问题引入
Prim算法(普里姆算法),图论算法的一种,用来在加权连通图中找到最小生成树。解决在一个连通网中找到一个最小生成树的问题:给定n个城市(编号由1到n),假设n个城市之间两两互相不连通。现在要在n个城市之间修路: 已知n个城市之间有m条公路(每条公路的预算经费已知)经过规划通过了省政府的审批,但为了节省资金,要求从m条公路中选出n-1条使n个城市中任意两个城市之间连通且需要的经费最少。
2,图论中的一些基本概念
在讲解Prim算法之前需要了解一些基本的图论概念:
2.1,图
图G(Graph)由顶点集合V(Vertex)和边集合E(Edge)两部分组成,记作G=(V,E)。通常
用V(G),E(G)分别表示图G的顶点集合和边集合。
2.2,无向图
若图G中的边集合E(G)中的边均为无向边,则图G为无向图。
2.3,子图
有两个图G=(V,E)和G'=(V',E'),若有V'是V的子集(V'∈V)且E' 是E的子集(E'∈E),
则称图G'为图G的子图。
2.4,连通图
若图G=(V,E)中任意两个顶点都连通(直接连接或间接连接),则称 G为连通图。
2.5,权和网
若图中的边都加上特定含义的数值,则称该图为网,称边上的数值为权值(网=图+权)。
2.6,树
有n个顶点和n-1条边组成的无向图G,若G中任意两个顶点之间都连通(直接或间接连接)且图G中
的边都不形成回路,则称此无向图为树。也可以简单的将树说成是没有形成回路的连通图。
2.7,生成树
取连通图G的n个顶点(假设图G的顶点数目为n)和n-1条边构成一个子图G',使G'满足
V(G')=V(G) ,E(G')∈E(G)。若G'是一个树(没有形成回路的连通图),则该树为图G的
一个生成树,也可以是说当图G去掉某些边后成为了一个树。PS:引入树的概念是因为树的性质与
此类问题的解相一致,即任意两个顶点连通且不形成回路,不产生无用的边。
2.8,最小生成树--Minimum Spanning Tree (MST)
图/网(网可以理解为特殊的图--加上权值)的生成树往往不是唯一的,因此这些树中边的权值之和
就会有所不同,将各边的权值之和最小的生成树称为图/网G的最小生成树。PS:最小生成树也不
唯一。
3,Prim算法的基本思想
3.1,初步辨析Prim算法与Kruskal算法
Prim算法常被称为加点法,它通过增加目标图T(Tree)中的顶点来逐步实现最小生成树的生成。
另外一个求解最小生成树的算法是Kruskal(克鲁斯卡尔)算法,这个算法通过增加目标图T的边
(当边数等于n-1时就可停止遍历)实现最小生成树的生成,可以称为加边法。Prim算法因为通
过增加顶点来生成目标,因此可以用来处理边较稠密的图,其时间复杂度为O(n^2)(n为顶点数)
,而 Kruskal算法常被用来边数较稀疏的图,其时间复杂度为elog(e)(e为边的数目)。因为
Kruskal 算法需要用到并查集的知识,所以需要再讲解过并查集之后再进行讲解。
3.2,Prim算法的基本思想
设图G=(V,E)是具有n个顶点的连通网,另外设T(P,S)(P为Peak的缩写,S为Side的缩写)
为目标图,即最小生成树。下面是Prim算法的具体思路:
⑴,开始时V={v1,v2,v3......vn},E={e1,e2,e3......en},P(T),S(T)均为
空集, 即P=Φ,S=Φ。
⑵,从V中任取一个顶点,假设所取点为v1,将v1入P中。此时V={v2,v3......vn},P={v1}。
⑶,找出E中的一个顶点在P中,另一个顶点在V中的所有边中的权值最小的一条边,并
将此边的在V中的邻接点加入到P中(此时V中顶点数为n-2,P中顶点数为2)。
⑷,如果V中顶点数目为0,P中顶点数目为n,证明已经V中将n个顶点和n-1条边加入
到P中,且T此时就是G的一个最小生成树。否则,则进行⑶步骤。
3.3,prim算法的简要证明
反证法:前提--假设prim生成的生成树不是最小生成树:
⑴,设prim生成的树为T;
⑵,假设G'为G的最小生成树,则在G'中存在边<u,>不属于S(T);
⑶,将<u,v>加入T中可得一个环,且<u,v>不是该环的最长边(因为 <u,v> ∈G' );
⑷,这与prim每次生成最短边矛盾;
⑸,故假设不成立,命题得证。
4,Prim算法的实现
4.1,几个需要解决的问题
Prim算法的实现还有以下几个问题:
㈠,如何实现网G的构建:这里我们需要明白当具体实现Prim时我们需要网G储
存所有的起点和终点以及边信息(边的权值)。
解决方法如下:
⑴,对于起点终点的储存我们可以考虑使用邻接矩阵(二维数组),并
将权值赋值给各边。
⑵,如果一边的两点直接相连,我们可以将此边直接赋值。对于不相邻的两点,
我们可以将其边初始化为无穷大(可以用0x3f3f3f3f,0x7fffffff表示无
穷大)。对于矩阵主对角线上的数据(也就是边起点和终点是同一个点),
我们可以将其赋值为0,也可以将其赋值为无穷大。
㈡,如何找出一个顶点在P中,另一个顶点在V中的所有边中的权值最小的一条边:
我们需要将目标可能存在的边一一遍历,将权值最小的边记入总数并标记,同
时将此边的在V中的邻接点加入到P中。
下面介绍两种方法:
⑴,具体实现时我们可能会直接想到运用两层for循环:一层是P中的顶点,一层
是V中的顶点,同时要用两个一维数组分别记录P和V中的顶点,(假设两数组
为P[100],V[100]) 。从V中取点加入P中时所取点要用sign[100]数组标记。
(事实上因为在本文中的问题上因为V中数据是1--n,是连续的,因此不使用V
数组也可以)这样,当P中元素个数为n时表明已经找出最小生成树。
如下表:(代码随后附上)
P V sign第一次P[1]=1V[1]=1,V[2]=2,V[3]=3,V[4]=4,V[5]=5sign[1]=1第二次P[1]=1,P[2]=5V[1]=1,V[2]=2,V[3]=3,V[4]=4,V[5]=5sign[1]=1,sign[5]=1第三次P[1]=1,P[2]=5,P[3]=3V[1]=1,V[2]=2,V[3]=3,V[4]=4,V[5]=5sign[1]=1,sign[3]=1,sign[5]=1第四次P[1]=1,P[2]=5,P[3]=3,P[4]=2V[1]=1,V[2]=2,V[3]=3,V[4]=4,V[5]=5
sign[1]=1,sign[2]=1,sign[3]=1,sign[5]=1第五次P[1]=1,P[2]=5,P[3]=3,P[4]=2,P[5]=4V[1]=1,V[2]=2,V[3]=3,V[4]=4,V[5]=5sign[1]=1,sign[2]=1,sign[3]=1,sign[4]=1,
sign[5]=1
⑵,第⑴种方法对于理解Prim算法的基本思想非常有用,但实际应用中要用到3
重for循环,时间复杂度较高。第⑵种方法理解起来有些困难,但却是最实
用的一种方法:
这里要用到一个一维数组dist[100]用来存储P中的点到V中的点的权值,仍
然用sign[100]标记加入到P中的点。刚开始将定点1加入P,则P={1},
V={2,3,......n},sign[1]=1。(在这里不再使用P,V数组,而仅用sign数
组进行标记)
dist初始化如下:
① sign[1]=1;for(i=1;i<=n;i++){dist[i]=graph[1][i];//将1到其他定点的权值赋值给dist}
接下来要找出dist数组(下标1--n)中权值最小值并标记:
②for(j=1;j<=n;j++) { min=INF; if(!sign[j]&&dist[j]<min)//找出V中一点,用u记录,dist[u]为dist数组中的最小值 { min=dist[j]; u=j; } } s+=min;//记录最小生成树的总权值 sign[u]=1;//标记u,就表示将u加入P中
在②中找到了最小生成树的一个节点(1到其它节点的最小值),但接下来的问题是:如何更新dist数组使dist记录的是:一个顶点在P中,另一个顶点在V中的所有边的权值。
③for(j=1;j<=n;j++){if(!sign[j]&&graph[u][j]<dist[j]){dist[j]=graph[u][j];}}
通过③的代码我们可以看到其实dist不需要记录一个顶点在P中,另一个顶点在V中的所有边的权值,只需要更新时保证一个顶点在P中,另一个顶点在V中的所有边中的权值最小的一条边记录在dist数组中就可以了。在这里通过新加入最小生成树的节点u来更新P中的点到V中的点的权值,使dist[j]记录的是P中的所有点到j(V中的一点)的权值中的最小值,所以下一个最小生成树权值一定包含在dist数组中,且此时dist数组的下标就是最小生成树的一个节点。
PS:对于上面的代码只需要将②③包含在同一个for循环中就可以找出另外n-1个节点。
4.2,代码附上
㈠,基于4.1㈡①的代码实现:
<pre class="cpp" name="code">#include<cstdio>#include<cstring>#define INF 0x3f3f3f3fint graph[100][100];int P[100];//P记录最小生成树的顶点bool sign[100];//标记加入P中的顶点 void prim(int n){int k=1;int u=1;int sum=0;//记录最小生成树的总权值(修建公路的总花费) memset(sign,false,sizeof(sign));P[1]=1;//P中有一个顶点(此顶点可任选),V中有n-1个顶点 sign[1]=true;for(int i=1;i<n;i++)//在往P中加入n-1个顶点 {int min=INF;for(int j=1;j<=k;j++)//两重for循环,找出P中的点到V中的点的最短路径 {for(int h=1;h<=n;h++){if(!sign[h]&&graph[P[j]][h]<min){min=graph[P[j]][h];u=h;}}} sign[u]=true;P[++k]=u;sum+=min;}printf("最小生成树路径:");for(int i=1;i<=k;i++){printf("%d%s",P[i],i==k?"\n":"-->"); } printf("最小生成树总权值:%d\n",sum);}int main(){int n,m;//n个城市,m条边公路 int u,v,w;//u,v两个城市相连且花费为w while(~scanf("%d%d",&n,&m)){memset(graph,INF,sizeof(graph));//graph初始化为INF for(int i=0;i<m;i++){scanf("%d%d%d",&u,&v,&w);graph[u][v]=graph[v][u]=w;//graph所表示的为无向图 }prim(n); } return 0;}
㈡,基于4.1㈡②的代码实现:
#include<cstdio>#include<cstring>#define INF 0x3f3f3f3fint graph[100][100];int P[100];bool sign[100];//标记加入P中的顶点 int dist[100];void prim(int n){memset(sign,false,sizeof(sign));int sum=0;//记录最小生成树的总权值(修建公路的总花费) int u=1,k=1;P[1]=1;//P中有一个顶点(此顶点可任选),V中有n-1个顶点 sign[1]=true; for(int i=1;i<=n;i++)//初始化 {dist[i]=graph[1][i];//将1到其他定点的权值赋值给dist}for(int i=1;i<n;i++)//在往P中加入n-1个顶点 {int min=INF; for(int j=1;j<=n;j++) { if(!sign[j]&&dist[j]<min)//找出V中一点,用u记录,dist[u]为dist数组中的最小值 { min=dist[j]; u=j; } } sign[u]=true;P[++k]=u;sum+=min;for(int j=1;j<=n;j++)//更新dist数组 {if(!sign[j]&&graph[u][j]<dist[j]){dist[j]=graph[u][j];}}}printf("最小生成树路径:");for(int i=1;i<=k;i++){printf("%d%s",P[i],i==k?"\n":"-->"); } printf("最小生成树总权值:%d\n",sum);}int main(){int n,m;//n个城市,m条边公路 int u,v,w;//u,v两个城市相连且花费为w while(~scanf("%d%d",&n,&m)){memset(graph,INF,sizeof(graph));//graph初始化为INF for(int i=0;i<m;i++){scanf("%d%d%d",&u,&v,&w);graph[u][v]=graph[v][u]=w;//graph所表示的为无向图 }prim(n); } return 0;}
㈢,测试实例及结果
根据上图写出测试数据:
6 103 1 13 2 53 4 53 5 63 6 41 2 62 5 35 6 66 4 24 1 5
测试结果(两部分程序结果一致):
本文参考:
①http://wk.baidu.com/view/bd0300d2b14e852458fb57d2?pcf=2
②http://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html
③http://blog.sina.com.cn/s/blog_81ca6e750101gw4n.html
本文写的有许多不足之处,欢迎大家批评指正
- prim
- Prim
- Prim
- prim
- Prim
- prim
- prim
- Prim
- prim
- Prim
- Prim
- Prim
- prim
- prim
- prim
- prim
- Prim
- prim 算法
- 给sublime text3添加rust自动提示
- 引用一个自定义类库中不包含相应的实体类
- boost编译
- Rect类
- eclipse 启动失败,报错org.eclipse.swt.SWTException: Failed to execute runnable
- Prim
- 【Java解析XML】【三】JDOM介绍
- DEDE整站动态化或整站静态化设置方法
- php接收ios/android上传图片
- JAVA开发_身份证校验
- iOS 基础知识总结 self.name = @"老王" 与 _name = @"老王" 的区别
- XML文件读写
- Android L 自动获取时区失败问题的解决
- 继续畅通工程 HDU杭电1879 【Kruscal算法】