prime算法求最小生成树——邻接矩阵(c语言详细代码)(附带案例)

Prime算法是解决最小生成树的一个算法。
不管是Kruskal算法还是Prime算法去,其核心都是切分定理。
切分定理:
切分
把图中的节点分为两部分,称为一个切分(Cut);
横切边
如果一个边的两个端点,属于切分(Cut)不同的两边,这个边称为横切边(Crossing Edge);
切分定理
给定任意切分,横切边中权值最小的边必然属于最小生成树;

切分定理的证明可使用反正法,这里不给出证明,证明不了只需要知道切分定理的正确性就能推出Prime算法的正确性。

Prime算法:
Prime算法是切分定理的正向应用。首先选定一个顶点0,当选择一个顶点后就把它看成一个切分,一部分为已选顶点集合{0},另一部分为未选顶点集合{1,2,3,4,5}。那么此时会出现横切边{{0,1},{0,2},{0,3}}。

在这里插入图片描述

按照切分定理选择最小的边,{0,3}则为最小生成树的一条边。此时将{0,3}这条边加入到存储最小生成树的数据结构中,将顶点3加入到已选集合中{0,3},未选集合为{2,4,5},这时横向边集合也发生了改变{{0,2},{0,1},{0,3},{3,7},{3,5},{3,4}}。
在这里插入图片描述

可以观察到{0,3}这条边刚刚已经加入到最小生成树中了,已经不再是横向边了。
此时可按照切分定理,选择最小的横向边,将边的另一点加入到已选集合中。
按照这个算法继续往下执行到如图所示:
在这里插入图片描述

可发现横向边从{0,2}从橙色变成了绿色。其实很好理解,已选集合{0,2,3,4}中的{0,2}那条边已经没有机会再被选择了,他们内部已经构成了局部最小生成树了。
按照这个算法,直到所有顶点都在已选集合中,算法就计算结束了。

总而言之,就是在找到的点和未找到的点的两个集合之间找到路劲最短的边,再将未找到的点放到已经找到的点的集合里

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#define N  100
#define max 1000;
//无向图邻接矩阵数据结构 
typedef  struct {
    int vcount;//顶点数
    char  vexs[N];     // 顶点信息
    int  arcs[N][N]; //带权关系矩阵,就是边
} GraphMatrix;

int letter2num(char letter) {
    return letter - 65;
}
char num2letter(int num) {
    return num + 65;
}//数字转字母,字母转数字

//void printGraph(GraphMatrix* G)
//{//本函数输出图的邻接矩阵
//    int i, j;
//    for (i = 0; i < G->vcount; i++)
//    {
//        printf("顶点%c      ", G->vexs[i]);
//        for (j = 0; j < G->vcount; j++)
//            printf("%d ", G->arcs[i][j]);
//        printf("\n");
//    }
//}

/*图初始化-邻接矩阵
*/
GraphMatrix* initGraphMatrix()
{
    GraphMatrix* G;
    G = (GraphMatrix*)malloc(sizeof(GraphMatrix));
    int v, s;
    printf("请输入定点数和边数: ");
    scanf("%d %d",&v, &s);
    G->vcount = v;
    int i, j;
    getchar();                  //吸收回车
    printf("请输入定点名称: ");
    for (i = 0; i < v; i++)     //创建矩阵
    {
        scanf("%c", &G->vexs[i]);
        getchar();              //吸收回车
        for (j = 0; j < v; j++)
        {
            G->arcs[i][j] = max;//初始化矩阵
        }
    }
    char str_s, str_e;
    int start,end,length;

    for (i = 0; i < s; i++) {
        printf("请输入边%d的顶点1,2和长度: ",i+1);
        scanf("%c %c %d", &str_s, &str_e,&length);
        getchar();              //吸收回车
        start =letter2num(str_s);
        end =letter2num(str_e);
        G->arcs[start][end] = length;
        G->arcs[end][start] = length;
    }
    return G;
}
void prime(GraphMatrix* G,char a) {
    int v=letter2num(a);        //起始点

    int visit[N]={0};           //点是否访问过,未访问0,访问1
    visit[v] = 1;               //起始点已经访问过

    int short_length[N];        //访问过的点的集合到未访问过点的集合的最小路径

    for (int i = 0; i < G->vcount; i++) 
        short_length[i] = G->arcs[v][i];        //初始化,此时只有起始点
    short_length[v] = 0;        //起始点路径为0

    printf("%c", a);
    for (int i = 0; i < G->vcount-1; i++)       //有n个点,那最短路径就是n-1个边组成
    {
        int min_length = max;
        int k=0;
        for (int j = 0; j < G->vcount; j++) {   //在所有未访问过的边里找到最短路径
            if (min_length > short_length[j] && visit[j] == 0) {
                k = j;
                min_length = short_length[j];
            }
        }
        visit[k] = 1;//将这个点加入已访问过的集合
        printf("-%c", num2letter(k));
        for (int j = 0; j < G->vcount; j++) {
            if (visit[j] == 0 && short_length[j] > G->arcs[k][j])
                short_length[j] = G->arcs[k][j];
        }//更新未访问过点的最短路径

    }
    return;
}
int main()
{

    GraphMatrix* G = initGraphMatrix();
    //printGraph(G);
    char a;
    //scanf("请输入起始点%c", &a);
    a = 'A';                    //假设起始点为A
    prime(G,a);
}

