Agri-Net 搭互联网线路最短问题

Agri-Net
最短网络
农民约翰被选为他们镇的镇长!他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场。当然,他需要你的帮助。
约翰已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。为了用最小的消费,他想铺设最短的光纤去连接所有的农场。
你将得到一份各农场之间连接费用的列表,你必须找出能连接所有农场并所用光纤最短的方案。
每两个农场间的距离不会超过100000
PROGRAM NAME: agrinet
INPUT FORMAT
第一行: 农场的个数,N(3<=N& lt;=100)。
第二行..结尾: 后来的行包含了一个N*N的矩阵,表示每个农场之间的距离。理论上,他们是N行,每行由N个用空格分隔的数组成,实际上,他们限制在80个字符,因此,某 些行会紧接着另一些行。当然,对角线将会是0,因为不会有线路从第i个农场到它本身。
SAMPLE INPUT (file agrinet.in)
4
0 4 9 21
4 0 8 17
9 8 0 16
21 17 16 0
OUTPUT FORMAT
只有一个输出,其中包含连接到每个农场的光纤的最小长度。
SAMPLE OUTPUT (file agrinet.out)
28

纯最小生成树问题,而且是无向图,无负权值边,以下为kruskal和prim算法伪码。

首先是一个引理,
设A为图G(V,E)的一个最小生成树T的非真子集。       // V为G中所有节点的集和,E为所有边的集和

A初始为空 我们要做的是向A中加入边
引理:如果S,V-S为图G的一个不妨碍A的划分(就是A中没有从S跨越到V-S的边),那么划分(S,V-S)的轻边(所有跨越划分的边中权值最小的边)(u,v)可以将其加入集和A。

证明:
若(u,v)为连接节点u,v的唯一边,则引理显然成立;
若u,v之间存在另一条路径p
因为(u,v)跨越划分,所以路径p上必存在一条边(x,y)跨越划分(S,V-S)
而且w(x,y)>=w(u,v)         // w表示权值
假设(u,v)不属于T
则我们可以构造另一棵最小生成树T’=(T-(u,v))+(x,y)
则w(T)<=w(T’)
假设不成立

kruskal算法将每个节点视为一棵树,寻找连接任意两棵树间的最短边将其加入A,然后将两棵树合并;
伪代码:
A=0;
while A<E do begin          // 当还存在未并合的边时
    (u,v)=min(E);          // 找出所有边中的最小边
    if u and v are not in the same tree then begin        // 找出的最小边不在同一颗树中时
        A=A+(u,v);E=E-(u,v);           // 在A中加入边(u,v),在边集E中除去边(u,v)
        union(tree(u),tree(v));             // 并合u和v两棵树
    end;
end.
以上伪码仅供参考 方法不唯一

tree(u)表示u所在的树 union 表示将两棵树合并为一棵树
正确性证明:
每次加入A中的边都属于同一棵树(因为两棵树被合并了)
所以不同的树之间的边一定不跨越A,且为轻边,
由引理可知算法正确

kruskal算法是寻找最小生成树中的边 而且经过不断的合并最后所有的点会成为一棵树 这颗树就是得到的最小生成树。找边需要大约E,合并需要大约V,该算法时间复杂度为o(EV)

下面给出代码~~~:

Kruscal版:(47ms AC)

#include<iostream>
#include<algorithm>
using namespace std;
int u[5000],v[5000],w[5000],r[5000],p[5000],n;//注意这个必须开成>Cn2的数组,即>n*(n-1)/2,要不然就runtime error了~~
int cmp(const int i,const int j) { return w[i]<w[j];}
int find(int x) { return p[x]==x ? x : p[x]=find(p[x]);}
int kruskal()
{
	int ans=0,i;
	for(i=0; i<n; i++) p[i]=i;
	for(i=0; i<n; i++) r[i]=i;
	sort(r,r+n,cmp);
	for(i=0;i<n;i++)
	{
		int e=r[i];int x=find(u[e]); int y=find(v[e]);
		if(x!=y) { ans+=w[e];p[x]=y; }
	}
	return ans;
}
int main()
{
	int k,l;
	while(cin>>n){
		k=0;
		for(int i=0; i<n;i++)
			for(int j=0; j<n;j++){
				cin>>l;
				if(j>i) {w[k]=l,u[k]=i,v[k]=j;k++;}
			}
		n=k;
		int ans=kruskal();
		cout << ans << endl;
	}
	return 0;
}

