克鲁斯卡尔算法(Kruskal算法)求最小生成树
克鲁斯卡尔算法(Kruskal算法)求最小生成树
克鲁斯卡尔算法,从边的角度求网的最小生成树,时间复杂度为O(eloge)。和普里姆算法恰恰相反,更适合于求边稀疏的网的最小生成树。
对于任意一个连通网的最小生成树来说,在要求总的权值最小的情况下,最直接的想法就是将连通网中的所有边按照权值大小进行升序排序,从小到大依次选择。
由于最小生成树本身是一棵生成树,所以需要时刻满足以下两点:
1.生成树中任意顶点之间有且仅有一条通路,也就是说,生成树中不能存在回路;
2.对于具有 n 个顶点的连通网,其生成树中只能有 n-1 条边,这 n-1 条边连通着 n 个顶点。
3.连接 n 个顶点在不产生回路的情况下,只需要 n-1 条边。
所以克鲁斯卡尔算法的具体思路是:将所有边按照权值的大小进行升序排序,然后从小到大一一判断,条件为:如果这个边不会与之前选择的所有边组成回路,就可以作为最小生成树的一部分;反之,舍去。直到具有 n 个顶点的连通网筛选出来 n-1 条边为止。筛选出来的边和所有的顶点构成此连通网的最小生成树。
判断是否会产生回路的方法为:(方法一)
在初始状态下给每个顶点赋予不同的标记,对于遍历过程的每条边,其都有两个顶点,判断这两个顶点的标记是否一致,如果一致,说明它们本身就处在一棵树中,如果继续连接就会产生回路;如果不一致,说明它们之间还没有任何关系,可以连接。
假设遍历到一条由顶点 A 和 B 构成的边,而顶点 A 和顶点 B 标记不同,此时不仅需要将顶点 A 的标记更新为顶点 B 的标记,还需要更改所有和顶点 A 标记相同的顶点的标记,全部改为顶点 B 的标记。
例如,使用克鲁斯卡尔算法找图 1 的最小生成树的过程为:
首先,在初始状态下,对各顶点赋予不同的标记(用颜色区别),如下图所示:
对所有边按照权值的大小进行排序,按照从小到大的顺序进行判断,首先是(1,3),由于顶点 1 和顶点 3 标记不同,所以可以构成生成树的一部分,遍历所有顶点,将与顶点 3 标记相同的全部更改为顶点 1 的标记,如(2)所示:
其次是(4,6)边,两顶点标记不同,所以可以构成生成树的一部分,更新所有顶点的标记为:
其次是(2,5)边,两顶点标记不同,可以构成生成树的一部分,更新所有顶点的标记为:
然后最小的是(3,6)边,两者标记不同,可以连接,遍历所有顶点,将与顶点 6 标记相同的所有顶点的标记更改为顶点 1 的标记:
继续选择权值最小的边,此时会发现,权值为 5 的边有 3 个,其中(1,4)和(3,4)各自两顶点的标记一样,如果连接会产生回路,所以舍去,而(2,3)标记不一样,可以选择,将所有与顶点 2 标记相同的顶点的标记全部改为同顶点 3 相同的标记:
当选取的边的数量相比与顶点的数量小 1 时,说明最小生成树已经生成。所以最终采用克鲁斯卡尔算法得到的最小生成树为(6)所示。
方法二(并查集)
将图中边按照权值从小到大排列,然后从最小的边开始扫描,设置一个边的集合来记录,如果该边并入不构成回路的话,则将该边并入当前生成树。直到所有的边都检测完为止。
并查集详细资料
实现代码
方法一:
//用于思路二
//辅助数组
typedef struct
{
int value; // 顶点数据(下标)
int sign; //顶点所属集合
}Assist[MAX_VERTEX];
Assist assists;
//在assist数组中找到顶点point对应的位置的下标
int Locatevex(int vexnum, int point)
{
for (int i = 1; i <= vexnum; i++)
{
if (assists[i].value == point)
{
return i;
}
}
return -1;
}
void Kruscal2(Matrix_Graph* G)
{
int initail, end , i , path = 0;
Edge_Node Edge[100];
//对边集矩阵进行排序---采用冒泡排序方法
fastSort(Edge, G);
for (i = 1; i <= G->edge_number; i++)
{
initail = Locatevex(G->vertex_number, Edge[i].begin);
end = Locatevex(G->vertex_number, Edge[i].end);//通过find找寻“待选”边两个顶点的“源顶点”
if (assists[end].sign != assists[initail].sign && initail != -1 && end != -1)
{
printf("路径 %d : [%c %c] , 权值为 %d\n", path+1, G->vertex[Edge[i].begin], G->vertex[Edge[i].end], Edge[i].weight);
path++;
//将新生成的树标记为一样
for (int k = 1; k <= G->vertex_number; k++)
{
if (assists[k].sign == assists[end].sign)
{
assists[k].sign = assists[initail].sign;
}
}
}
if (path == G->vertex_number - 1)
{
break;
}
}
}
方法二:
/***find函数***/
//功能:找寻每一个输入顶点的“源顶点”,并返回此源顶点
//用于思路一:
int find(int* parent, int f)//parent[i]数组记录顶点i的father为parent[i],层层追寻father,就可以找到顶点i的“源顶点”
{
while (parent[f] > 0)
f = parent[f];
return f;
}
//克鲁斯卡尔主功能代码思路一:
void Kruscal(Matrix_Graph* G)
{
int i, m, n, path;
Edge_Node Edge[100];
int parent[100];
path = 0;
//对边集矩阵进行排序---采用冒泡排序方法
fastSort(Edge, G);
//打印边集矩阵
PrintEdge_Node(G, Edge);
printf("\nKruscal\n");
//parent[]数组初始化:将所有顶点的父亲顶点置0
for (i = 1; i <= G->vertex_number; i++)
{
parent[i] = 0;
}
for (i = 1; i <= G->edge_number; i++)
{
n = find(parent, Edge[i].begin);
m = find(parent, Edge[i].end);//通过find找寻“待选”边两个顶点的“源顶点”
if (n != m)
{
parent[m] = n;//更新m顶点的“源顶点”是n
printf("路径 %d : [%c %c] , 权值为 %d\n", path+1, G->vertex[Edge[i].begin], G->vertex[Edge[i].end], Edge[i].weight);
path++;
}
if (path == G->vertex_number - 1)
{
break;
}
}
}
方法三
------思想类似于方法一
邻接矩阵生成
#define MAX_VERTEX 100
#define inf 65535 //用65535代表无穷大
#define VertexType char
/*************************建立无向图邻接矩阵**************************/
typedef struct Matrix_Graph
{
VertexType vertex[MAX_VERTEX];//存储顶点信息
int edge[MAX_VERTEX][MAX_VERTEX];//存储边信息
int vertex_number, edge_number;//存储图中的顶点数和边数
}Matrix_Graph;
//索引判断
int index(char vex, Matrix_Graph* mg)
{
int i;
for (i = 1;i <= mg->vertex_number;i++)
{
if (mg->vertex[i] == vex)
return i;
}
return 0;
}
void create_non_direction_matrix_Graph(Matrix_Graph* G)
{
int i, j, w, k;
VertexType h, t;
char ch;
printf("请输入顶点数和边数:\n");
scanf_s("%d %d", &G->vertex_number, &G->edge_number);
ch = getchar();
printf("请输入无向图顶点信息(如ABCD...):\n");
for (i = 1; i <= G->vertex_number; i++)
{
scanf_s("%c", &G->vertex[i], 1);
//为每一个顶点做标记不同的标记,说明属于不同的集合
assists[i].value = i;
assists[i].sign = i; //用于方法2 ,思路一可不需要
}
ch = getchar();
//初始化边信息
for (i = 1; i <= G->vertex_number; i++)
for (j = 1; j <= G->vertex_number; j++)
{
if (i == j)
G->edge[i][j] = 0;
else
G->edge[i][j] = inf;
}
for (k = 1; k <= G->edge_number; k++)
{
printf("第%d条边的两个顶点(Vi,Vj)和权值" , k);
scanf_s("%c %c %d", &h,1 ,&t,1, &w);
ch = getchar();
i = index(h, G);
j = index(t, G);
G->edge[i][j] = w;
G->edge[j][i] = G->edge[i][j];//由于是无向图,故是对称阵
}
//打印邻接矩阵
printf("*************************构造的邻接矩阵如下**************************\n");
for (i = 1; i <= G->vertex_number; i++)
{
for (j = 1; j <= G->vertex_number; j++)
if (G->edge[i][j] == inf)
{
printf("∞\t");
}
else
{
printf("%d\t", G->edge[i][j]);
}
printf("\n");
}
}
将邻接矩阵转化成边集矩阵
typedef struct Edge_Node
{
int begin;
int end;
int weight;
}Edge_Node;
//-----将邻接矩阵转换成记录权值从小到大排序的边集矩阵
void fastSort(Edge_Node* array, Matrix_Graph* G)
{
int i = 1, j = 0, k;
Edge_Node temp;
for (j = 1; j <G->vertex_number; j++)
{
array[j ].begin = 0;
array[j].end = 0;
array[j].weight =0;
}
//匹配相应的权值点
for (j = 1; j <= G->vertex_number; j++)
{
for (k = j; k <= G->vertex_number; k++)
{
if (G->edge[j][k] != 0 && G->edge[j][k] != inf)
{
array[i].begin = j;
array[i].end = k;
array[i].weight = G->edge[j][k];
i++;
}
}
}
printf("\n");
//对权值进行排序 -----冒泡排序
for (i = 1; i <= G->edge_number; i++)
{
for (j = 1; j <= G->edge_number - 1; j++)
{
if (array[j].weight > array[i].weight)
{
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
}
//----打印边集矩阵----
void PrintEdge_Node(Matrix_Graph* G, Edge_Node* array)
{
//打印“新矩阵”
printf("***************构造的边集矩阵如下****************\n");
printf("Edge[ ] \tbegin\tend\tweight\n");
for (int i = 1; i <= G->edge_number; i++)
printf("Edge[%d]:\t %c \t %c \t %d \t\n", i, G->vertex[array[i].begin], G->vertex[array[i].end], array[i].weight);
}
完整代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_VERTEX 100
#define inf 65535 //用65535代表无穷大
#define VertexType char
/*************************建立无向图邻接矩阵**************************/
typedef struct Matrix_Graph
{
VertexType vertex[MAX_VERTEX];//存储顶点信息
int edge[MAX_VERTEX][MAX_VERTEX];//存储边信息
int vertex_number, edge_number;//存储图中的顶点数和边数
}Matrix_Graph;
//用于思路二
//辅助数组
typedef struct
{
int value; // 顶点数据(下标)
int sign; //顶点所属集合
}Assist[MAX_VERTEX];
Assist assists;
//索引判断
int index(char vex, Matrix_Graph* mg)
{
int i;
for (i = 1;i <= mg->vertex_number;i++)
{
if (mg->vertex[i] == vex)
return i;
}
return 0;
}
void create_non_direction_matrix_Graph(Matrix_Graph* G)
{
int i, j, w, k;
VertexType h, t;
char ch;
printf("请输入顶点数和边数:\n");
scanf_s("%d %d", &G->vertex_number, &G->edge_number);
ch = getchar();
printf("请输入无向图顶点信息(如ABCD...):\n");
for (i = 1; i <= G->vertex_number; i++)
{
scanf_s("%c", &G->vertex[i], 1);
//为每一个顶点做标记不同的标记,说明属于不同的集合
assists[i].value = i;
assists[i].sign = i; //用于方法2 ,思路一可不需要
}
ch = getchar();
//初始化边信息
for (i = 1; i <= G->vertex_number; i++)
for (j = 1; j <= G->vertex_number; j++)
{
if (i == j)
G->edge[i][j] = 0;
else
G->edge[i][j] = inf;
}
for (k = 1; k <= G->edge_number; k++)
{
printf("第%d条边的两个顶点(Vi,Vj)和权值" , k);
scanf_s("%c %c %d", &h,1 ,&t,1, &w);
ch = getchar();
i = index(h, G);
j = index(t, G);
G->edge[i][j] = w;
G->edge[j][i] = G->edge[i][j];//由于是无向图,故是对称阵
}
//打印邻接矩阵
printf("*************************构造的邻接矩阵如下**************************\n");
for (i = 1; i <= G->vertex_number; i++)
{
for (j = 1; j <= G->vertex_number; j++)
if (G->edge[i][j] == inf)
{
printf("∞\t");
}
else
{
printf("%d\t", G->edge[i][j]);
}
printf("\n");
}
}
/*************************克鲁斯卡尔算法**************************/
//构造记录权值、边的起点、边的终点的边集矩阵结点
typedef struct Edge_Node
{
int begin;
int end;
int weight;
}Edge_Node;
//-----将邻接矩阵转换成记录权值从小到大排序的边集矩阵
void fastSort(Edge_Node* array, Matrix_Graph* G)
{
int i = 1, j = 0, k;
Edge_Node temp;
for (j = 1; j <G->vertex_number; j++)
{
array[j ].begin = 0;
array[j].end = 0;
array[j].weight =0;
}
//匹配相应的权值点
for (j = 1; j <= G->vertex_number; j++)
{
for (k = j; k <= G->vertex_number; k++)
{
if (G->edge[j][k] != 0 && G->edge[j][k] != inf)
{
array[i].begin = j;
array[i].end = k;
array[i].weight = G->edge[j][k];
i++;
}
}
}
printf("\n");
//对权值进行排序 -----冒泡排序
for (i = 1; i <= G->edge_number; i++)
{
for (j = 1; j <= G->edge_number - 1; j++)
{
if (array[j].weight > array[i].weight)
{
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
}
//----打印边集矩阵----
void PrintEdge_Node(Matrix_Graph* G, Edge_Node* array)
{
//打印“新矩阵”
printf("***************构造的边集矩阵如下****************\n");
printf("Edge[ ] \tbegin\tend\tweight\n");
for (int i = 1; i <= G->edge_number; i++)
printf("Edge[%d]:\t %c \t %c \t %d \t\n", i, G->vertex[array[i].begin], G->vertex[array[i].end], array[i].weight);
}
/***find函数***/
//功能:找寻每一个输入顶点的“源顶点”,并返回此源顶点
//用于思路一:
int find(int* parent, int f)//parent[i]数组记录顶点i的father为parent[i],层层追寻father,就可以找到顶点i的“源顶点”
{
while (parent[f] > 0)
f = parent[f];
return f;
}
//克鲁斯卡尔主功能代码思路一:
void Kruscal(Matrix_Graph* G)
{
int i, m, n, path;
Edge_Node Edge[100];
int parent[100];
path = 0;
//对边集矩阵进行排序---采用冒泡排序方法
fastSort(Edge, G);
//打印边集矩阵
PrintEdge_Node(G, Edge);
printf("\nKruscal\n");
//parent[]数组初始化:将所有顶点的父亲顶点置0
for (i = 1; i <= G->vertex_number; i++)
{
parent[i] = 0;
}
for (i = 1; i <= G->edge_number; i++)
{
n = find(parent, Edge[i].begin);
m = find(parent, Edge[i].end);//通过find找寻“待选”边两个顶点的“源顶点”
if (n != m)
{
parent[m] = n;//更新m顶点的“源顶点”是n
printf("路径 %d : [%c %c] , 权值为 %d\n", path+1, G->vertex[Edge[i].begin], G->vertex[Edge[i].end], Edge[i].weight);
path++;
}
if (path == G->vertex_number - 1)
{
break;
}
}
}
//思路二 用数组标记顶点,用一个集合标记,在同一个集合中有相同的标记
//在assist数组中找到顶点point对应的位置的下标
int Locatevex(int vexnum, int point)
{
for (int i = 1; i <= vexnum; i++)
{
if (assists[i].value == point)
{
return i;
}
}
return -1;
}
void Kruscal2(Matrix_Graph* G)
{
int initail, end , i , path = 0;
Edge_Node Edge[100];
//对边集矩阵进行排序---采用冒泡排序方法
fastSort(Edge, G);
for (i = 1; i <= G->edge_number; i++)
{
initail = Locatevex(G->vertex_number, Edge[i].begin);
end = Locatevex(G->vertex_number, Edge[i].end);//通过find找寻“待选”边两个顶点的“源顶点”
if (assists[end].sign != assists[initail].sign && initail != -1 && end != -1)
{
printf("路径 %d : [%c %c] , 权值为 %d\n", path+1, G->vertex[Edge[i].begin], G->vertex[Edge[i].end], Edge[i].weight);
path++;
//将新生成的树标记为一样
for (int k = 1; k <= G->vertex_number; k++)
{
if (assists[k].sign == assists[end].sign)
{
assists[k].sign = assists[initail].sign;
}
}
}
if (path == G->vertex_number - 1)
{
break;
}
}
}
//用于思路三: //查找源顶点的集合 //如果属于同一棵树上拥有同一个节点 思路二三比思路一时间复杂度较大
int find_pre(int* parent, int f)//parent[i]数组记录顶点i的father为parent[i],层层追寻father,就可以找到顶点i的“源顶点”
{
if (parent[f] == f)
return parent[f];
return find_pre( parent, parent[f]);
}
//克鲁斯卡尔主功能代码思路三:
void Kruscal3(Matrix_Graph* G)
{
int i, m, n, path;
Edge_Node Edge[100];
int parent[100];
path = 0;
//对边集矩阵进行排序---采用冒泡排序方法
fastSort(Edge, G);
printf("\nKruscal\n");
//parent[]数组初始化:将所有顶点的父亲顶点置0
for (i = 1; i <= G->vertex_number; i++)
{
parent[i] = i;
}
for (i = 1; i <= G->edge_number; i++)
{
n = find_pre(parent, Edge[i].begin);
m = find_pre(parent, Edge[i].end);//通过find找寻“待选”边两个顶点的“源顶点”
if (n != m)
{
printf("路径 %d : [%c %c] , 权值为 %d\n", path + 1, G->vertex[Edge[i].begin], G->vertex[Edge[i].end], Edge[i].weight);
path++;
//将新生成的树标记为一样
for (int k = 1; k <= G->vertex_number; k++)
{
if (parent[k] == parent[n])
{
parent[k] = parent[m];
}
}
}
if (path == G->vertex_number - 1)
{
break;
}
}
}
int main()
{
Matrix_Graph* G;
G = (Matrix_Graph*)malloc(sizeof(Matrix_Graph));
create_non_direction_matrix_Graph(G);
Kruscal(G);
printf("\nKruscal2");
Kruscal2(G);
printf("\nKruscal3");
Kruscal3(G);
return 0;
}
更多推荐
所有评论(0)