zoukankan      html  css  js  c++  java
  • 最小生成树

    最小生成树

          在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边(即),而 w(u, v) 代表此边的权重,若存在 T 为 E 的无回路子集,使得的 w(T) 最小,且包含了全部顶点。则此T 为 G 的最小生成树
         最小生成树其实是最小权重生成树的简称。生成树和最小生成树有许多重要的应用。例如:要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。

    1.prime普里姆算法   

       

       思路

              1).输入:一个加权连通图(邻接矩阵存储),其中顶点集合为V(n个);

              2).初始化:Vnew= {V0},其中V0为集合V中的任一节点(起始点),定义辅助矩阵edge[n-1],d初始化为V0到剩余节点【0,1,.......,n-1】的长度;

              3)循环k=0:n-2共n-1次(确定n-1条边):

                    a.在辅助矩阵edge[k]---edge[n-1]选取权值最小的边<Vi,Vj>,其中Vi为集合Vnew中的元素,而Vj不在Vnew集合当中。

                    b.将edge[k]与edge[j]交换,即将Vj加入到了Vnew中d

                    c.更新到不在Vnew集合的顶点的最短路径。此时使用新加入的节点Vj到剩余节点的距离与已知的距离比大小去较小的即可。

            4).输出:使用edge[n-1]描叙最小生成树。


        注意:更新策略避免了每次都需要在Vnew和非Vnew集合中遍历寻找最短路径(O(n(n-k))),而是采用新加入的顶点到剩余顶点的路径,与已知的最短路径比较。这样单次循环更新时间花销减小为O(n-k)。

                   时间复杂度为O(n.^2),由于涉及到交换,空间复杂度为O(1)。

         整体思路:即确定一个集合,开始仅仅包含顶点0。在n-1次循环中,确定该集合到剩余节点的最短距离,并将最短距离的终点添加进该集合(并无实质添加,程序中用交换即解决)。更新该集合到剩余顶点的最短距离。


      代码

    1. //交换函数  
    2. void swap(edge& e1,edge& e2){  
    3.     edge temp=e1;  
    4.     e1=e2;  
    5.     e2=temp;  
    6. }  
    7.   
    8. //prime算法求最小生成树  
    9. void prime(int** adjMatrix,int n,edge* ct ){  
    10.   
    11.     int minWeight,minIndex;  
    12.     int currentFrom,currentEnd;  
    13.     bool flag;  
    14.   
    15.   
    16.     for(int i=0;i<n-1;i++){  
    17.         ct[i].from=0;  
    18.         ct[i].end=i+1;  
    19.         ct[i].weight=adjMatrix[0][i+1];  
    20.     }  
    21.   
    22.     for(int i=1;i<n;i++){//依次加入n-1个点(n-1条路径),因此有n-1个循环  
    23.   
    24.         //ct中[i-1,n-2]区间段中寻找最小长度的路径,并将其与i-1的路径交换  
    25.         minWeight=ct[i-1].weight;  
    26.         minIndex=i-1;  
    27.         flag=false;  
    28.         for(int j=i;j<n-1;j++)  
    29.            if(ct[j].weight<minWeight){  
    30.                 minWeight=ct[j].weight;  
    31.                 minIndex=j;  
    32.                 flag=true;  
    33.            }  
    34.         if(flag)  
    35.            swap(ct[i-1],ct[minIndex]);  
    36.   
    37.         //比较新的顶点到剩余节点的路径与当前到该节点的路径,更新到剩余节点的最短路径  
    38.         currentFrom=ct[i-1].end;  
    39.         for(int j=i;j<n-1;j++){  
    40.            currentEnd=ct[j].end;  
    41.            if(adjMatrix[currentFrom][currentEnd]<ct[j].weight){  
    42.                ct[j].from=currentFrom;  
    43.                ct[j].weight=adjMatrix[currentFrom][currentEnd];  
    44.            }  
    45.          }  
    46.     }  
    47. }  




    2.Kruskal克鲁斯卡尔算法

      思路

          定义集合标号数组vexSet。

         (1)基于图的边集数组存储来完成,实现需要依照权重从小大道排列边集数组。

         (2)初始化每一个顶点为单独集合。

         (3)遍历边集数组:

             a.如果路径两端节点不在一个集合内,储存该路径并将遍历终节点所属的集合全部合并到起始节点所在集合。

             b.如果路径两端节点在一个集合内,跳过,继续遍历。


       注意:使用标号来标示集合,不在同一个集合保证了无回路存在.

                  由于需要辅助数组标记顶点所属集合,故算法的空间复杂度为O(n)。而内嵌的循环中,合并集合需要执行n次,故时间复杂度最坏为为O(ne),最好为O(n.^2)。但是注意到,边集数组减小了图的空间复杂度为O(e),其中e为边数。

                整体思路:集合合并问题。初始化每个顶点单独成为一个集合。每一轮循环中,确定顶点终点不在同一集合的最短边,保存该最短边,将顶点和终点所属集合合并(此时同一用边的顶点序号来标定同一集合)。

     

         代码:

    1. //根据邻接矩阵获得升序排列的边集数组  
    2. void getSet(edge* &edgeSet,int &size,int** adjMatrix,int n){  
    3.     for(int i=1;i<n;i++){  
    4.         for(int j=0;j<i;j++)  
    5.             if(adjMatrix[i][j]!=MAX){  
    6.                 size++;  
    7.                 if(!edgeSet){  
    8.                     edgeSet=(edge*)malloc(sizeof(edge));  
    9.                     edgeSet[0].from=j;  
    10.                     edgeSet[0].end=i;  
    11.                     edgeSet[0].weight=adjMatrix[i][j];  
    12.                 }  
    13.                 else{  
    14.                     edgeSet=(edge*)realloc(edgeSet,size*sizeof(edge));  
    15.                     int k=size-2;  
    16.                     while(k>=-1&&edgeSet[k].weight>adjMatrix[i][j]){  
    17.                         edgeSet[k+1]=edgeSet[k];  
    18.                         k--;  
    19.                     }  
    20.                     edgeSet[k+1].from=j;  
    21.                     edgeSet[k+1].end=i;  
    22.                     edgeSet[k+1].weight=adjMatrix[i][j];  
    23.                 }  
    24.             }  
    25.     }  
    26. }  
    
    

    1. //克鲁斯卡尔算法求最小生成树  
    2. void Kruskal(int** adjMatrix,int n,edge* ct){  
    3.         edge* edgeSet=NULL;  
    4.         int size=0;  
    5.         getSet(edgeSet,size,adjMatrix,n);  
    6.   
    7.         int i,k;  
    8.         int currentEnd;  
    9.         int* vexSet=new int[n];//集合标记辅助数组 
    10.         for(i=0;i<n;i++)  
    11.             vexSet[i]=i;  
    12.   
    13.         for(i=0,k=0;i<size&&k<n-1;i++){//至多有n-1条边  
    14.             if(vexSet[edgeSet[i].from]!=vexSet[edgeSet[i].end]){//如果起点终点处于不同的集合  
    15.                 ct[k++]=edgeSet[i];  
    16.                 currentEnd=vexSet[edgeSet[i].end];  
    17.                 for(int j=0;j<n;j++)//遍历终节点所属的集合全部合并到起始节点所在集合  
    18.                     if(vexSet[j]==currentEnd)  
    19.                          vexSet[j]=vexSet[edgeSet[i].from];  
    20.             }  
    21.         }  
    22.   
    23.         free(edgeSet);  
    24.         delete []vexSet;  
    25. }  


    3.主程序

        使用数组矩阵模拟邻接矩阵,假设最大权重为100。

    1. #include<iostream>  
    2. using namespace std;  
    3. #define  MAX 100  
    4.   
    5. #include"algo.h"  
    6. void main(){  
    7.   
    8.     //构建邻接矩阵  
    9.     int** a=new int*[7];  
    10.     for(int i=0;i<7;i++){  
    11.         a[i]=new int[7];  
    12.         for(int j=0;j<7;j++)  
    13.             a[i][j]=100;  
    14.     }  
    15.     a[3][0]=a[0][3]=5;  
    16.     a[1][0]=a[0][1]=8;  
    17.     a[2][1]=a[1][2]=12;  
    18.     a[3][1]=a[1][3]=3;  
    19.     a[4][1]=a[1][4]=10;  
    20.     a[4][2]=a[2][4]=6;  
    21.     a[5][2]=a[2][5]=2;  
    22.     a[5][3]=a[3][5]=7;  
    23.     a[6][3]=a[3][6]=15;  
    24.     a[5][4]=a[4][5]=9;  
    25.   
    26.     //最小生成树  
    27.     edge* ct=new edge[7];  
    28.       
    29.     //prime(a,7,ct);  
    30.     Kruskal(a,7,ct);  
    31.       
    32.   
    33.     for(int i=0;i<6;i++){  
    34.         cout<<ct[i].from<<"--"<<ct[i].end<<": "<<ct[i].weight<<endl;  
    35.     }  
    36.   
    37.     for(int i=0;i<7;i++){  
    38.         delete[]a[i];  
    39.     }  
    40.     delete []a;  
    41.     delete []ct;  
    42. }  

  • 相关阅读:
    DP——背包问题(三)
    堆——练习题
    DP——背包问题(二)
    二叉树的后序遍历(暴力版) 小白菜oj 1034
    树状数组2模板 Luogu 3368
    树状数组1模板 Luogu 3374
    DP——最长上升子序列(n^2与n log n)
    线段树(区间修改)模板题 Luogu 2357 守墓人
    c语言学习摘录
    python 学习摘录
  • 原文地址:https://www.cnblogs.com/engineerLF/p/5393034.html
Copyright © 2011-2022 走看看