C数据结构与算法——无向图(最小生成树) 应用

实验任务

(1) 掌握Kruskal最小生成树算法;
(2) 掌握Prim最小生成树算法。

实验内容

(1) 随机生成一个无向网 G = ( V, E ),V = { A, B, C, D, E, F },| E | = 11,边的权值取值范围为 [ 1, 40 ];
(2) 使用Prim算法求出图G的最小生成树,给出选择顶点的顺序;
(3) 使用Kruskal算法从顶点A出发求图G的最小生成树,给出算法添加边的顺序;
(4) 给出最小生成树的代价。

实验源码

#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "math.h"

#define MAX_AMNUMS 100

int cost = 0; // 代价

// 枚举 布尔
typedef enum {
    FALSE,
    TRUE
} Boolean;

// 队列
typedef struct {
    int *base;
    int front;
    int rear;
} SqQueue;

// 无向网
typedef struct {
    char verxs[MAX_AMNUMS];
    int arcs[MAX_AMNUMS][MAX_AMNUMS];
    int numVertexes, numEdges;
    int minW, maxW;
} AMGraph;

// 访问标志
Boolean visited[MAX_AMNUMS];

// 克鲁斯卡尔
struct { // 辅助数组 Edge(边集) 的定义
    char Head; // 边的始点
    char Tail; // 边的终点
    int lowCost; // 边上的权值
} Edge[MAX_AMNUMS];

// 辅助数组 VexSet 的定义
int VexSet[MAX_AMNUMS];

Boolean InitUDN(AMGraph *G); // 初始化
void CreateRandUDN(AMGraph *G); // 创建随机无向网
void knuthShuffle(int arr[], int length); // 洗牌算法
void swapInt(int *card_1, int *card_2); // 交换函数
void PrintUDN(AMGraph G); // 打印无向网
void MiniSpanTree_Prim(AMGraph G, char vex); // 普里姆最小生成树算法
void MiniSpanTree_Kruskal(AMGraph G); // 克鲁斯卡尔最小生成树算法
void BubbleSort(int length); // 冒泡排序
int LocateVex(AMGraph G, char vex); // 取出顶点信息对应的顶点下标
void PrintCost(); // 打印代价

/**
 * <h2>无向网 实验三</h2>
 * @return 0
 */
int main() {

    srand(time(NULL));
    // 定义
    AMGraph G;
    // 初始化
    InitUDN(&G);
    // 顶点数+边数
    G.numVertexes = 7;
    G.numEdges = 11;
    // 权值范围
    G.minW = 1;
    G.maxW = 40;
    // 创建随机无向网
    CreateRandUDN(&G);
    // 打印无向网
    PrintUDN(G);
    printf("\n");
    // Prim算法
    printf("\n使用Prim算法从顶点A出发求G的最小生成树,依次选择的顶点是:\n");
    MiniSpanTree_Prim(G, 'A');
    printf("\n");
    // Kruskal算法
    printf("\n使用Kruskal算法求G的最小生成树,依次选择的边是:\n");
    MiniSpanTree_Kruskal(G);
    printf("\n");
    // 最小生成树代价
    printf("\n图G最小生成树的代价为:");
    PrintCost();

    getchar();
}

// 初始化无向网
Boolean InitUDN(AMGraph *G) {
    G = (AMGraph *) malloc(sizeof(AMGraph));
    if (!G) {
        return FALSE;
    }
    return TRUE;
}