案例:

 
6 10
A B C D E F
A B 5
A C 6
A D 4
B C 1
B D 2
C D 2
C E 5
C F 3
D F 4
F E 4

  • 7
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
创建图邻接矩阵的步骤如下: 1. 定义一个二维数组来表示图的邻接矩阵,其中第i行第j列的值表示节点i到节点j的边权重。如果两个节点之间没有边,则该位置的值可以设置为无穷大或者一个非常大的值。 2. 初始化邻接矩阵,将所有值都设为无穷大或者一个非常大的值。 3. 遍历所有的边,将相应位置的值设置为边的权重。 下面是一个简单的示例代码: ```c #include <stdio.h> #define MAX 100 #define INF 0x7fffffff int G[MAX][MAX]; int n, m; void createGraph() { int i, j, u, v, w; scanf("%d %d", &n, &m); for(i = 1; i <= n; i++) { for(j = 1; j <= n; j++) { G[i][j] = INF; } } for(i = 1; i <= m; i++) { scanf("%d %d %d", &u, &v, &w); G[u][v] = G[v][u] = w; } } ``` 接下来,我们使用普里姆算法最小生成树。普里姆算法是一种贪心算法,它的基本思想是从一个任意节点开始,不断选择与当前生成树相连的最小边,直到所有的节点都被连通为止。具体步骤如下: 1. 选择一个任意节点作为起点,加入生成树中。 2. 遍历与生成树相邻的节点,找到其中权重最小的一条边,将它所连接的节点加入生成树中。 3. 重复步骤2,直到所有的节点都被加入生成树中。 下面是一个简单的示例代码: ```c void prim() { int i, j, k, min, sum = 0; int lowcost[MAX], closest[MAX]; for(i = 2; i <= n; i++) { lowcost[i] = G[1][i]; closest[i] = 1; } closest[1] = 0; for(i = 2; i <= n; i++) { min = INF; k = 0; for(j = 2; j <= n; j++) { if(closest[j] != 0 && lowcost[j] < min) { min = lowcost[j]; k = j; } } closest[k] = 0; sum += min; for(j = 2; j <= n; j++) { if(closest[j] != 0 && G[k][j] < lowcost[j]) { lowcost[j] = G[k][j]; closest[j] = k; } } } printf("最小生成树的权值之和为:%d\n", sum); } ``` 在上面的代码中,我们使用两个数组`lowcost`和`closest`来记录生成树中与每个节点相连的最小边和最近的节点。在初始化时,我们将数组`lowcost`和`closest`分别设置为节点1到其他节点的边权重和节点1。接着,我们遍历所有的节点,找到与当前生成树相邻的节点中,权重最小的一条边,并将它所连接的节点加入生成树中。接着,我们更新数组`lowcost`和`closest`中的值,继续遍历下一个节点,直到所有的节点都被加入生成树中。 完整代码如下: ```c #include <stdio.h> #define MAX 100 #define INF 0x7fffffff int G[MAX][MAX]; int n, m; void createGraph() { int i, j, u, v, w; scanf("%d %d", &n, &m); for(i = 1; i <= n; i++) { for(j = 1; j <= n; j++) { G[i][j] = INF; } } for(i = 1; i <= m; i++) { scanf("%d %d %d", &u, &v, &w); G[u][v] = G[v][u] = w; } } void prim() { int i, j, k, min, sum = 0; int lowcost[MAX], closest[MAX]; for(i = 2; i <= n; i++) { lowcost[i] = G[1][i]; closest[i] = 1; } closest[1] = 0; for(i = 2; i <= n; i++) { min = INF; k = 0; for(j = 2; j <= n; j++) { if(closest[j] != 0 && lowcost[j] < min) { min = lowcost[j]; k = j; } } closest[k] = 0; sum += min; for(j = 2; j <= n; j++) { if(closest[j] != 0 && G[k][j] < lowcost[j]) { lowcost[j] = G[k][j]; closest[j] = k; } } } printf("最小生成树的权值之和为:%d\n", sum); } int main() { createGraph(); prim(); return 0; } ``` 注意,上面的代码中使用了无穷大来表示两个节点之间没有边。在实际使用时,可以根据具体情况选择一个合适的值来表示无穷大。此外,普里姆算法的时间复杂度为O(n^2),其中n为节点数。当节点数比较大时,可能需要使用更高效的算法最小生成树

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值