您的位置:首页 > 其它

prim最小生成树

2017-01-17 22:21 281 查看
一、关于最小生成树的问题引入

布线问题

比如随意给你很多个点,你要将他们全部连起来,要连接n个点,如何只使用n-1根连线,并且尽量使得连线总距离最短

将这问题用一个连通无向图来表示。

我们希望在很多的连通方式中,找到一个无环的连通方式,既能够将所有结点连接起来,又具有最小的权重。由于T是无环的,并且连通所有的结点,因此T必然是一颗树。我们称这样的树为生成树。求取生成树的问题为最小生成树问题。比如如下这张图。

二、关于prim算法的思路

这是一个典型的贪心算法,由局部最优推导出全局最优。把每次已经点亮的节点、所有可到达的边中权重最小的边选入(注意,不能构成回路)




(1)假设从a开始点亮,到b是4,到h是8,那么选择b

(2)a b都被点亮,且两个到c和h都是最小边8,都可以选择,这时候就看你的代码顺序了,并无妨碍

(3)接下来abc点亮,他们三个连接的最小边,是c到i,接下来几步稍加省略了

(4)到(g)图这个时候注意,不能选择h和i,因为他们会构成回路,应该选择c与d

简单解释一下为什么可以用贪心算法,因为每一次你选进来的边是采用局部最优的方法选取的 ,最短而又不构成回路。而全局来看,这条边又不得不选。恰好吻合。

最后是接下来要用到的邻接矩阵



三、代码

#include<iostream>
#include<cstring>
#include<vector>
#include<fstream>
using namespace std;

const int maxn=100;    //顶点数
const int INF=0x7fffffff;
void inputgraph ();  //初始化并邻接表存图
void prime();
struct edge  //边
{
//int from不需要;因为edge在主函数会创建graph的二维数组,而from就是每个二维数组的行值
int to;    //到达的点
int cost;  //边的权重或称花费
bool flag; //是否入选
};
int choosed[maxn];      //已选顶点
int nodeNum,edgeNum,MST;  //顶点数、边数、最小生成树
bool scan[maxn];//是否已经扫描(点亮)
vector <edge>  graph[maxn];//邻接图表

int main()
{
while(scanf("%d%d",&nodeNum,&edgeNum)==2)  //输入图的点数、边数
{
inputgraph();
prime();
}
system("pause");
return 0;
}
void inputgraph ()   //初始化邻接表
{
//ifstream fin;
//fin.open("input.txt");
for(int j=0;j<maxn;j++)  //清空
{
graph[j].clear();
}
int from,to,cost,num=0;  //从from到to花费cost的边
for(int i=0;i<edgeNum;i++)
{
cin>>from>>to>>cost; //建议把cin改成fin文件输入方式
choosed[num++]=from; //  choosed[0]十分重要,其他内容无关紧要因为会再更新。
//开始构建无向图
edge tmp;
tmp.flag=false;
tmp.cost=cost;
tmp.to=to;
graph[from].push_back(tmp);//graph[i][j]的出处,i由from表示,j是每一次pushback后的tmp
tmp.to=from;  //无向图注意from和to同等地位处理
graph[to].push_back(tmp);
}
}

void prime()  //prime算法
{
MST=0;
memset(scan,false,sizeof(scan));
scan[choosed[0]]=true;  //初始点已经点亮
int choosedNum=1,from,to,cost,index;
while(choosedNum<nodeNum)
{
cost=INF;
for(int i=0;i<choosedNum;i++)  //每选入一个点,就多一次的扫描
{
for(int j=0;j<graph[choosed[i]].size();j++)  //扫描一个点亮的节点
{
edge tmp=graph[choosed[i] ][j];   //把表中的这个edge拿去接下来的三个if判断一下
if(!scan[tmp.to] && !tmp.flag && tmp.cost<cost)  // 如果这个节点想要去的to节点没有被点亮过(避免回路),如果这个节点没有被选,如果这个节点代价小
{
from=choosed[i];//看一下这个from
to=tmp.to;
cost=tmp.cost;
index=j;
}
}
}
graph[from][index].flag=true;  //将找到的边标志为已选
//无向图,从from->to的边已选,那么从to->from的边也已选
for(int k=0;k<graph[to].size();k++)
{
if(graph[to][k].to==from)
{
graph[to][k].flag=true;
break;
}
}
cout<<"第"<<choosedNum<<"引进边的重量为"<<cost<<endl;
choosed[choosedNum++]=to;  //将选择的点亮的点放到已选点放到已选的集合中
scan[to]=true;
MST+=cost;   //最小生成树费用增加
}
printf("%d\n",MST);
}


四、案例测试

(输入格式)

9 14

1 2 4

1 8 8

2 3 8

2 8 11

3 4 7

3 6 4

3 9 2

4 5 9

4 6 14

5 6 10

6 7 2

7 8 1

7 9 6

8 9 7

(因为自己测试时候用了ifstream,这里直接输出结果)

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: