最小生成树之Kruskal算法
Kruskal算法是一种用来查找最小生成树的算法,由Joseph Kruskal在1956年发表。用来解决同样问题的还有Prim算法和Boruvka算法等。三种算法都是贪心算法的应用。和Boruvka算法不同的地方是,Kruskal算法在图中存在相同权值的边时也有效。
最小生成树:
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。
生成树中权值(代价)最小的树
问题分析:
就最小生成树问题而言,解决问题的关键是贪心算法的应用.
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
生成最小生成树有两种方案,一是按边找点,一是按点找边。Kruskal算法的思路是按边找点。
并边+贪心
Kruskal算法本身并不难,他的难度主要是并查集的应用。
并查集。
并查集(Union-find Sets)是一种非常精巧而实用的数据结构,它主要用于处理一些不相交集合的合并问题。一些常见的用途有求连通子图、求最小生成树的 Kruskal 算法和求最近公共祖先(Least Common Ancestors, LCA)等。
- 构造一个只有n个顶点、没有边的非连通图T={V, ∅ }(也可以认为是所有点的集合),每个顶点自成一个连通分量(因为用到并查集的思想,可认为所有点都是一棵树,自己是自己的根节点);
- 将所有边组成一个集合,并将其按从小到大进行排列,每次选取当前边集合中权值最短的边;
- 找出选取出来的边的两端的节点,如果这两个节点分属与不同的连通分量,则将这两个连通分量合并,否则不做处理(两个节点分属于不同的树,则将两棵树合并为一棵。如果两个节点已经属于一棵树,则该边不做处理。)
- 重复下去,直到所有顶点在同一连通分量上为止(知道所以的节点均在一颗树上)。
本函数以邻接链表为存储结构运用Kruskal算法,判断改图是否为连通图同时求其最小生成树的权值和
#include <cstdio>
#include <iostream>
using namespace std;
typedef int Vertex; /* 用顶点下标表示顶点,为整型 */
/* 邻接点的定义 */
typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode{
Vertex AdjV; /* 邻接点下标 */
int weight;
PtrToAdjVNode Next; /* 指向下一个邻接点的指针 */
};
/* 顶点表头结点的定义 */
typedef struct Vnode{
PtrToAdjVNode FirstEdge; /* 边表头指针 */
int father;
} *PAdjList, AdjList; /* AdjList是邻接表类型 */
/* 图结点的定义 */
typedef struct GNode *PtrToGNode;
struct GNode{
int Nv; /* 顶点数 */
int Ne; /* 边数 */
PAdjList G; /* 邻接表 */
};
typedef PtrToGNode LGraph; /* 以邻接表方式存储的图类型 */
// 尾插法建立邻接表
LGraph CreateGraph(int cityNum, int roadNum, int*&weights) // 创建图并且将Visited初始化为false
{
LGraph graph = (LGraph)malloc(sizeof(GNode)); // 图表的内存申请
graph -> G = (PAdjList)malloc(cityNum * sizeof(AdjList)); // 邻接表的内存申请
PAdjList tail = (PAdjList)malloc(cityNum * sizeof(AdjList));
// 变量初始化
for(int i = 0; i < cityNum; ++i) {(graph->G + i)->FirstEdge = nullptr;}
for(int i= 0; i < cityNum; ++i) {(tail + i)->FirstEdge = nullptr;}
for(int i = 0; i < cityNum; ++i) {(graph ->G + i) ->father = -1;}
graph ->Nv = cityNum;
graph->Ne = roadNum;
for(int j = 0; j < roadNum; ++j) { // 循环记录数据
int city, road, cost;
scanf("%d %d %d", &city, &road, &cost);
if(city > cityNum || city < 1)
return nullptr;
getchar();
*(weights + j) = cost;
// 建立正向出度节点
PtrToAdjVNode node1 = (PtrToAdjVNode)malloc(sizeof(GNode));
node1 ->weight = cost;
node1->AdjV = road - 1;
node1 ->Next = nullptr;
if((graph ->G + city - 1) -> FirstEdge == nullptr) (graph -> G + city - 1) ->FirstEdge = node1;
else (tail + city - 1) ->FirstEdge ->Next = node1;
(tail + city - 1) ->FirstEdge = node1;
}
return graph;
}
// 并查集时更新各节点跟值
void UpdateTree(LGraph &Graph, int preFather, int currFather) {
for (int i = 0; i < Graph->Nv; ++i) {
if ((Graph->G + i)->father == preFather)
(Graph->G + i)->father = currFather;
}
(Graph ->G + preFather) ->father = currFather;
}
// 对所有边进行排序,从而每次取最权值最短的边
void Sort(int* &weights, int length) {
for (int i = 1; i < length; i++) {
for (int j = 0; j < length - i; j++) {
if (weights[j] > weights[j + 1]) {
int temp = weights[j];
weights[j] = weights[j + 1];
weights[j + 1] = temp;
}
}
}
}
// 寻找最少花费
// 本函数将并查集与Kruskal合并为一个函数,最好是将其分成两个函数。
int Kruskal ( LGraph &Graph, const int* weights){
int flag = 0;
int sum = 0;
PtrToAdjVNode ptemp = nullptr; // 定义外循环表头指针
for(int i = 0; i < Graph ->Ne; ++i){ // 对weights进行遍历,依次寻找最小边
for(int j = 0; j < Graph ->Nv; ++j){ // 对邻接表表头便利寻找对应边的节点
ptemp = (Graph ->G + j) ->FirstEdge; // 定义邻接表表头指针
while(ptemp && flag < 1) {
if(ptemp ->weight == *(weights + i) && flag < 1){ // 并查集操作
// 如果两个节点都是根
if((Graph ->G + j) ->father == -1 && (Graph -> G + (ptemp ->AdjV)) ->father == -1) {
//(Graph->G + (ptemp->AdjV))->father = j;
UpdateTree(Graph, j, ptemp ->AdjV);
flag = 1;
}
// 两个节点在不同的树上
else if((Graph ->G + j) ->father != (Graph -> G + (ptemp ->AdjV)) ->father
&& (Graph ->G + j) ->father != ptemp ->AdjV
&& (Graph -> G + (ptemp ->AdjV)) ->father != j) {
if ((Graph->G + j)->father == -1 && (Graph->G + (ptemp->AdjV))->father > -1)
UpdateTree(Graph, (Graph->G + (ptemp->AdjV))->father, j);
// 第二个是根,第一个不是根
else if ((Graph->G + j)->father > -1 && (Graph->G + (ptemp->AdjV))->father == -1)
UpdateTree(Graph, (Graph->G + j)->father, ptemp->AdjV);
else if ((Graph->G + j)->father > -1 && (Graph->G + (ptemp->AdjV))->father > -1)
UpdateTree(Graph, (Graph->G + (ptemp->AdjV))->father, (Graph->G + j)->father);
flag = 1;
}
}
ptemp = ptemp ->Next;
}
}
if(flag == 1) sum += *(weights + i);
flag = 0;
}
return sum;
}
int main()
{
LGraph graph = NULL;
int cityNum = 0;
int roadNum = 0;
scanf("%d %d", &cityNum, &roadNum);
getchar();
int* weights = (int*)malloc(roadNum * sizeof(int));
graph = CreateGraph(cityNum, roadNum, weights);
if(!graph){
printf(("Impossible"));
return 0;
}
Sort(weights, roadNum);
int cost = Kruskal(graph, weights);
int count = 0;
for(int i = 0; i < graph ->Nv; ++i)
{
//printf("%d\n", (graph ->G + i) ->father);
if((graph ->G + i) ->father == -1)
++count;
}
if(count > 1)
printf(("Impossible")); // 打印输出
else
printf("%d", cost);
return 0;
}