void CreateRandUDN(AMGraph *G) {
    // 顶点编号(字母)
    for (int i = 0; i < G->numVertexes; i++) {
        G->verxs[i] = 'A' + i;
    }
    // 初始化 邻接矩阵为 相对[minW-maxW]范围无穷大
    for (int i = 0; i < G->numVertexes; i++) {
        for (int j = 0; j < G->numVertexes; j++) {
            G->arcs[i][j] = (G->maxW) + 1;
        }
    }
    /*
     * 随机生成 无向网上三角部分
     */
/*    // 结果测试
    int arr[6][6] = {
            {41, 9,  41, 41, 17, 13},
            {9,  41, 19, 16, 7,  41},
            {41, 19, 41, 32, 41, 12},
            {41, 16, 32, 41, 34, 14},
            {17, 7,  41, 34, 41, 22},
            {13, 41, 12, 14, 22, 41}
    };
    for (int i = 0; i < 6; i++) {
        for (int j = 0; j < 6; j++) {
            G->arcs[i][j] = arr[i][j];
        }
    }*/
    int upNum = (pow(G->numVertexes, 2) - G->numVertexes) / 2;
    int randArr[upNum];
    // 生成权值
    for (int i = 0; i < G->numEdges; i++) {
        // max-min+1 , min
        randArr[i] = rand() % (G->maxW) + (G->minW);
    }
    // 生成 无权值
    for (int i = (G->numEdges); i < upNum; i++) {
        randArr[i] = (G->maxW) + 1;
    }
    // 洗牌
    knuthShuffle(randArr, upNum);
    // 放入无向网(对称赋值)
    int count = 0;
    for (int i = 0; i < G->numVertexes - 1; i++) {
        for (int j = i + 1; j < G->numVertexes; j++) {
            G->arcs[i][j] = G->arcs[j][i] = randArr[count++];
        }
    }
}

// 洗牌
void knuthShuffle(int arr[], int length) {
    for (int i = length - 1; i >= 1; i--) {
        swapInt(&arr[i], &arr[rand() % (i + 1)]);
    }
}

// 交换
void swapInt(int *card_1, int *card_2) {
    int tCard;
    tCard = *card_1;
    *card_1 = *card_2;
    *card_2 = tCard;
}

// 输出
void PrintUDN(AMGraph G) {
    // 其他信息
    printf("无向网 G =(V, E)的顶点集 V = {");
    for (int i = 0; i < G.numVertexes; i++) {
        printf(" %c", G.verxs[i]);
        if (i != (G.numVertexes - 1)) {
            printf(",");
        }
    }
    printf(" }、边集 E及其权值如下邻接矩阵所示:\n");
    // 表头
    printf("    _");
    for (int i = 0; i < G.numVertexes; i++) {
        printf("  %c", G.verxs[i]);
    }
    printf(" _");
    printf("\n");
    // 内容
    for (int i = 0; i < G.numVertexes; i++) {
        // 最左侧打印
        if (i == (G.numVertexes - 1)) {
            printf(" %c |_", G.verxs[i]); // 尾行
        } else {
            printf(" %c | ", G.verxs[i]); // 非尾行
        }
        // 中间打印
        for (int j = 0; j < G.numVertexes; j++) {
            if (G.arcs[i][j] > G.maxW) {
                printf(" ∞");
            } else {
                printf(" %2d", G.arcs[i][j]);
            }
        }
        // 最右侧打印
        if (i == (G.numVertexes - 1)) {
            printf(" _|"); // 尾行
        } else {
            printf("  |"); // 非尾行
        }
        printf("\n");
    }
}

// Prim
void MiniSpanTree_Prim(AMGraph G, char vex) {
    char adjVex[MAX_AMNUMS]; // 最小边在 v 中那个顶点
    int lowCost[MAX_AMNUMS]; // 最小边上的权值

    int v = LocateVex(G, vex); // v 为顶点vex的下标
    /*
     * 初始化
     */
    for (int i = 0; i < G.numVertexes; i++) { // 对 VEX-V 的每一个顶点 Vi, 初始化adjVex 和 lowCost
        if (i != v) {
            adjVex[i] = vex; // 初始化全部先为 v 的顶点信息
            lowCost[i] = G.arcs[v][i]; // 将邻接矩阵第 0行 所有权值 先加入 lowCost
        }
    }
    lowCost[v] = G.minW - 1; // 初始,V = { v }
    /*
     * 构造最小生成树
     */
    for (int i = 1; i < G.numVertexes; i++) { // 选择其余 n-1 个顶点,生成 n-1 条边(n=G.numVertexes)
        // 求出T的下一个结点:第 k 个顶点,closedge[k]中存有当前最小边
        int min = 41;
        int j = 0;
        while (j < G.numVertexes) {
            // 寻找 和 最小边 和 最小边的另一个顶点
            if (lowCost[j] != (G.minW - 1) && lowCost[j] < min) {
                min = lowCost[j];
                v = j; // 当前顶点 变为 相邻顶点
            }
            j++;
        }
        // 输出
        char tVex_L = adjVex[v]; // tVex_L 为最小边的一个顶点,tVex 属于 VEX
        char tVex_R = G.verxs[v]; // tVex_R 为最小边的另一个顶点,属于 VEX - V
//        printf("(%c, %c)", tVex_L, tVex_R);
        if (i == 1) {
            printf("%c", tVex_L);
        }
        printf(" -> %c", tVex_R);
        // 选择最小边
        lowCost[v] = G.minW - 1; // 第 v 个顶点并入 V 集
        for (int k = 0; k < G.numVertexes; k++) {
            if (lowCost[k] != (G.minW - 1) && G.arcs[v][k] < lowCost[k]) { // 新顶点并入 V 后重新选择最小边
                adjVex[k] = G.verxs[v]; // 存入新顶点 v 的信息
                lowCost[k] = G.arcs[v][k];
            }
        }
    }
}

