zoukankan      html  css  js  c++  java
  • 最小生成树的Kruskal算法实现

    最近在复习数据结构,所以想起了之前做的一个最小生成树算法。用Kruskal算法实现的,结合堆排序可以复习回顾数据结构。现在写出来与大家分享。

      最小生成树算法思想:书上说的是在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边(即),而 w(u, v) 代表此边的权重,若存在 T 为 E 的子集(即)且为无循环图,使得的 w(T) 最小,则此 T 为 G 的最小生成树。说白了其实就是在含有 n 个顶点的连通网中选择 n-1 条边,构成一棵极小连通子图,并使该连通子图中 n-1 条边上权值之和达到最小,则称最小生成树

      本程序用的是克鲁斯卡尔算法(Kruskal),也可以使用prim算法实现。Kruskal思想是在带权连通图中,不断地在排列好的边集合中找到最小的边,如果该边满足得到最小生成树的条件,就将其构造,直到最后得到一颗最小生成树。

     图的顶点存储结构体:

    1 //结构体定义,储存图的顶点
    2 typedef struct { 
    3     int from; //边的起始顶点
    4     int to;   //边的终止顶点
    5     int cost; //边的权值
    6 }Edge;

     问题:顶点编号的类型。

      好的程序应该可以扩展,不论顶点用0,1,2...  顺序编号还是用5,2,1,7... 乱序编号还是用a,b,c...  英文编号都应该可以做到兼容通过,所以在存储图的节点的时候我做了一个映射,就是不论输入的什么编号一律转换成顺序编号0,1,2...,在最后输出的时候再把编号映射回原来的编号,这样就可以应对不同而顶点编号。如下面程序:

     1 for(i = 0;i < edgeNum; i++){
     2     printf("请输入第 %d 条边!
    ",i+1);
     3     scanf(" %c %c %d",&from,&to,&cost); 
     4     edge_temp[i][0] = judge_num(from); 
     5     edge_temp[i][1] = judge_num(to);
     6     edge_temp[i][2] = cost; 
     7 }
     8 //对输入的边和点信息进行堆排序
     9 HeapSort(edge_temp,edgeNum);
    10 int j;
    11 for(j = 0;j < edgeNum; j++){
    12     edge[j].from = edge_temp[j][0]; 
    13     edge[j].to = edge_temp[j][1]; 
    14     edge[j].cost = edge_temp[j][2];   
    15 }

    每次输入顶点后都会先保存到临时存储数组edge_temp中,进行堆排序后再把数据白存在真正的数据中。其中判断是否形成回路借助了一个递归方法:

    1 //用于判断是否形成回路
    2 int judge(int num){  
    3     if(num == judge_temp[num]){
    4         return judge_temp[num]; 
    5     } 
    6     return judge_temp[num] = judge(judge_temp[num]);  
    7 }

    执行步骤:

    1:在带权连通图中,将边的权值排序(本程序用的是堆排序);

    2:判断是否需要选择这条边(此时的边已按权值从小到大排好序)。判断的依据是边的两个顶点是否已连通,如果连通则继续下一条;如果不连通,那么就选择使其连通。

    3:循环第二步,直到图中所有的顶点都在同一个连通分量中,即得到最小生成树。

    判断法则:(当将边加入到已找到边的集合中时,是否会形成回路?)

      1:如果没有形成回路,那么直接将其连通。此时,对于边的集合又要做一次判断:这两个点是否在已找到点的集合中出现过?如果两个点都没有出现过,那么将这 两个点都加入已找到点的集合中;如果其中一个点在集合中出现过,那么将另一个没有出现过的点加入到集合中;如果这两个点都出现过,则不用加入到集合中。

       2:如果形成回路,不符合要求,直接进行下一次操作。

    重点类容就这么多,下面给出源程序,程序直接复制后可以运行,有兴趣的朋友也可以用prim算法实现。

      1 #include <stdio.h> 
      2 #include <string.h> 
      3 //常量定义,边点最大数量限制50;
      4 #define MAXE 52
      5 
      6 /*
      7  * Info:最小生成树算法源码(C语言版)
      8  * @author: zhaoyafei 
      9  * time: 2015
     10  */
     11 
     12 //结构体定义,储存图的顶点
     13 typedef struct { 
     14     int from; //边的起始顶点
     15     int to;   //边的终止顶点
     16     int cost; //边的权值
     17 }Edge;
     18 
     19 int nodeNum;  //顶点数;
     20 int edgeNum;  //边数;
     21 int min_cost; //记录最小生成树(权值)
     22 int judge_temp[MAXE]; //记录判断是否成环
     23 int sort[MAXE][MAXE]; //用来做排序
     24 int edge_temp[MAXE][3]; //用于存储堆排序边点信息
     25 
     26 Edge edge[MAXE];        //用于存储边点信息
     27 Edge min_edge[MAXE];    //用于存储最小生成树边点信息
     28 
     29 char judge_num_int[MAXE]; 
     30 int inputs = 1;
     31 void HeapSort(int array[MAXE][3],int length);
     32 int judge_num(char from);
     33 
     34 //save_point()函数,存储图的点边信息; 
     35 void save_point(){  
     36     char from;
     37     char to;
     38     int cost = 0; 
     39     int i;  
     40     for(i = 0;i < edgeNum; i++){
     41         printf("请输入第 %d 条边!
    ",i+1);
     42         scanf(" %c %c %d",&from,&to,&cost); 
     43 
     44         edge_temp[i][0] = judge_num(from); 
     45         edge_temp[i][1] = judge_num(to);
     46         edge_temp[i][2] = cost; 
     47     }
     48     //对输入的边和点信息进行堆排序
     49     HeapSort(edge_temp,edgeNum);
     50     int j;
     51     for(j = 0;j < edgeNum; j++){
     52         edge[j].from = edge_temp[j][0]; 
     53         edge[j].to = edge_temp[j][1]; 
     54         edge[j].cost = edge_temp[j][2];   
     55     }
     56 } 
     57 
     58 int judge_num(char str){
     59     int n1 = 0;
     60     for(int j1 = 1;j1 < edgeNum * 2; j1++){
     61         if(str == judge_num_int[j1]){
     62             n1++;
     63         }
     64     }
     65     if(n1 == 0){
     66         judge_num_int[inputs] = str;
     67         inputs++;
     68     }
     69     int return_num = 1;
     70     for(int j2 = 1;j2 < edgeNum * 2; j2++){
     71         if(str == judge_num_int[j2]){
     72             return_num = j2;
     73         }
     74     }
     75     return return_num;
     76 }
     77 
     78 //用于判断是否形成回路
     79 int judge(int num){  
     80     if(num == judge_temp[num]){
     81         return judge_temp[num]; 
     82     } 
     83     return judge_temp[num] = judge(judge_temp[num]);  
     84 } 
     85 
     86 //判断是否是一棵最小生成树
     87 bool is_judge(){   
     88     int oneedge = judge(1);
     89     int i;    
     90     for(i = 2;i <= nodeNum; i++)  {  
     91         if(oneedge != judge(i)){  
     92             return false;  
     93         }  
     94     }      
     95     return true;  
     96 }
     97 
     98 //kruskal算法   
     99 void kruskal(){  
    100     min_cost = 0;//最小生成树 
    101     //初始化辅助回路判断数组  
    102     int m;   
    103     for(m = 0;m < MAXE;m++)  {  
    104         judge_temp[m] = m;  
    105     }  
    106       
    107     int edge_num = 0;//记录最小生成树的边数   
    108     int i;
    109     for(i = 0;i < edgeNum; i++){
    110         //小于总节点数
    111         if(edge_num != nodeNum - 1){
    112             int edge_from = judge(edge[i].from);  
    113             int edge_to = judge(edge[i].to);
    114             //如果形成回路则edge_from == edge_to;
    115             if(edge_from != edge_to){
    116                 //如果没有形成回路,则改变原临时数组中的值  
    117                 judge_temp[edge_from] = edge_to;  
    118                 min_cost += edge[i].cost;  
    119 
    120                 //将符合的边加入到存储数组中
    121                 min_edge[edge_num].from = edge[i].from;  
    122                 min_edge[edge_num].to = edge[i].to;  
    123                 min_edge[edge_num].cost = edge[i].cost; 
    124                 
    125                 edge_num++;  
    126             }   
    127         }   
    128     }  
    129 }
    130 
    131 //array是待调整的堆数组,i是待调整的数组元素的位置,nlength是数组的长度
    132 //根据数组array构建大顶堆
    133 void HeapAdjust(int array[MAXE][3],int i,int nLength){
    134    int nChild;
    135    for(; 2*i + 1 < nLength; i = nChild){  //子结点的位置=2*(父结点位置)+1
    136         nChild = 2*i + 1;
    137         //得到子结点中较大的结点
    138         if(nChild < nLength-1 && array[nChild+1][2] > array[nChild][2]){
    139             ++nChild;
    140         }
    141         //如果较大的子结点大于父结点那么把较大的子结点往上移动,替换它的父结点
    142         if(array[i][2] < array[nChild][2]){
    143             int temp_arr2[3];
    144             temp_arr2[0] = array[i][0];
    145             temp_arr2[1] = array[i][1];
    146             temp_arr2[2] = array[i][2];
    147 
    148             array[i][0] = array[nChild][0];
    149             array[i][1] = array[nChild][1];
    150             array[i][2] = array[nChild][2];
    151 
    152             array[nChild][0] = temp_arr2[0];
    153             array[nChild][1] = temp_arr2[1];
    154             array[nChild][2] = temp_arr2[2];
    155         }else{
    156             break;//否则退出循环
    157         }
    158     }
    159 }
    160  
    161 //堆排序算法
    162 void HeapSort(int array[MAXE][3],int length){
    163     //调整序列的前半部分元素,调整完之后第一个元素是序列的最大的元素
    164     //length/2-1是最后一个非叶节点,此处"/"为整除
    165     int j;
    166     for( j= length/2 - 1; j >= 0; --j){
    167         HeapAdjust(array,j,length);
    168     }
    169     //从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素
    170     int i;
    171     for(i = length - 1; i > 0; --i){
    172         //把第一个元素和当前的最后一个元素交换,
    173         //保证当前的最后一个位置的元素都是在现在的这个序列之中最大的
    174         int temp_arr1[3]; //构建二维数组的原因:交换后保证数组中其他属性值同时交换
    175         temp_arr1[0] = array[i][0];
    176         temp_arr1[1] = array[i][1];
    177         temp_arr1[2] = array[i][2];
    178 
    179         array[i][0] = array[0][0];
    180         array[i][1] = array[0][1];
    181         array[i][2] = array[0][2];
    182 
    183         array[0][0] = temp_arr1[0];
    184         array[0][1] = temp_arr1[1];
    185         array[0][2] = temp_arr1[2];
    186 
    187         //不断缩小调整heap的范围,每一次调整完毕保证第一个元素是当前序列的最大值
    188         HeapAdjust(array,0,i);
    189     }
    190 }
    191 
    192 //输出最小生成树的信息(包括边点和权值) 
    193 void output(){   
    194     if(is_judge()){  
    195         printf("最小生成树:
    "); 
    196         printf("起点 -> 终点   路径长:
    ");   
    197         for(int i = 0;i < nodeNum-1; i++){  
    198             printf(" %c   ->  %c       %d
    ",judge_num_int[min_edge[i].from],judge_num_int[min_edge[i].to],min_edge[i].cost);  
    199         }  
    200         printf("min cost is : %d
    ",min_cost); 
    201         printf("*******************************************************************************
    "); 
    202         printf("请输入 节点数 边数(中间需用空格隔开):
    ");
    203     }else{
    204         printf("最小生成树不存在!
    ");
    205         printf("*******************************************************************************
    "); 
    206         printf("请输入 节点数 边数(中间需用空格隔开):
    ");  
    207     } 
    208 }
    209 
    210 /*
    211  * 程序主方法;
    212  * 用于开始程序,介绍程序操作须知;
    213  */
    214 int main(){ 
    215     printf("*******************************************************************************
    "); 
    216     printf("**                         最小生成树(kruskal算法)                        ***
    ");
    217     printf("**  说明:开始程序输入图的总点数和总边数,测试程序目前边点限制最多输入50个  ***
    ");
    218     printf("**        中间用空格隔开。输入边和点信息,需要按格式:开始边 终止边  权值   ***
    ");
    219     printf("**        本次计算结束可以按要求开始下次计算。                              ***
    ");
    220     printf("*******************************************************************************
    ");
    221     printf("请输入 节点数 边数(中间需用空格隔开):
    ");
    222     while(scanf("%d%d",&nodeNum,&edgeNum) != EOF){ 
    223         //判断输入的边和点的合法性
    224         if(nodeNum < 1 || edgeNum < 1){
    225             printf("输入的数据不合法
    ");
    226             printf("请输入 节点数 边数(中间需用空格隔开):
    ");
    227             return 0;
    228         }else if(nodeNum > 50 || edgeNum > 50){
    229             printf("输入的边或者点不能大于50
    ");
    230             printf("请输入 节点数 边数(中间需用空格隔开):
    ");
    231             return 0;
    232         }else{
    233             printf("请输入 开始节点 终止节点 该边的权值(中间需用空格隔开,回车换行):
    ");
    234             printf("共 %d 条边
    ",edgeNum);
    235             for(int m = 0;m < MAXE; m++)  {  
    236                 judge_num_int[m] = '-';  
    237             }  
    238             inputs = 1;
    239             save_point(); //存储边点信息          
    240             kruskal();    //算法执行 
    241             output();     //输出执行结果
    242         }  
    243     } 
    244     return 0;  
    245 }

    运行结果如下:

  • 相关阅读:
    8.4 IP地址的划分及子网划分
    8.3 TCPIP协议族
    微软官方宣布:Edge 浏览器将采用 Chromium 内核
    微软官方宣布:Edge 浏览器将采用 Chromium 内核
    微软官方宣布:Edge 浏览器将采用 Chromium 内核
    Web 安全开发规范手册 V1.0
    Web 安全开发规范手册 V1.0
    Web 安全开发规范手册 V1.0
    NET Core入门笔记
    NET Core入门笔记
  • 原文地址:https://www.cnblogs.com/zyf-zhaoyafei/p/4604006.html
Copyright © 2011-2022 走看看