• 最小生成树[摘录自严长生老师的网站]


    最小生成树的对象是无向连通网,这个前提是必要的,无向,邻接矩阵是对称的,连通,点与点之间两两可达,网,所有边都有权重,对应于城市修路问题,就是路的长度,最小生成树,对应同时满足城市之间连通和修路花费最小,是一个实际应用性很强的算法。

    最小生成树的实现主要有两种算法,Prim算法和Kruskal算法,下面分别介绍这两种算法,及其C语言实现。

    1. Prim

    首先定义无向网,采用邻接矩阵的存储方式,个人认为采用邻接表也可以,而且可以节省不少空间

    #include <stdio.h>
    #include <stdlib.h>
    #define VertexType int
    #define VRType int
    #define MAX_VERtEX_NUM 20
    #define InfoType char   
    #define INFINITY 65535
    typedef struct {
        VRType adj;                             //对于无权图,用 1 或 0 表示是否相邻;对于带权图,直接为权值。
        InfoType * info;                        //弧额外含有的信息指针
    }ArcCell,AdjMatrix[MAX_VERtEX_NUM][MAX_VERtEX_NUM];
    typedef struct {
        VertexType vexs[MAX_VERtEX_NUM];        //存储图中顶点数据
        AdjMatrix arcs;                         //二维数组,记录顶点之间的关系
        int vexnum,arcnum;                      //记录图的顶点数和弧(边)数
    }MGraph;
    //根据顶点本身数据,判断出顶点在二维数组中的位置
    int LocateVex(MGraph G,VertexType v){
        int i=0;
        //遍历一维数组,找到变量v
        for (; i<G.vexnum; i++) {
            if (G.vexs[i]==v) {
                return i;
            }
        }
        return -1;
    }
    

     然后创建无向网,相互之间不连接的顶点,邻接矩阵权值设为最大整数,这里代表着无穷大。

    //构造无向网
    void CreateUDN(MGraph* G){
        scanf("%d,%d",&(G->vexnum),&(G->arcnum));
        for (int i=0; i<G->vexnum; i++) {
            scanf("%d",&(G->vexs[i]));
        }
        for (int i=0; i<G->vexnum; i++) {
            for (int j=0; j<G->vexnum; j++) {
                G->arcs[i][j].adj=INFINITY;
                G->arcs[i][j].info=NULL;
            }
        }
        for (int i=0; i<G->arcnum; i++) {
            int v1,v2,w;
            scanf("%d,%d,%d",&v1,&v2,&w);
            int m=LocateVex(*G, v1);
            int n=LocateVex(*G, v2);
            if (m==-1 ||n==-1) {
                printf("no this vertex
    ");
                return;
            }
            G->arcs[n][m].adj=w;
            G->arcs[m][n].adj=w;
        }
    }
    

     然后要建立辅助数组,这里的辅助数组是针对每个顶点,如果已经在最小生成树中,权值设为0,否则设为距当前最小生成树的最小距离

    //辅助数组,用于每次筛选出权值最小的边的邻接点
    typedef struct {
        VertexType adjvex;//记录权值最小的边的起始点
        VRType lowcost;//记录该边的权值
    }closedge[MAX_VERtEX_NUM];
    closedge theclose;//创建一个全局数组,因为每个函数中都会使用到
    //在辅助数组中找出权值最小的边的数组下标,就可以间接找到此边的终点顶点。
    int minimun(MGraph G,closedge close){
        int min=INFINITY;
        int min_i=-1;
        for (int i=0; i<G.vexnum; i++) {
            //权值为0,说明顶点已经归入最小生成树中;然后每次和min变量进行比较,最后找出最小的。
            if (close[i].lowcost>0 && close[i].lowcost < min) {
                min=close[i].lowcost;
                min_i=i;
            }
        }
        //返回最小权值所在的数组下标
        return min_i;
    }
    

     最后是主体函数,根据给定的初始顶点,初始化辅助数组,然后不断迭代n-1次

    //普里姆算法函数,G为无向网,u为在网中选择的任意顶点作为起始点
    void miniSpanTreePrim(MGraph G,VertexType u){
        //找到该起始点在顶点数组中的位置下标
        int k=LocateVex(G, u);
        //首先将与该起始点相关的所有边的信息:边的起始点和权值,存入辅助数组中相应的位置,例如(1,2)边,adjvex为0,lowcost为6,存入theclose[1]中,辅助数组的下标表示该边的顶点2
        for (int i=0; i<G.vexnum; i++) {
            if (i !=k) {
                theclose[i].adjvex=k;
                theclose[i].lowcost=G.arcs[k][i].adj;
            }
        }
        //由于起始点已经归为最小生成树,所以辅助数组对应位置的权值为0,这样,遍历时就不会被选中
        theclose[k].lowcost=0;
        //选择下一个点,并更新辅助数组中的信息
        for (int i=1; i<G.vexnum; i++) {
            //找出权值最小的边所在数组下标
            k=minimun(G, theclose);
            //输出选择的路径
            printf("v%d v%d
    ",G.vexs[theclose[k].adjvex],G.vexs[k]);
            //归入最小生成树的顶点的辅助数组中的权值设为0
            theclose[k].lowcost=0;
            //更新辅助数组中存储的信息,由于此时树中新加入了一个顶点,需要判断,由此顶点出发,到达其它各顶点的权值是否比之前记录的权值还要小,如果还小,则更新
            for (int j=0; j<G.vexnum; j++) {
                if (G.arcs[k][j].adj<theclose[j].lowcost) {
                    theclose[j].adjvex=k;
                    theclose[j].lowcost=G.arcs[k][j].adj;
                }
            }
        }
        printf("
    ");
    }
    int main(){
        MGraph G;
        CreateUDN(&G);
        miniSpanTreePrim(G, 1);
    }
    

    2.Kruskal

    相比于Prim算法,Kruskal算法更适合边比较少的无向网,因为Kruskal是关于边的算法,算法的初始状态是一张没有边但顶点与无向网相同的图,然后逐步向图中添加边,添加满n-1条边,算法告成,我觉得比Prim算法更简单直观些。

    所以克鲁斯卡尔算法的具体思路是:将所有边按照权值的大小进行升序排序,然后从小到大一一判断,条件为:如果这个边不会与之前选择的所有边组成回路,就可以作为最小生成树的一部分;反之,舍去。直到具有 n 个顶点的连通网筛选出来 n-1 条边为止。筛选出来的边和所有的顶点构成此连通网的最小生成树。

    如何判断是否存在回路呢,其实比较简单,初始时对所有顶点做标记,每个顶点标记都不同,然后选择一条最短的边,并将边的两端顶点置为相同的标记,标记相同,代表着连通,所以如果你选择的边,两端顶点已经连通的话,那末放弃这条边,继续迭代。

    以下面的图为例。

    对顶点进行标记,这里用颜色标记

    接着对边排序,然后从最短的边开始迭代

     当边的数目为n-1时,算法结束。

    下面是代码实现。

        #include "stdio.h"
        #include "stdlib.h"
        #define MAX_VERtEX_NUM 20
        #define VertexType int
        typedef struct edge{
            VertexType initial;
            VertexType end;
            VertexType weight;
        }edge[MAX_VERtEX_NUM];
        //定义辅助数组
        typedef struct {
            VertexType value;//顶点数据
            int sign;//每个顶点所属的集合
        }assist[MAX_VERtEX_NUM];
        assist assists;
        //qsort排序函数中使用,使edges结构体中的边按照权值大小升序排序
        int cmp(const void *a,const void*b){
            return  ((struct edge*)a)->weight-((struct edge*)b)->weight;
        }
        //初始化连通网
        void CreateUDN(edge *edges,int *vexnum,int *arcnum){
            printf("输入连通网的边数:
    ");
            scanf("%d %d",&(*vexnum),&(*arcnum));
            printf("输入连通网的顶点:
    ");
            for (int i=0; i<(*vexnum); i++) {
                scanf("%d",&(assists[i].value));
                assists[i].sign=i;
            }
            printf("输入各边的起始点和终点及权重:
    ");
            for (int i=0 ; i<(*arcnum); i++) {
                scanf("%d,%d,%d",&(*edges)[i].initial,&(*edges)[i].end,&(*edges)[i].weight);
            }
        }
        //在assists数组中找到顶点point对应的位置下标
        int Locatevex(int vexnum,int point){
            for (int i=0; i<vexnum; i++) {
                if (assists[i].value==point) {
                    return i;
                }
            }
            return -1;
        }
        int main(){
           
            int arcnum,vexnum;
            edge edges;
            CreateUDN(&edges,&vexnum,&arcnum);
            //对连通网中的所有边进行升序排序,结果仍保存在edges数组中
            qsort(edges, arcnum, sizeof(edges[0]), cmp);
            //创建一个空的结构体数组,用于存放最小生成树
            edge minTree;
            //设置一个用于记录最小生成树中边的数量的常量
            int num=0;
            //遍历所有的边
            for (int i=0; i<arcnum; i++) {
                //找到边的起始顶点和结束顶点在数组assists中的位置
                int initial=Locatevex(vexnum, edges[i].initial);
                int end=Locatevex(vexnum, edges[i].end);
                //如果顶点位置存在且顶点的标记不同,说明不在一个集合中,不会产生回路
                if (initial!=-1&& end!=-1&&assists[initial].sign!=assists[end].sign) {
                    //记录该边,作为最小生成树的组成部分
                    minTree[num]=edges[i];
                    //计数+1
                    num++;
                    //将新加入生成树的顶点标记全不更改为一样的
                    for (int k=0; k<vexnum; k++) {
                        if (assists[k].sign==assists[end].sign) {
                            assists[k].sign=assists[initial].sign;
                        }
                    }
                    //如果选择的边的数量和顶点数相差1,证明最小生成树已经形成,退出循环
                    if (num==vexnum-1) {
                        break;
                    }
                }
            }
            //输出语句
            for (int i=0; i<vexnum-1; i++) {
                printf("%d,%d
    ",minTree[i].initial,minTree[i].end);
            }
            return 0;
        }
    

    输出

    输入连通网的边数:
    6 10
    输入连通网的顶点:
    1
    2
    3
    4
    5
    6
    输入各边的起始点和终点及权重:
    1,2,6
    1,3,1
    1,4,5
    2,3,5
    2,5,3
    3,4,5
    3,5,6
    3,6,4
    4,6,2
    5,6,6
    1,3
    4,6
    2,5
    3,6
    2,3
    
  • 相关阅读:
    Linux Vim编辑器
    Linux sed 流编辑器
    Shell 编程 (变量和条件测试)
    Linux 下 Bash配置文件读取
    Linux 用户、权限
    Linux 指令(一)文件/目录操作
    Ubuntu 下安装 Swoole
    Mysql IN语句查询
    Mysql 查询优化
    Mysql 获取表属性
  • 原文地址:https://www.cnblogs.com/wzyuan/p/10041261.html
Copyright © 2020-2023  润新知