// Kruskal
void MiniSpanTree_Kruskal(AMGraph G) {
    // 取出无向网的权值 到辅助数组 Edge[]中
    int length = 0;
    for (int i = 0; i < G.numVertexes - 1; i++) {
        for (int j = i + 1; j < G.numVertexes; j++) {
            if (G.arcs[i][j] <= G.maxW) {
                Edge[length].Head = G.verxs[i];
                Edge[length].Tail = G.verxs[j];
                Edge[length++].lowCost = G.arcs[i][j];
            }
        }
    }
    // 将数组中的 所有元素 按 权值 从小到大排序
    BubbleSort(length);
    // 辅助数组,表示各顶点自成一个连通分量
    for (int i = 0; i < G.numVertexes; i++) {
        VexSet[i] = i;
    }
    // 依次查看数组 Edge 中的边
    for (int i = 0; i < G.numEdges; i++) {
        int vHead = LocateVex(G, Edge[i].Head); // vHead 为边的始点 Head 的下标
        int vTail = LocateVex(G, Edge[i].Tail); // vTail 为边的终点 Tail 的下标
        int vsHead = VexSet[vHead]; // 获取边 Edge[i]的始点所在的连通分量 vsHead
        int vsTail = VexSet[vTail]; // 获取边 Edge[i]的终点所在的连通分量 vsTail
        if (vsHead != vsTail) { // 边的两个顶点分别属于不同的连通分量
            if (i != 0) {
                printf(" -> ");
            }
            printf("(%c, %c)", Edge[i].Head, Edge[i].Tail); // 输出此边
            cost += Edge[i].lowCost;
            for (int j = 0; j < G.numVertexes; j++) { // 合并 vsHead 和 vsTail两个分量,即两个集合统一编号
                if (VexSet[j] == vsTail) { // 集合编号为 vsTail 的都改为 vsHead
                    VexSet[j] = vsHead;
                }
            }
        }
    }
}

// 排序(小到大)
void BubbleSort(int length) {
    int temp;
    char tHead;
    char tTail;
    for (int i = 0; i < length - 1; i++) { // 外层循环:轮次
        int index = -1;
        for (int j = 0; j < length - 1 - i; j++) { // 内层循环:比较并交换位置(找出每轮最小数)
            if (Edge[j].lowCost > Edge[j + 1].lowCost) {
                temp = Edge[j + 1].lowCost;
                tHead = Edge[j + 1].Head;
                tTail = Edge[j + 1].Tail;
                Edge[j + 1].lowCost = Edge[j].lowCost;
                Edge[j + 1].Head = Edge[j].Head;
                Edge[j + 1].Tail = Edge[j].Tail;
                Edge[j].lowCost = temp;
                Edge[j].Head = tHead;
                Edge[j].Tail = tTail;
                index++;
            }
        }
        if (index == -1) {
            break; // 为提高排序效率,如果在每轮排序中未发生一次位置交换则代表已经是需要的顺序(直接跳出排序)
        }
    }
}

// 取顶点下标
int LocateVex(AMGraph G, char vex) {
    for (int i = 0; i < G.numVertexes; i++) {
        if (G.verxs[i] == vex) {
            return G.verxs[i] - 'A';
        }
    }
    return FALSE;
}

// 输出代价
void PrintCost() {
    printf("%d ", cost);
}

实验结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小丶象

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值