(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
#include
#include
#include
#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(); // 打印代价
/**
* 无向网 实验三
* @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);
}