Kruskal算法以邊為中心,按權值遞增選擇邊,若不形成環則加入生成樹。適用於**稀疏圖**(邊數接近頂點數),時間複雜度O(E log E)主要來自排序。本文提供完整C實現,含圖構建、邊排序、并查集檢測環、完整測試用例。
edges[]V-1時結束#include <stdio.h>
#include <stdlib.h>
#define MAX_VEX_NUM 50
#define MAX_ARC_NUM 100
#define UN_REACH 1000 // 無連接標記
typedef char VertexType;
typedef enum { DG, UDG } GraphType; // 有向/無向圖
/* 鄰接矩陣圖結構 */
typedef struct {
VertexType vexs[MAX_VEX_NUM]; // 頂點表(1-based索引)
int arcs[MAX_VEX_NUM][MAX_VEX_NUM]; // 鄰接矩陣
int vexnum, arcnum; // 頂點數、邊數
GraphType type; // 圖類型
} MGraph;
/* 邊結構 */
typedef struct {
int start, end; // 起點/終點索引(1-based)
int cost; // 權值
} Edge;
/* ===== 輔助函數 ===== */
/* 獲取頂點在vexs中的索引(1-based) */
int getIndexOfVexs(char vex, MGraph *MG) {
for (int i = 1; i <= MG->vexnum; i++)
if (MG->vexs[i] == vex) return i;
return 0; // 未找到
}
/* 創建圖:交互式輸入 */
void create_MG(MGraph *MG) {
int type, v1, v2, weight;
char c1, c2;
printf("圖類型 DG(0有向) UDG(1無向): ");
scanf("%d", &type);
MG->type = type ? UDG : DG;
printf("頂點數: "); scanf("%d", &MG->vexnum);
printf("邊數: "); scanf("%d", &MG->arcnum);
getchar(); // 吸收換行
for (int i = 1; i <= MG->vexnum; i++) {
printf("第%d個頂點字符: ", i);
scanf("%c", &MG->vexs[i]);
getchar();
}
/* 初始化鄰接矩陣 */
for (int i = 1; i <= MG->vexnum; i++)
for (int j = 1; j <= MG->vexnum; j++)
MG->arcs[i][j] = (i == j) ? 0 : UN_REACH;
/* 輸入邊 */
for (int k = 1; k <= MG->arcnum; k++) {
printf("第%d條邊: 起點 終點 權值: ", k);
scanf("%c %c %d", &c1, &c2, &weight);
getchar();
v1 = getIndexOfVexs(c1, MG);
v2 = getIndexOfVexs(c2, MG);
if (MG->type == UDG)
MG->arcs[v1][v2] = MG->arcs[v2][v1] = weight;
else
MG->arcs[v1][v2] = weight;
}
}
/* 打印圖信息 */
void print_MG(MGraph MG) {
printf("\n圖類型: %s\n", MG.type == DG ? "有向圖" : "無向圖");
printf("頂點數: %d, 邊數: %d\n", MG.vexnum, MG.arcnum);
printf("頂點集: ");
for (int i = 1; i <= MG.vexnum; i++) printf("%c ", MG.vexs[i]);
printf("\n鄰接矩陣:\n");
for (int i = 1; i <= MG.vexnum; i++) {
for (int j = 1; j <= MG.vexnum; j++) {
if (MG.arcs[i][j] == UN_REACH) printf("∞ ");
else printf("%d ", MG.arcs[i][j]);
}
printf("\n");
}
}
/* 從鄰接矩陣提取邊數組 */
void init_edge(MGraph MG, Edge edge[]) {
int count = 0;
if (MG.type == DG) { // 有向圖:所有非0、非UN_REACH
for (int i = 1; i <= MG.vexnum; i++)
for (int j = 1; j <= MG.vexnum; j++)
if (MG.arcs[i][j] != 0 && MG.arcs[i][j] != UN_REACH) {
edge[count++] = (Edge){i, j, MG.arcs[i][j]};
}
} else { // 無向圖:只取上三角避免重複
for (int i = 1; i <= MG.vexnum; i++)
for (int j = i; j <= MG.vexnum; j++)
if (MG.arcs[i][j] != 0 && MG.arcs[i][j] != UN_REACH) {
edge[count++] = (Edge){i, j, MG.arcs[i][j]};
}
}
MG.arcnum = count; // 實際邊數(無向圖去重後)
}
/* 簡單冒泡排序邊(教學用,工程用qsort) */
void sort_edge(Edge edge[], int arcnum) {
for (int i = 0; i < arcnum - 1; i++)
for (int j = i + 1; j < arcnum; j++)
if (edge[i].cost > edge[j].cost) {
Edge tmp = edge[i];
edge[i] = edge[j];
edge[j] = tmp;
}
}
/* ===== 并查集核心 ===== */
/* 查找根節點(帶路徑壓縮優化) */
int findFather(int father[], int v) {
int t = v;
while (father[t] != -1) t = father[t];
/* 路徑壓縮:將路徑上所有節點直接指向根 */
while (v != t) {
int parent = father[v];
father[v] = t;
v = parent;
}
return t;
}
/* ===== Kruskal主算法 ===== */
void Kruskal_MG(MGraph MG, Edge edge[]) {
int father[MAX_VEX_NUM];
for (int i = 0; i < MAX_VEX_NUM; i++) father[i] = -1; // -1表示根
int i = 0, count = 0;
printf("\nKruskal算法構建MST過程:\n");
while (i < MG.arcnum && count < MG.vexnum - 1) {
int vf1 = findFather(father, edge[i].start);
int vf2 = findFather(father, edge[i].end);
if (vf1 != vf2) { // 不同集合,不成環
father[vf2] = vf1; // 合併集合
count++;
printf("選擇邊: %c-%c 權值: %d\n",
MG.vexs[edge[i].start], MG.vexs[edge[i].end], edge[i].cost);
} else {
printf("捨棄邊: %c-%c 權值: %d (形成環)\n",
MG.vexs[edge[i].start], MG.vexs[edge[i].end], edge[i].cost);
}
i++;
}
if (count == MG.vexnum - 1)
printf("\nMST構建完成,共 %d 條邊\n", count);
else
printf("\n圖不連通,無法構建生成樹\n");
}
/* ===== 主函數 ===== */
int main(void) {
MGraph MG;
Edge edge[MAX_ARC_NUM];
create_MG(&MG);
print_MG(MG);
init_edge(MG, edge);
sort_edge(edge, MG.arcnum);
printf("\n排序後邊列表:\n");
for (int i = 0; i < MG.arcnum; i++)
printf("%c-%c: %d\n", MG.vexs[edge[i].start], MG.vexs[edge[i].end], edge[i].cost);
Kruskal_MG(MG, edge);
return 0;
}
輸入:
圖類型: 0 (有向)
頂點數: 4
邊數: 5
頂點: a b c d
邊: a b 1
a c 3
a d 4
b c 2
c d 3
輸出:
排序後邊列表:
a-b: 1
b-c: 2
a-c: 3
c-d: 3
a-d: 4
Kruskal算法構建MST過程:
選擇邊: a-b 權值: 1
選擇邊: b-c 權值: 2
捨棄邊: a-c 權值: 3 (形成環)
選擇邊: c-d 權值: 3
MST構建完成,共 3 條邊
/* 基礎版:僅查找根 */
int findFather(int father[], int v) {
while (father[v] != -1) v = father[v];
return v;
}
/* 優化版:路徑壓縮 - 將查找路徑上所有節點直接掛到根下 */
int findFather(int father[], int v) {
if (father[v] == -1) return v;
return father[v] = findFather(father, father[v]); // 遞歸壓縮
}
/* 進一步優化:按秩合併(按樹高/尺寸合併) */
int rank[MAX_VEX_NUM] = {0}; // 樹高上界
void unionSets(int father[], int rank[], int x, int y) {
int rx = findFather(father, x);
int ry = findFather(father, y);
if (rx == ry) return;
if (rank[rx] < rank[ry]) father[rx] = ry;
else if (rank[rx] > rank[ry]) father[ry] = rx;
else { father[ry] = rx; rank[rx]++; }
}
| 維度 | Kruskal | Prim |
|---|---|---|
| 核心思路 | 邊中心,全局排序 | 點中心,局部擴展 |
| 數據結構 | 邊數組 + 并查集 | 優先隊列/最小堆 + 鄰接表 |
| 時間複雜度 | O(E log E) | O(E log V) (堆優化) |
| 適合圖類型 | 稀疏圖 (E ≈ V) | 稠密圖 (E ≈ V²) |
| 邊權重複 | 天然處理 | 需去重或允許重複入堆 |
| 並行化 | 排序易並行 | 難並行 |
V*(V-1)/2條邊,有向圖V*(V-1)count < V-1說明圖不連通Kruskal算法以其簡潔優雅的邏輯、優秀的稀疏圖性能、天然的并行化潛力,成為最小生成樹問題的首選方案。掌握并查集的路徑壓縮與按秩合併優化,是解決動態連通性問題的核心技能,廣泛應用於網絡路由、聚類分析、圖像分割等領域。