图的应用--最小生成树

  • 最小生成树

    在一个连通网的所有生成树中,各边的代价之和最小的那颗生成树称为该连通网的最小代价生成树,简称为最小生成树。

    最小生成树主要有两种算法,普利姆(Prim)算法 && 克鲁斯卡尔(Kruskal)算法

  • 普利姆(Prim)算法

    普里姆算法在找最小生成树时,将顶点分为两个集合,一类是在查找的过程中已经包含在树中的顶点集U,另一类V(总-U)顶点集。
    从U与V中找一条权值最小的边,同时将V中的顶点并入U中。
    可以看出,普利姆(Prim)算法逐步增加U中的顶点,可称为加点法。

    • 辅助数组

      	typedef struct{
      		vertexType adjvex;			//最小边在U中的顶点 
      		arcType lowcost;			//最小边的权值 
      	}ClosEdge;
      	
      	ClosEdge closedge[MVNum];
      	```
      

    该辅助数组记录着集合U中到集合V中的最小权值边。

    • 普利姆(Prim)算法

      需要注意的是: V中的点并入了U中时,需要更新U到V的权值。更新后辅助数组中可能存着U中不同的顶点到V的距离。然后选择最小的作为最小生成树的。

      有点抽象,举个例子说明,结合着代码看:
      在这里插入图片描述
      该图以这样的邻接矩阵存着:Z是一个足够大的值
      在这里插入图片描述
      一开始:以V0为起点。closedge[ ]数组如下所示:
      在这里插入图片描述
      0是在集合U中的标志,找到最小权值1,对应的V集合中的顶点是下标2位置确定的顶点V3。当然V3与V1连接。
      此时应该更新V3并入之后,closedge[ ]数组的变化。
      两段划线的相比,取其小的更新closedge[ ]数组:在这里插入图片描述
      得到更新后的closedge[ ]数组:
      在这里插入图片描述
      找到最小权值4,对应的V集合中的顶点是下标5位置确定的顶点V6。当然V6与V3连接。
      然后将V6并入U集合中,这样以此类推。。。

    int Prim(Graph g, vertexType u) {			//分成两堆。U集合与V集合(总-U) 
    	int weight=0;							//累计最小生成树的权值 
    	int i,j,k;
    	
    	k=FindPos(g,u);  						//以u为起点,确定u的位置 
    
        for (i = 0; i < g->vexnum; i++) {
            if(i!=k){
            	closedge[i]={u,g->Arc[k][i]}; 	//u顶点到,i位置确定的顶点,的权值,初始化为邻接表中对应的值 
    		}
        }   
       
    	closedge[k].lowcost=0;   				// 初始时U集合只有u点,在U集合里,就记为0 
    
    	for (j = 1; j < g->vexnum; j++) {
    		
    		k=Min(g,closedge);					//找到最小权值。当然也就知道顶点 
    		
    		vertexType u0 = closedge[k].adjvex; //最小边在U中的顶点 
    		vertexType v0 = g->vertex[k];		//最小边在V中的顶点 
    		printf("%c,%c\n",u0,v0);
    		
    		weight=weight+g->Arc[FindPos(g,u0)][k];				//最小生成树的权值 
    		
    		closedge[k].lowcost=0;								//将k确定的顶点放入集合U中 
    		
    		for (i = 0; i < g->vexnum; i++) {   				//由于k顶点加入U中,检查k到其他顶点的权值是否比k加入之前小  
    			if (g->Arc[k][i] < closedge[i].lowcost) {
    			    closedge[i] ={g->vertex[k],g->Arc[k][i]};	// 更新
    			}
    		}
    	}   
    	return weight;  
    }
    

    回到顶部

    • 具体代码:

    #include<iostream>
    #include<stdlib.h>
    using namespace std;
    
    #define MVNum 10				//最大顶点数 
    #define MAXINT 32768			//表示极大值
     
    typedef int arcType;
    typedef char vertexType;
    
    typedef struct MNode{
    	vertexType vertex[MVNum];	//存储顶点 
    	arcType Arc[MVNum][MVNum];	//邻接矩阵,存储边 
    	int vexnum,arcnum;
    }GraphNode,*Graph;
    
    int Init(Graph &G){
    	G=(GraphNode *)malloc(sizeof(GraphNode));
    	G->vexnum=0;
    	G->arcnum=0;
    	if(G) return 1;
    	else cout<<"初始化出错!"<<endl;
    	return 0;
    }
    
    int FindPos(Graph G,char a){//查找位置 
    	int pos=-1;
    	for(int i=0;i<G->vexnum;i++){
    		if(G->vertex[i]==a){
    			pos=i;
    		}
    	}
    	return pos;
    }
    
    void CreateUDN(Graph G)			//创建无向网
    {
    	int num=0,					//控制输入顶点数 
    		pos1,pos2,				//确认顶点位置 
    		i,j,
    		weight;					//权值 
    	char a,
    		 b,
    		 ch;					//顶点 
    		 
    	for(i = 0;i<MVNum; i++){ 	//初始化弧
            for(j = 0;j<MVNum; j++){
                G->Arc[i][j] = MAXINT;
            }
        }
    
        printf("请输入顶点(不超过10个,以#结束):\n");
        cin>>ch;
        while(ch!='#' && num <10){
            G->vertex[num] = ch;
            cin>>ch;
            num++;
        }
        G->vexnum = num;  //顶点个数
        cout<<"请输入对应的弧(ab与ba是一样的边)和权值,以###结束"<<endl;
        cin>>a>>b>>weight;
        while(a!='#' && b!='#'){
            cout<<a<<"-"<<b<<":"<<weight<<endl;
            pos1=FindPos(G,a);
            pos2=FindPos(G,b);
            printf("位置a:%d,位置b:%d\n",pos1,pos2);
            if(pos1!= -1 && pos2!= -1){  //忽略不存在的顶点
                G->Arc[pos1][pos2] = weight;
                G->Arc[pos2][pos1] = weight;
                G->arcnum++;
            }
            cin>>a>>b>>weight;
        }
    }
    
    void PrintGraph(Graph g) {
        cout << "图的顶点为:" << endl;
        for (int i = 0; i < g->vexnum; i++) {
            cout << g->vertex[i] << " ";
        }
        cout << endl;
        cout << "图的邻接矩阵为:" << endl;
        for (int i = 0; i < g->vexnum; i++) {
            for (int j = 0; j < g->vexnum; j++) {
                printf("%10d ",g->Arc[i][j]);
            }
            cout << endl;
        }
    }
    
    
    typedef struct{
    	vertexType adjvex;			//最小边在U中的顶点 
    	arcType lowcost;			//最小边的权值 
    }ClosEdge;
    
    ClosEdge closedge[MVNum];
    
    int Min(Graph g,ClosEdge a[]){
    	int j=0;
    	int min=MAXINT;
    	for(int i=0;i<g->vexnum;i++){
    		if(a[i].lowcost!=0 && a[i].lowcost<min){
    			min=a[i].lowcost;
    			j=i;
    		}
    	}
    	return j;
    }
    
    int Prim(Graph g, vertexType u) {			//分成两堆。U集合与V集合(总-U) 
    	int weight=0;							//累计最小生成树的权值 
    	int i,j,k;
    	
    	k=FindPos(g,u);  						//以u为起点,确定u的位置 
    
        for (i = 0; i < g->vexnum; i++) {
            if(i!=k){
            	closedge[i]={u,g->Arc[k][i]}; 	//u顶点到,i位置确定的顶点,的权值,初始化为邻接表中对应的值 
    		}
        }   
       
    	closedge[k].lowcost=0;   				// 初始时U集合只有u点,在U集合里,就记为0 
    
    	for (j = 1; j < g->vexnum; j++) {
    		
    		k=Min(g,closedge);					//找到最小权值。当然也就知道顶点 
    		
    		vertexType u0 = closedge[k].adjvex; //最小边在U中的顶点 
    		vertexType v0 = g->vertex[k];		//最小边在V中的顶点 
    		printf("%c,%c\n",u0,v0);
    		
    		weight=weight+g->Arc[FindPos(g,u0)][k];				//最小生成树的权值 
    		
    		closedge[k].lowcost=0;								//将k确定的顶点放入集合U中 
    		
    		for (i = 0; i < g->vexnum; i++) {   				//由于k顶点加入U中,检查k到其他顶点的权值是否比k加入之前小  
    			if (g->Arc[k][i] < closedge[i].lowcost) {
    			    closedge[i] ={g->vertex[k],g->Arc[k][i]};	// 更新
    			}
    		}
    	}   
    	return weight;  
    }
    
    int main(){
        Graph G = NULL;
        Init(G);
        CreateUDN(G);
    	PrintGraph(G);
    	cout<<"权值"<<Prim(G,'1')<<endl;
        return 0;
    }
    

    回到顶部

    • 运行结果:

在这里插入图片描述
在这里插入图片描述
回到顶部

  • 克鲁斯卡尔(Kruskal)算法

    克鲁斯卡尔(Kruskal)算法在找最小生成树时,先将边按权值大小排好序,在直接去拿最小的权值边,只需保证拿来的边不能产生回路即可。当拿了(n-1)条边且没有回路,就能构成最小生成树。
    可以看出,克鲁斯卡尔(Kruskal)算法逐步增加生成树的边,可称为加边法。

    而保证拿来的边不能产生回路,需要一个辅助数组,初始时,数组里存着每个顶点不同的标记,即n个连通分量。当拿来一条边,看看这条边依附的两个顶点的标记相不相等,如果等,则说明在一个连通分量里,即会产生回路。不相等,则能构成最小生成树的边。

    • 克鲁斯卡尔(Kruskal)算法

      当确定了一条边时,需要将v2顶点及其与v2标记一样的顶点的标记改为v1的标记,因为他们在一个连通分量里。
      还是上面那个网,当克鲁斯卡尔(Kruskal)算法选到这一步时,即将选择顶点3和6,不仅6要改为3的标记,4也要改为3的标记。所以需要用循环判断去改变标记。
      在这里插入图片描述
    int Kruskal(Graph G)    //最小生成树,克鲁斯卡尔算法
    {
    	int i,j;
    	int V1,V2; 
    	int vs1,vs2; 
    	int weight=0;     //累计最小生成树权值 
    	Sort(G,G->edges);
    	for (i = 0; i < G->vexnum; i++){
    		Vexset[i] = i;
    	}
    
    	for (i = 0; i < G->arcnum; i++)
    	{
    		V1 = FindPos(G, G->edges[i].initial);
    		V2 = FindPos(G, G->edges[i].end);
    		
    		vs1=Vexset[V1];
    		vs2=Vexset[V2];
    		
    		if (vs1 != vs2)			//若相等,说明有环
    		{
    			weight= weight+ G->edges[i].weight;  //累计最小生成树权值 
    			printf("(%c,%c):%d\n", G->edges[i].initial, G->edges[i].end, G->edges[i].weight);//打印边和权值
    			for(j=0;j<G->vexnum;j++){	//之所以要循环,就是因为有时(边变多时),要改变标记的点不止一个。 
    				if(Vexset[j]==vs2){
    					Vexset[j]=vs1;
    				}					
    			}
    		}
    	}
    	return weight; 
    }
    

    回到顶部

    • 具体代码:

    #include<iostream>
    #include<stdlib.h>
    using namespace std;
    
    #define MVNum 10			//最大顶点数 
     
    typedef int arcType;
    typedef char vertexType;
    
    typedef struct edge{
        vertexType initial;		//类似邻接矩阵横坐标 
        vertexType end;			//类似邻接矩阵纵坐标 
        arcType weight;			//权值
    }Edge;
    
    typedef struct MNode{
    	vertexType vertex[MVNum];	//存储顶点 
    	Edge edges[MVNum]; 			//类似邻接矩阵
    	int vexnum,arcnum;
    }GraphNode,*Graph;
    
    int Vexset[MVNum];
    
    int Init(Graph &G){
    	G=(GraphNode *)malloc(sizeof(GraphNode));
    	G->vexnum=0;
    	G->arcnum=0;
    	if(G) return 1;
    	else cout<<"初始化出错!"<<endl;
    	return 0;
    }
    
    int FindPos(Graph G,char a){//查找位置 
    	int pos=-1;
    	for(int i=0;i<G->vexnum;i++){
    		if(G->vertex[i]==a){
    			pos=i;
    		}
    	}
    	return pos;
    }
    
    void CreateUDN(Graph G)			//创建无向网
    {
    	int num=0,					//控制输入顶点数 
    		pos1,pos2,				//确认顶点位置 
    		weight;					//权值 
    	char  ch;					//顶点 
    	Edge e;						//边 
    
        printf("请输入顶点(不超过10个,以#结束):\n");
        cin>>ch;
        while(ch!='#' && num <10){
            G->vertex[num] = ch;
            cin>>ch;
            num++;
        }
        G->vexnum = num;  //顶点个数
        cout<<"请输入对应的弧(ab与ba是一样的边)和权值,以###结束"<<endl;
        cin>>e.initial>>e.end>>e.weight;
        int k=0;
        while(e.initial!='#' && e.initial!='#'){
            cout<<e.initial<<"-"<<e.end<<":"<<e.weight<<endl;
            G->arcnum++;
            G->edges[k] = e;
            cin>>e.initial>>e.end>>e.weight;
            k++;
        }
    }
    
    void PrintGraph(Graph g) {
        cout << "图的顶点为:" << endl;
        for (int i = 0; i < g->vexnum; i++) {
            cout << g->vertex[i] << " ";
        }
        cout << endl;
        cout << "图的边为:" << endl;
        for (int j = 0; j < g->arcnum; j++) {
            printf("起点%c,终点%c,权值%8d\n",g->edges[j].initial,g->edges[j].end,g->edges[j].weight);
        }
    }
    
    void Sort(Graph g,Edge a[]){
    	Edge temp;		
    	for(int i=1;i<g->arcnum;i++){
    		for(int j=0;j<g->arcnum-i;j++){
    			if(a[j].weight>a[j+1].weight){
    				temp=a[j];
    				a[j]=a[j+1];
    				a[j+1]=temp;
    			}
    		}
    	}
    }
    
    int Kruskal(Graph G)    //最小生成树,克鲁斯卡尔算法
    {
    	int i,j;
    	int V1,V2; 
    	int vs1,vs2; 
    	int weight=0;     //累计最小生成树权值 
    	Sort(G,G->edges);
    	for (i = 0; i < G->vexnum; i++){
    		Vexset[i] = i;
    	}
    
    	for (i = 0; i < G->arcnum; i++)
    	{
    		V1 = FindPos(G, G->edges[i].initial);
    		V2 = FindPos(G, G->edges[i].end);
    		
    		vs1=Vexset[V1];
    		vs2=Vexset[V2];
    		
    		if (vs1 != vs2)			//若相等,说明有环
    		{
    			weight= weight+ G->edges[i].weight;  //累计最小生成树权值 
    			printf("(%c,%c):%d\n", G->edges[i].initial, G->edges[i].end, G->edges[i].weight);//打印边和权值
    			for(j=0;j<G->vexnum;j++){	//之所以要循环,就是因为有时(边变多时),要改变标记的点不止一个。 
    				if(Vexset[j]==vs2){
    					Vexset[j]=vs1;
    				}					
    			}
    		}
    	}
    	return weight; 
    }
     
    int main()
    {
        Graph G = NULL;
        Init(G);
        CreateUDN(G);
    	PrintGraph(G);
    	cout<<"最小生成树权值"<<Kruskal(G);
        return 0;
    }
    

    回到顶部

  • 19
    点赞
  • 84
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值