图的应用-最小生成树

573 阅读7分钟
  • 连通图的生成树概念

    连通图的⽣成树定义:所谓⼀个连通图的⽣成树是⼀个极⼩的连通⼦图,它含有图中全部的n个顶点,但只⾜以 构成⼀颗树的n-1条边.例如下图(再多一条边就不是连通图的生成树)


    连通图生成树的三个条件:

    1. 图是连通图
    2. 图中包含N个顶点
    3. 图的边数等于N-1
  • 最小生成树概念

    把构成连通⽹的最⼩代价的⽣成树称为最⼩⽣成树

    其实就是不但要达到连通图的生成图的三个条件,并且所有变得权值加起来还要是最小的

  • 最⼩⽣成树Prim 算法解析与实现

    • 算法思路以及步骤

      大概的思路就是循环一个一个找,先找到v0到所有结点的前置记录下来,如果不能直接到达的权值设置为无穷大,这时候可以将0这个位置的下标j记为0 表示0位置这个顶点已经加入到最小生成树中了,然后遍历lowcost数组找到权值最小的顶点索引,再将该顶点对应到每个顶点的权值(已经加入到最小生成树的顶点除外)和lowcost数组中的前置进行对比如果更小更新lowcost数组的值然后再更新adjvex数组的值(如果是连通图的话冲一个顶点出发循环(顶点个数)次数,刚好吧图整个都遍历了一遍)
      步骤:
      1.创建两个数组lowcost和adjvex数组,lowcost用于存储权值,adjvex用来存储当前顶点的前驱顶点索引
      2.遍历所有顶点,并将对应的权值和索引写入到对应的数组
      3.最后的到的adjvex索引数组就是得出的最小生成树

    • 代码实现
    #include <stdio.h>
    #include "stdlib.h"
    
    #define OK 1
    #define ERROR 0
    #define TRUE 1
    #define FALSE 0
    #define MAXVEX 100 /* 最大顶点数,应由用户定义 */
    #define INFINITYC 65535
    
    typedef int Status;    /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
    typedef char ElemType;
    
    //typedef struct {
    //    int weight; //权值
    //    ElemType data; //内容
    //} vertex, *Vertexs;
    
    typedef struct {
        int numVertexes; //顶点的数量
        int numEdges; //边数
        int arc[MAXVEX][MAXVEX]; //编标信息
    }Graph;
    
    void createPGraph(Graph * G){
        int i, j;
        
        /* printf("请输入边数和顶点数:"); */
        G->numEdges=15;
        G->numVertexes=9;
        
        
        for (i = 0; i < G->numVertexes; i++)/* 初始化图 */
        {
            for ( j = 0; j < G->numVertexes; j++)
            {
                if (i==j)
                    G->arc[i][j]=0;
                else
                    G->arc[i][j] = G->arc[j][i] = INFINITYC;
            }
        }
        
        G->arc[0][1]=10;
        G->arc[0][5]=11;
        G->arc[1][2]=18;
        G->arc[1][8]=12;
        G->arc[1][6]=16;
        G->arc[2][8]=8;
        G->arc[2][3]=22;
        G->arc[3][8]=21;
        G->arc[3][6]=24;
        G->arc[3][7]=16;
        G->arc[3][4]=20;
        G->arc[4][7]=7;
        G->arc[4][5]=26;
        G->arc[5][6]=17;
        G->arc[6][7]=19;
        
        for(i = 0; i < G->numVertexes; i++)
        {
            for(j = i; j < G->numVertexes; j++)
            {
                G->arc[j][i] =G->arc[i][j];
            }
        }
        
        
        for(i = 0; i < G->numVertexes; i++)
        {
            for(j = 0; j < G->numVertexes; j++)
            {
                printf("%d    ",G->arc[i][j]);
            }
            printf("\n");
        }
    }
    
    /**
     Prim实现思路
     大概的思路就是循环一个一个找,先找到v0到所有结点的前置记录下来,如果不能直接到达的权值设置为无穷大,这时候可以将0这个位置的下标j记为0  表示0位置这个顶点已经加入到最小生成树中了,然后遍历lowcost数组找到权值最小的顶点索引,再将该顶点对应到每个顶点的权值(已经加入到最小生成树的顶点除外)和lowcost数组中的前置进行对比如果更小更新lowcost数组的值然后再更新adjvex数组的值(如果是连通图的话冲一个顶点出发循环(顶点个数)次数,刚好吧图整个都遍历了一遍)
     步骤:
     1.创建两个数组lowcost和adjvex数组,lowcost用于存储权值,adjvex用来存储当前顶点的前驱顶点索引
     2.遍历所有顶点,并将对应的权值和索引写入到对应的数组
     3.最后的到的adjvex索引数组就是得出的最小生成树
     */
    
    void prim(Graph G,int *adjvex){
        //用于记录下一次比较用哪个顶点对应w的关系表比较
        int k = 0;
        int min; //用于记录最小权值
        //1.初始化索引数组(默认所有顶点对应的前驱顶点都是0)
        adjvex = (int *)calloc(G.numVertexes, sizeof(int));
        
        //2.初始化权值数组
        int lowcost[G.numVertexes];
        for(int i = 0; i < G.numVertexes; i ++){
            //应为默认所有的顶点的前驱顶点都是0,所以将0到所有顶点的权值写入到权值表中,
            //无法达到的顶点写入无穷大
            lowcost[i] = G.arc[0][i];
        }
        
        
        //3.开始遍历数组(直接冲1开始循环,应为0位置的顶点对应的信息已经写入到数组中)
        //达到更新lowcost和adjvex数组的几个条件
        //1.新权值小于旧 的权值
        
        int sum = 0;
        for(int i = 1; i < G.numVertexes; i ++){
    
            min = INFINITYC;
            for(int n = 0; n < G.numVertexes; n ++){
                if(lowcost[n] == 0){
                    continue;
                }
                
                if(lowcost[n] < min){
                    
                    min = lowcost[n];
                    
                    k = n;
                }
            }
            
            //将索引为k的顶点加入到最小生成树中
            lowcost[k] = 0;
            
            printf("v%d -> v%d \n",adjvex[k],k);
            
            sum += min;
            
            for(int j = 0; j < G.numVertexes; j ++){
                
                if(G.arc[k][j] < lowcost[j] && G.arc[k][j] > 0 && lowcost[j] > 0){
                    //满足条件开始更新lowcost和adjvex数组
                    lowcost[j] = G.arc[k][j];
                    
                    adjvex[j] = i;
                    
                }
            }
        }
        printf("%d",sum);
        
    }
    int main(int argc, const char * argv[]) {
        Graph G;
        createPGraph(&G);
        
        int *adjvex = NULL;
        prim(G,adjvex);
        return 0;
    }
    
  • 最⼩⽣成树Kruskal 算法解析与实现

    • 算法思路
      思路:首先要将邻接矩阵转换成边表数组,然后将边表数组的权值冲小到大排列, 然后再创建一个parent数组用来存放结点的父节点,然后遍历所有的边,通过parent 数组找到对应的对应边的链接信息避免闭环问题.如果不存在闭环则添加到最小生 成树中(也就是将对应的值存储在parent数组中)

      这个算法的精髓就是按照边表顺序从小到大一个边一个边的组合。只要组合成了一个生成树 那这时候的生成树就是最小生成树

    • 步骤图解

    • 代码实现

    /**
     Kruskal算法求出最小生成树
     思路:首先要将邻接矩阵转换成边表数组,然后将边表数组的权值冲小到大排列,
     然后再创建一个parent数组用来存放结点的父节点,然后遍历所有的边,通过parent
     数组找到对应的对应边的链接信息避免闭环问题.如果不存在闭环则添加到最小生
     成树中(也就是将对应的值存储在parent数组中)
     
     这个算法的精髓就是按照边表顺序从小到大一个边一个边的组合。只要组合成了一个生成树
     那这时候的生成树就是最小生成树
     */
    typedef struct {
        int beginIndex;  //起始顶点的索引
        int endIndex;   //终止顶点的索引
        int weight;     //权值
    } Egde;
    
    
    //冒泡排序
    void sort(Egde * Egdes,int num){
        for(int i = 0; i < num; i ++){
            for(int j = i + 1; j < num; j ++){
                if(Egdes[i].weight > Egdes[j].weight){
                    Egde temp = Egdes[i];
                    Egdes[i] = Egdes[j];
                    Egdes[j] = temp;
                }
            }
        }
        printf("sdf");
    }
    
    void kruskal(Graph G){
        Egde Egdes[G.numEdges];
        //现将所有的边信息录入到边表数组中
        int k = 0;
        for(int i = 0; i < G.numVertexes; i ++){
            for(int j = i; j < G.numVertexes; j ++){
                if(G.arc[i][j] > 0 && G.arc[i][j] < INFINITYC){
                    Egdes[k].beginIndex = i;
                    Egdes[k].endIndex = j;
                    Egdes[k].weight = G.arc[i][j];
                    k++;
                }
            }
        }
        
        //然后将边表数组中的信息排序(从小到达排序)
        sort(Egdes, G.numEdges);
        
        //创建parent数组用于存放节点的父顶点
        int parent[G.numVertexes];
        
        //默认所有顶点对应的父顶点都是0
        for(int i = 0; i < G.numVertexes; i ++){
            parent[i] = 0;
        }
        
        //开始遍历
        int sum = 0;
        for(int i = 0; i < G.numEdges; i ++){
            Egde e = Egdes[i];
            
            int n = e.beginIndex;
            int n_temp = parent[e.beginIndex];
            while(n_temp != 0){
                n = n_temp;
                n_temp = parent[n_temp];
            }
            
            int m = e.endIndex;
            int m_temp = parent[e.endIndex];
            while(m_temp != 0){
                m = m_temp;
                m_temp = parent[m_temp];
            }
            
            if(n != m){
                parent[n] = m;
                
                /*打印最小生成树路径*/
                printf("(%d, %d) %d\n", Egdes[i].beginIndex, Egdes[i].endIndex, Egdes[i].weight);
                sum += Egdes[i].weight;
            }
            
        }
        printf("%d",sum);
            
    }