16 - 12 - 05 普里姆(Prim)算法-最小生成树-奥义

/*
一定要先修正误区:两个数组绝对不是简单的一维数组,他们并不单纯,
一个数组元素可以说包含了几层含义。
先强调一遍:
lowcost[maxvex]数组的意义:
  lowcost[j]表示,在已经被纳入生成树的顶点中,他们到顶点 j 的最小权重的值。
  如:lowcost[3] = 8 表示顶点3到“已被纳入所有顶点”的最小权值是8.
adjvex[maxvex]数组的意义:
   adjvex[j] = x 表示, j 是未被纳入生成树的节点,他到已被纳入树的所有节点的最小权值是与 x相连而产生的。 
   如:adjvex[3] = 5 表示顶点3(未纳入)与顶点5(已纳入)之间存在最小权值。
   */

//算法思路:
//① 从图中任意找一个点,加入到最小生成树中;
//②在剩下的顶点中,找权重最小的边,将该顶点纳入最小生成树;
//③重复步骤②;
//```
/* 邻接矩阵表示的图结构*/
#include <stdio.h>
#include <stdlib.h>
//#include <curses.h>  这是啥?

typedef char VertexType;        //顶点类型应由用户定义
typedef int EdgeType;           //边上的权值类型应由用户定义

#define MAXVEX  100             //最大顶点数,应由用户定义
#define INFINITY 65535       //用65535来代表无穷大
#define DEBUG

/****邻接矩阵结构****/
typedef struct {
    VertexType vexs[MAXVEX];    //顶点表
    EdgeType   arc[MAXVEX][MAXVEX]; //邻接矩阵,可看作边
    int numVertexes, numEdges;      //图中当前的顶点数和边数
} Graph;

/****定位****/
int locates(Graph *g, char ch) {
    int i = 0;
    for(i = 0; i < g->numVertexes; i++) {
        if(g->vexs[i] == ch) {
            break;
        }
    }
    if(i >= g->numVertexes) {
        return -1;
    }
    return i;
}

/****建立一个无向网图的邻接矩阵表示****/ 
void CreateGraph(Graph *g) {
    int i, j, k, w;
    printf("输入顶点数,和边数(中间加个逗号~):\n");
    scanf("%d,%d", &(g->numVertexes), &(g->numEdges));

#ifdef DEBUG
    printf("%d %d\n", g->numVertexes, g->numEdges);
#endif

    for(i = 0; i < g->numVertexes; i++) {
        printf("请输入顶点%d:\n", i);
        g->vexs[i] = getchar();
        while(g->vexs[i] == '\n') {
            g->vexs[i] = getchar();
        }
    }

#ifdef DEBUG
    for(i = 0; i < g->numVertexes; i++) {
        printf("%c ", g->vexs[i]);
    }
    printf("\n");
#endif


    for(i = 0; i < g->numEdges; i++) {
        for(j = 0; j < g->numEdges; j++) {
            g->arc[i][j] = INFINITY;   //邻接矩阵初始化
        }
    }
    for(k = 0; k < g->numEdges; k++) {
        char p, q;
        printf("输入边(vi,vj)上的下标i,下标j和权值:\n");

        p = getchar();
        while(p == '\n') {
            p = getchar();
        }
        q = getchar();
        while(q == '\n') {
            q = getchar();
        }
        scanf("%d", &w);

        int m = -1;
        int n = -1;
        m = locates(g, p);
        n = locates(g, q);
        if(n == -1 || m == -1) {
            fprintf(stderr, "there is no this vertex.\n");
            return;
        }
        //getchar();     //????啥意思 
        g->arc[m][n] = w;
        g->arc[n][m] = g->arc[m][n];  //因为是无向图,矩阵对称
    }
}

/****打印图****/ 
/*这句话要明白:如:adjvex[3] = 5    lowcost[3] = 8 即表示序号为5的顶点到序号为3的顶点的边的权值为8*/
void printGraph(Graph g) {
    int i, j;
    printf("构建的邻接矩阵如下所示.\n");
    for(i = 0; i < g.numVertexes; i++) {
        for(j = 0; j < g.numVertexes; j++) {
            printf("%5d  ", g.arc[i][j]);
        }
        printf("\n");
    }
}

