算法——最小生成树:Kruskal算法、Prim算法

最小生成树定义:

    在一个具有V个节点的连通无向图中,找到一个子图,该子图包含原图的所有节点和部分连接边,且不能形成回路,同时子图边的权值总和最小。

最小生成树的算法:

根据对安全边的不同规则,有两种算法可以生成最小生成树。即Kruskal算法和Prim算法

Kruskal算法

        Kruskal算法使用的贪婪准则是:从剩下的边中选择一条不会产生环路,且具有最小耗费的边加入已选择的边的集合中;    

       假设 G=(V,{E}) 是一个含有 n 个顶点的连通网,则按照 Kruskal算法构造最小生成树的过程为:先构造一个只含 n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林。之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。

该算法找到安全边的方法是:在所有连接森林中两颗不同树的边里面,找到权值最小的边

步骤:

  1. 在带权值连通图中,按边的权值大小进行排序。
  2. 根据权值大小顺序判断集合是否需要该边,若集合需要该边,则将该边的两端节点加入到集合中,并使其连通。
  3. 循环第二步,直到所有节点都在集合中。

判断法则:当将边加入到已找到边的集合中时,是否会形成回路?
     1.如果没有形成回路,那么直接将其连通。此时,对于边的集合又要做一次判断:这两个点是否在已找到点的集合中出现过?
          ①.如果两个点都没有出现过,那么将这两个点都加入已找到点的集合中;
          ②.如果其中一个点在集合中出现过,那么将另一个没有出现过的点加入到集合中;
          ③.如果这两个点都出现过,则不用加入到集合中。
     2.如果形成回路,不符合要求,直接进行下一次操作。

下面根据带权值连通图对Kruskal算法的工作流程进行讲解:


上图为原图;连接边按照权值的大小排序:即;Kruskal算法按照权值从小到大进行判断,判断连接边节点是否属于集合A。


第一步:判断权值为1的连接边hg是否属于集合,由于开始集合为空,则将两个节点加入到集合,且连接该边,并判断是否属于回路;


第二步:判断权值为2的连接边ic是否属于集合,由于集合只有节点h和g,则将节点i和c加入到集合中,且连接该边,并判断是否属于回路;


第三步:判断权值为2的连接边gf是否属于集合,根据判断规则,把f加入到集合,且连接该边;


第四步:根据判断规则,将节点a和b加入到集合,且连接该边;


第五步:根据判断规则,连接该边cf;

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Prim算法Kruskal算法都是求解最小生成树的经典算法之一,这里我们分别介绍如何使用无向网的邻接矩阵存储结构实现这两个算法。 ## Prim算法 Prim算法是一种贪心算法,它从一个源节点开始不断扩展最小生成树的边,直到所有节点都被包含在最小生成树中。具体实现步骤如下: 1. 初始化最小生成树的边集为空集,将源节点加入最小生成树中。 2. 对于不在最小生成树中的节点,计算它们与最小生成树中节点的连边的权值,选择权值最小的边加入最小生成树中。 3. 重复步骤2,直到所有节点都被包含在最小生成树中。 下面是使用C++实现Prim算法的代码: ```cpp #include <iostream> #include <vector> #include <climits> using namespace std; const int MAXN = 100; // 最大节点数 const int INF = INT_MAX; // 无穷大 int graph[MAXN][MAXN]; // 无向网的邻接矩阵 bool visited[MAXN]; // 节点是否被访问过 int dist[MAXN]; // 节点到最小生成树的距离 int parent[MAXN]; // 最小生成树中节点的父节点 void prim(int start, int n) { // 初始化 for (int i = 0; i < n; i++) { visited[i] = false; dist[i] = INF; parent[i] = -1; } dist[start] = 0; // 按照Prim算法,不断扩展最小生成树 for (int i = 0; i < n; i++) { // 找到距离最小的节点 int minDist = INF, minIndex = -1; for (int j = 0; j < n; j++) { if (!visited[j] && dist[j] < minDist) { minDist = dist[j]; minIndex = j; } } // 将节点加入最小生成树中 visited[minIndex] = true; for (int j = 0; j < n; j++) { if (!visited[j] && graph[minIndex][j] < dist[j]) { dist[j] = graph[minIndex][j]; parent[j] = minIndex; } } } } int main() { int n = 6; // 节点数 // 初始化邻接矩阵 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { graph[i][j] = INF; } } graph[0][1] = graph[1][0] = 6; graph[0][2] = graph[2][0] = 1; graph[0][3] = graph[3][0] = 5; graph[1][2] = graph[2][1] = 5; graph[1][4] = graph[4][1] = 3; graph[2][3] = graph[3][2] = 5; graph[2][4] = graph[4][2] = 6; graph[2][5] = graph[5][2] = 4; graph[3][5] = graph[5][3] = 2; graph[4][5] = graph[5][4] = 6; prim(0, n); int sum = 0; for (int i = 0; i < n; i++) { if (parent[i] != -1) { cout << parent[i] << "-" << i << " " << graph[parent[i]][i] << endl; sum += graph[parent[i]][i]; } } cout << "Weight of MST: " << sum << endl; return 0; } ``` ## Kruskal算法 Kruskal算法也是一种贪心算法,它从所有边中选择权值最小的边,依次加入最小生成树中,直到所有节点都被包含在最小生成树中。具体实现步骤如下: 1. 初始化最小生成树的边集为空集。 2. 将所有边按照权值从小到大排序。 3. 依次选择每条边,如果它的两个端点不在同一个连通分量中,则将它加入最小生成树中,否则跳过。 4. 重复步骤3,直到所有节点都被包含在最小生成树中。 下面是使用C++实现Kruskal算法的代码: ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; const int MAXN = 100; // 最大节点数 const int INF = INT_MAX; // 无穷大 struct Edge { int from, to, weight; bool operator<(const Edge& other) const { return weight < other.weight; } }; int parent[MAXN]; // 节点的父节点 int rank[MAXN]; // 节点所在集合的秩 int find(int x) { if (parent[x] != x) { parent[x] = find(parent[x]); } return parent[x]; } void unionSet(int x, int y) { int rootX = find(x); int rootY = find(y); if (rootX == rootY) return; if (rank[rootX] < rank[rootY]) { swap(rootX, rootY); } parent[rootY] = rootX; if (rank[rootX] == rank[rootY]) { rank[rootX]++; } } vector<Edge> kruskal(int n, vector<Edge>& edges) { // 初始化 for (int i = 0; i < n; i++) { parent[i] = i; rank[i] = 0; } // 将边按照权值从小到大排序 sort(edges.begin(), edges.end()); // 依次选择每条边 vector<Edge> result; for (Edge edge : edges) { if (find(edge.from) != find(edge.to)) { result.push_back(edge); unionSet(edge.from, edge.to); } } return result; } int main() { int n = 6; // 节点数 // 初始化边 vector<Edge> edges = { {0, 1, 6}, {0, 2, 1}, {0, 3, 5}, {1, 2, 5}, {1, 4, 3}, {2, 3, 5}, {2, 4, 6}, {2, 5, 4}, {3, 5, 2}, {4, 5, 6} }; vector<Edge> result = kruskal(n, edges); int sum = 0; for (Edge edge : result) { cout << edge.from << "-" << edge.to << " " << edge.weight << endl; sum += edge.weight; } cout << "Weight of MST: " << sum << endl; return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值