prim算法,与上个算法不同,从任意(因为最小生成树是一个遍历)节点开始不断的添加节点扩充A,初始将任意节点加入A中,通过数组key[i]储存节点i到目前生成的mst的最短距离,建立队列Q其中为未加入A的节点,则显然Q中key[i]最小的边(设为key[u])为划分(A,Q)的轻边(key[u]为连接Q中剩余节点与树A距离的最小距离,key[u]将树A与队列划分开来了),然后将u出队加入A。
伪代码:
Q=V;       // 初始队列Q=所有点的集合
key=+inf;       // 将key[i]初始赋值为正无穷大
A=s;key[s]=0;      // 取任意节点s加入点集A
while Q!=0 do begin       // 队列非空,还有未加入A的点
    u=min(Q);dequeue(u);        // 找出Q中key[i]最小的点u并在Q中除去它
    A=A+u;        // 将点u加入A中
    for each vertex v in adj[u]        // 在所有和u相邻的点v中循环
        if w(u,v)<key[v] then key[v]=w(u,v);       // key[i]储存节点i到目前生成的mst的最短距离
end.

 

vertex 节点 adj[u]表示和u相邻的顶点的集和

(以上部分纯属转载)

Prim结构二:

建立一个外点到A的最小距数组losscost[i],找出最小值即此时的外点k,将k加入A中,循环;

lesscost[i] = w[0][i];     //初始化,将0点加入A 

while(n-1)      // 遍搜外点

     for each vertex j

          min = lesscost[j];  k = j;    //找到外点距losscost[i]的最小距,及对应外点k

     for each vertex j

          if(lesscost[j] > w[k][j])
                    lesscost[j] = w[k][j];      //将剩余外点k与A中所有点做一个比较,保留小的边值,存入losscost[i]
end      

注意第二种结构,每次比较的只是lesscost[i]中值与第k行值,且取较小值更新losscost

即边d(a,b)两端a,b到各外点k距的最小距d(i,k)=min{d(a,k),d(b,k)},存入losscost[i] 

     

用prim算法的代码如下:(47ms AC)

#include <iostream> 
using namespace std;
int A[101],vis[101];  //将节点加入A中 
int w[101][101];  //储存路径(邻接矩阵) 
int main()
{
    int n, dis ;
    int min, min_v;
    int i,j,u,v;
    while(cin >>n) {
        dis = 0;  //初始化总距离为0  
        memset(A,0,sizeof(A));
        memset(vis,0,sizeof(vis));
        for(int i = 0; i < n; i++)
            for( int j = 0; j < n; j++)
                cin >> w[i][j];
        A[0] = 0;  //A集合
        vis[0] = 1; //标记数组
        for( i = 1; i < n; i++) {   //执行n-1次,所有结点才会加入A树
            min = 999999999;
            for(u = 0; u < i; u++)  //要遍搜A树中每个结点A[u]
                for(v = 0; v < n; v++)
                    if( !vis[v] && w[(A[u])][v] && w[(A[u])][v] < min) {  //找到A[u]到剩余点v的最小的非零距
                        min = w[(A[u])][v];
                        min_v = v;
                    }
            A[u] = min_v;  //储存结点路径(建立树),每次执行完循环后u=i,扩展的新结点A[u]为min_v
            vis[min_v] = 1;
            dis += min;
        }
        cout << dis << endl;
    }
	return 0;
}

用第二种结构的prim算法的代码如下:(47ms AC)

#include<iostream>
using namespace std;
#define INF 99999999
#define N 101
int w[N][N],losscost[N];
int main()
{
	int n,i,j,k;
	while(cin>>n){
		for(i=0;i<n;i++)
			for(j=0;j<n;j++){
				cin>>w[i][j];
				if(i==j) w[i][j]=-1;
			}
		int ans=0;
		for(i=0;i<n;i++)
			losscost[i]=w[0][i];
		for(i=1;i<n;i++){
			int min=INF;
			for(j=0;j<n;j++)
				if(losscost[j]>=0 && losscost[j]<min){
					min=losscost[j];
					k=j;
				}
			ans+=min;
			for(j=0;j<n;j++)
				if(losscost[j]>w[k][j])
					losscost[j]=w[k][j];
		}
		cout<<ans<<endl;
	}
	return 0;			
}

    原文作者:旅游交通路线问题
    原文地址: https://blog.csdn.net/fuzimango/article/details/7529825
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