/****prim算法来一个最小生成树****/ 
/*
在实现该算法的过程中,主要用到两个数组lowcost[maxvex],adjvex[maxvex],只要理解这两个数组的意义,就能理解这个算法的实现。
lowcost[maxvex]数组的意义:
  lowcost[j]表示,在已经被纳入生成树的顶点中,他们到顶点 j 的最小权重的值。
  如:lowcost[3] = 8 表示顶点3到“已被纳入所有顶点”的最小权值是8.
adjvex[maxvex]数组的意义:
   adjvex[j] = x 表示, j 是未被纳入生成树的节点,他到已被纳入树的所有节点的最小权值是与 x相连而产生的。 
   如:adjvex[3] = 5 表示顶点3(未纳入)与顶点5(已纳入)之间存在最小权值。

   ex  :  adjvex[3] = 5  lowcost[3] = 8  即表示序号为5的顶点到序号为3的顶点的边的权值为8
*/
void MiniSpanTree_Prime(Graph g) 
{
    int min, i, j, k;
    int adjvex[MAXVEX];         //保存相邻接的(adjacent)顶点下标
    int lowcost[MAXVEX];        //保存相关顶点间边的权值,low cost(权值)
    lowcost[0] = 0;             //Vo加入生成树
    adjvex[0] = 0;        
    for(i = 1; i < g.numVertexes; i++)   /*FOR 1*/  
    {              
        /*循环除下标为0的顶点外的全部顶点,注意是全部,看看那张邻接矩阵图。*/
        lowcost[i] = g.arc[0][i];   /*将与Vo和其邻接点连成的边的权值存入数组*/
                                /*adjvex[0] = 0;lowcost[3]即表示Vo与下标为3的顶点的连线的权值*/                         
        adjvex[i] = 0;    /*初始化都为v0的下标  */
    }    /*adjvex[i] = 0,初始化所有未被纳入生成树的顶点到已被纳入生成树的顶点都是最小权值*/ 

    for(i = 1; i < g.numVertexes; i++)   /*FOR 2*/ 
    {
        min = INFINITY;    //初始化最小权值为无穷大
        j = 1;
        k = 0;
        while(j < g.numVertexes)
        {   //循环全部顶点
            if(lowcost[j] != 0 && lowcost[j] < min) { /*每次迭代,min都是INFINITY。别担心*/ 
                min = lowcost[j];   /*则让当前权值成为最小值(覆盖INFINITY)*/
                k = j;   /*若碰到比当前权值还小的,将这个最小值的下标存入k(后面lowcost[k] = 0要用到)*/
            }    /*当第二次循环时,就找到了 F 点,lowcost[5] = 6,第三趟循环时,lowcost里非零值只有7*/ 
            j++;   
        }   /*在第一次循环时,直到遍历完所有与Vo相关的顶点,找到与Vo邻接的权值最小的相邻顶点*/                                

        printf("(%d,%d)", adjvex[k], k); /*打印当前顶点边中权值最小边,第一次打印的是(0,3),第二遍打印(3,5)*/ 
        lowcost[k] = 0;     /*将“当前顶点与Vo”的权值设置为0,表示此顶点已OK(T:为什么要设置为零呢?)。PS:Vo仅代表当前头顶点*/ 
                    /*第二遍循环k = 5,lowcost[5] = 0,节点5(F)纳入生成树,lowcost[ 0 7 0 .. ..  ..]*/
                    /*第三趟循环 7比8小,7权额入树,即节点为 1的顶点B,lowcost[0 0 0 8  ..   ..  ]*/ 
        for(j = 1; j < g.numVertexes; j++)  /*FOR3:循环所有顶点,这里又定义了j,与上文的 j 无关系*/ 
        {  
            if(lowcost[j] != 0 && g.arc[k][j] < lowcost[j])  /*因为在上一步 k = j,所以现在以“刚才与头顶点之间权值最小的邻点”
                                                         为顶点来进行本轮搜索,从lowcost[j] != 0 这个条件可以看出刚才 lowcost[k] = 0的用意是别让他倒退搜索回去 */
                                                         /*第二趟循环k=5,g.arc[k][j] < lowcost[j]始终不满足,循环结束转向 FOR2。*/
                                                                    /*第三趟lowcost里是0,7和65535,FE是最小边,进lowcost*/ 
            {                       /*对g.arc[k][j] < lowcost[j]的理解:上文:lowcost[i] = g.arc[0][i],即:g.arc[k][j] < g.arc[0][j] “见附图”*/ 

                lowcost[j] = g.arc[k][j];    /*第一趟:lowcost[5] = arc[3][5]==6 ; */ 
                adjvex[j] = k;    /*第一趟里,j=5(F顶点);k=3(D顶点),*/ 
            }                   /*上文提到过,adjvex[5] = 3;表示顶点5(未纳入)与顶点3(已纳入)之间存在最小权值。*/ 
        }    /*结束FOR 3,转向 FOR 2循环*/ //
    }   /*第一趟结束时,lowcost[ 0 7 6..  .. ..  ..]*/ 
        /*第二趟结束,lowcost[ 0 0 7 8(EF) .. .. ..]*/ 

    printf("\n");
}

int main(int argc, char **argv) {
    Graph g;

    //邻接矩阵创建图
    CreateGraph(&g);
    //打印网图
    printGraph(g);
    //求最小生成树
    MiniSpanTree_Prime(g);

    return 0;
}

//```

有一步是这样描述的

这里写图片描述

这里写图片描述

另注:能否处理负权值?
不能,这与贪心选择性质有关,每次都找一个距源点最近的点(dmin),然后将该距离定为这个点到源点的最短
路径;但如果存在负权边,那就有可能先通过并不是距源点最近的一个次优点(dmin’),再通过这个负权边L(L<0),使得路径之和更小

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值