• 【算法导论】第23章最小生成树


      因为之前的一章写了贪心算法,最小生成树也是贪心算法的一个应用,这里就先总结一下第23章,本来上周末就能搞定的,不过呆在寝室就不想做,所以就拖到了现在,话说武汉的夏天太热了,,,

    1、问题引入

      设计电子线路时,常常要把多个元件的引脚连接到一起,使其电位相同,要使n个引脚互相连通,可以使用n-1条连接线,每条连接线需要一定长度,所以总想找出使用连接线长度最短的连接方式,可以把这一连接线问题模型化为一个无相连通图(v,e),v是引脚集合,e是每对引脚之间可能互联的集合,对于图中的每一条边(u,v)属于e,都有一个权值w(u,v)表示连接u和v的代价(连线的长度),我们希望找到一个无回路的子集t属于e,它连接了所有的顶点,且其权值之和最小。因为t无回路且连接了所有的顶点,所以它必然是一棵树,称为生成树,就把确定树t的问题称为最小生成树问题,

      这里主要给出解决最小生成树问题的两种算法:kruskal算法和prim算法。

    2.1 kruskal算法

      kruskal算法找出森林中连接任意两棵树的所有边中,具有最小权值的边(u,v)作为安全边,并把它添加到正在生长的森林中,设c1,c2表示边(u,v)连接的两棵树,因为(u,v)必是连接c1和其他某棵树的一条轻边,(u,v)对c1来说是安全边,kruskal算法是一种贪心算法,在算法的每一步中,添加到森林中的边的权值都是尽可能小的。

      kruskal算法的实现采用了一种不相交集合数据结构(并查集),以维护几个互不相交的元素集合,其算法主要思路是:首先根据顶点数目建立|v|棵树,每棵树包含图中的一个顶点;对图中的边按照非递减顺序进行拍u;然后检查每条边,其端点u和v是否属于同一棵树,如果是,就放弃该边,否则说明两个顶点分属于不同的树,则对两个顶点所在的树进行合并。

      kruskal算法中用到了(一种不相交集合数据结构-并查集),对并查集的理解参见:(并查集--学习详解http://www.cnblogs.com/cherish_yimi/archive/2009/10/11/1580839.html

      kruskal算法的具体实现如下:

      要生成最小生成树的图如下所示:

      具体代码:

    View Code
     1 #include<stdio.h>
     2 #include<stdlib.h>
     3 #define n 9//图中点的个数
     4 #define max 100
     5 struct edge{
     6     int u;//起点
     7     int v;//终点
     8     int value;//边的权值
     9 }e[max];
    10 int p[max];//用于记录最小生成树的边
    11 int father[n];//father[x]表示x的父节点;
    12 int rank[n];//rank[x]表示x的秩,从x到其某一后代叶节点的最长路径上边的数目
    13 int cmp(const void *a,const void *b)
    14 {
    15     return ((*(edge*)a).value > (*(edge*)b).value ? 1:-1);
    16 }
    17 /*----------------------并查集的基本操作--------------------------*/
    18 void makeSet()//初始化集合
    19 {
    20     for(int i=0;i<n;i++)
    21     {
    22         father[i]=i;
    23         rank[i]=0;
    24     }
    25 }
    26 int findSet(int x)//寻找x所在集合,回溯时压缩路径
    27 {
    28     if(x!=father[x])
    29         father[x]=findSet(father[x]);
    30     return(father[x]);
    31 }
    32 void unionSet(int x,int y)//按秩合并x和y所在的集合
    33 {
    34     if(rank[x]>rank[y])
    35         father[y]=x;
    36     else if(rank[x]<rank[y])
    37         father[x]=y;
    38     else 
    39     {
    40         rank[y]++;
    41         father[x]=y;
    42     }
    43 }
    44 /*-----------------------------主函数-------------------------------*/
    45 void main()
    46 {
    47     int i,j,x,y,h=0,sum=0,k=0;
    48     //en表示各个点之间的连接情况,为0表示无边,其他值表示边的权值
    49     int en[n][n]={{0,4,0,0,0,0,0,8,0},
    50             {4,0,8,0,0,0,0,11,0},
    51             {0,8,0,7,0,4,0,0,2},
    52             {0,0,7,0,9,14,0,0,0},
    53             {0,0,0,9,0,10,0,0,0},
    54             {0,0,4,14,10,0,2,0,0},
    55             {0,0,0,0,0,2,0,1,6},
    56             {8,11,0,0,0,0,1,0,7},
    57             {0,0,2,0,0,0,6,7,0}};
    58     
    59     for(i=0;i<n;i++)//各个边的初始化
    60         for(j=i;j<n;j++)
    61         {
    62             if(en[i][j]!=0)
    63             {
    64                 e[k].u=i;
    65                 e[k].v=j;
    66                 e[k].value=en[i][j];
    67                 k++;
    68             }
    69         }
    70     makeSet();//初始化集合
    71     qsort(e,k,sizeof(struct edge),cmp);//按照边的权值非递减顺序对边进行排序
    72     for(i=0;i<k;i++)
    73     {
    74         x=findSet(e[i].u);//寻找x所在集合的标志点
    75         y=findSet(e[i].v);//寻找y所在集合的标志点
    76         if(x!=y)//x和y不属于同一个集合
    77         {
    78             unionSet(x,y);//进行x和y所在集合的合并操作
    79             sum+=e[i].value;//将该边的权值计入总代价
    80             p[h++]=i;
    81         }
    82         else{}
    83     }
    84     printf("最小生成树的代价为:%d\n",sum);
    85     printf("最小生成树的各边及权值依次为:\n");
    86     for(i=0;i<h;i++)
    87         printf("边%c - %c 权值:%d \n",e[p[i]].u+'a',e[p[i]].v+'a',e[p[i]].value);
    88 }

    2.1 prim算法

      同kruskal算法一样,prim算法也是通用最小生成树算法的特例,其执行非常类似于寻找图的最短路径的dijkstra算法,树从任意根顶点r开始形成,并逐渐生成,直至覆盖了v中的所有顶点。在每一步中,一条连接了树A与(V,A)中某孤立顶点的轻边被加入到树A中,因此当算法终止时刻,A中的边就形成了一棵最小生成树。具体过程参见:http://ebkk.blog.163.com/blog/static/194135085201159115248724/中所述的流程图

      (1)采用数据结构书本上的算法实现

    View Code
     1 /*-------------------------------------------------------------------
     2  *名称:prim算法的实现
     3  *作者:lp
     4  *时间:2012.7.2
     5  *环境:vc++6.0
     6  *实现描述:        
     7  *-------------------------------------------------------------------*/
     8 #include<stdio.h>
     9 #include<stdlib.h>
    10 #define n 9//图中点的个数
    11 #define max n*n//图中的边的个数
    12 #define inf 9999//标示无穷大
    13 struct edge{
    14     int u;//起点
    15     int v;//终点
    16     int value;//边的权值
    17 }e[max];
    18 int p[n];//p[i]用于记录依次加入最小生成树的顶点
    19 
    20 /*-----------------------------prim算法-----------------------------*/
    21 int prim(int en[][n],int v)
    22 {
    23     int i,j,k,kk=0,min=inf,count=0;//min标示无穷大,count用于记录最小生成树各边的权值
    24     int lowcost[n];//lowcost[i]用于记录顶点i到最小生成树t中的顶点的最短路径权值
    25     int intree[n];//intree[i]用于记录顶点i是否在最小生成树中,1:在;0:不在。
    26     for(i=0;i<n;i++)//初始化
    27     {
    28         if(en[i][v]==inf)//路径不可达
    29             lowcost[i]=inf;
    30         else //有可达路径
    31             lowcost[i]=en[i][v];
    32             intree[i]=0;
    33             p[i]=-1;
    34     }
    35     intree[v]=1;//标记顶点v为最小生成树中的顶点
    36     p[0]=v;
    37     for(i=0;i<n-1;i++)//选择其余的n-1个顶点
    38     {
    39         min=inf;
    40         for(j=0;j<n;j++)//选出到最小生成树t中顶点最短的顶点k
    41         {
    42             if(intree[j]==0 && lowcost[j]<min)
    43             {
    44                 min=lowcost[j];
    45                 k=j;
    46             }
    47         }
    48         count=count+min;//记录权值
    49         intree[k]=1;
    50         v=k;
    51         p[++kk]=k;//记录依次加入最小生成树的顶点
    52         for(j=0;j<n;j++)
    53         {
    54             if(intree[j]==0 && en[v][j]!=0 && en[v][j]<lowcost[j])
    55                 lowcost[j]=en[v][j];
    56         }
    57     }
    58     return(count);
    59 }
    60 /*-----------------------------主函数-------------------------------*/
    61 void main()
    62 {
    63     int i,sum=0;
    64     char vex;
    65     //en表示各个点之间的连接情况,为inf表示无边,其他值表示边的权值看 :)  
    66     int en[n][n]={{0,4,inf,inf,inf,inf,inf,8,inf},
    67             {4,0,8,inf,inf,inf,inf,11,inf},
    68             {inf,8,0,7,inf,4,inf,inf,2},
    69             {inf,inf,7,0,9,14,inf,inf,inf},
    70             {inf,inf,inf,9,0,10,inf,inf,inf},
    71             {inf,inf,4,14,10,0,2,inf,inf},
    72             {inf,inf,inf,inf,inf,2,0,1,6},
    73             {8,11,inf,inf,inf,inf,1,0,7},
    74             {inf,inf,2,inf,inf,inf,6,7,0}};
    75     printf("输入最小生成树的构造开始顶点(a,b,c,d,e,f,g,h,i):\n");
    76     scanf("%c",&vex);
    77     sum=prim(en,vex-'a');
    78     printf("最小生成树的代价为:%d\n",sum);
    79     printf("构造最小生成树依次加入树的顶点为:\n");
    80     for(i=0;i<n-1;i++)
    81         if(p[i]!=-1)
    82             printf("%c -> ",p[i]+'a');
    83         printf("%c\n",p[i]+'a');
    84 }

      (2)采用最小堆的算法实现

    参考:http://lqm1987.blogbus.com/logs/67465589.html

      

    3 参考资料

    (1)算法导论

    (2)并查集:http://www.cnblogs.com/cherish_yimi/archive/2009/10/11/1580839.html

    (3)qsort:http://blog.sina.com.cn/s/blog_78b9c0f50100si6b.html

    (3)prim算法:http://www.cnblogs.com/hankskfc/archive/2011/09/27/2193559.html

  • 相关阅读:
    MyEclipse 快捷键
    Oracle使用split和splitstr函数批量分隔字符串
    linux解压 tar命令
    Crontab的用法
    .net 连接ORACLE中文显示乱码解决方案
    python 列表
    关于ios 11 的问题
    初步了解会计学
    iOS关于直播的链接
    知识链接中.....
  • 原文地址:https://www.cnblogs.com/lpshou/p/2573006.html
Copyright © 2020-2023  润新知