c++ 图的连通分量是什么_C语言:数据结构-图的生成树和最小生成树

图的生成树和最小生成树的概念

在一个连通图G中,如果取它的全部顶点和一部分边构成一个子图G′,即:

V(G’)=V(G)和E(G’)ÍE(G)

若边集E(G’)中的边既能够把图中的所有顶点连通而又不形成回路,则称子图G’是原图G的一棵生成树(Spanning Tree)。

下面简单说明一下既包含连通图G中的全部n个顶点又没有回路的子图G’(即生成树)必含有n-1条边。要构造子图G’,首先从图G中任取一个顶点加入G’中,此时G’中只有一个顶点,假定具有一个顶点的图是连通的,以后每向G’中加入一个顶点,都要加入以该顶点为一个端点,以已连通的顶点之中的一个顶点为另一个端点的一条边,这样既连通了该顶点又不会产生回路,进行n-1次后,就向G’中加入了n-1个顶点和n-1条边,使得G’中的n个顶点既连通又不产生回路。

在图G的一棵生成树G’中,若再增加一条边,就会出现一条回路。这是因为此边的两个端点已连通,再加入此边后,这两个端点间有两条路径,因此就形成了一条回路,子图G’也就不再是生成树了。同样,若从生成树G’中删去一条边,就使得G’变为非连通图。这是因为此边的两个端点是靠此边唯一连通的,删除此边后,必定使这两个端点分属于两个相互独立的连通分量中,使G’变成了具有两个独立连通分量的非连通图。

同一个图可以有不同的生成树,只要能够连通所有顶点又不产生回路的任何子图都是它的生成树。例如对于图7-11(a),图7-11(b)、(c)、(d)都是它的生成树。在每棵生成树中都包含有8个顶点和7条边,它们的差别只是边的选取不同。

928b724729f018fb2fc343ba971f74b8.png

连通图和它的生成树

在这三棵生成树中,图7-11(b)所示的树是从图中顶点v0出发利用深度优先搜索遍历得到的,被称之为深度优先生成树;图7-11(c)所示的树是从顶点v0出发利用广度优先搜索遍历得到的,被称之为广度优先生成树;图7-11(d)所示的树是任意一棵生成树。当然图7-11(a)的生成树远不止这几种,还可以画出其他许多种。

对于一个连通网(即连通带权图,假定每条边上的权均为大于零的实数)来说,生成树不同,每棵树的权(即树中所有边上的权值总和)也可能不同。图7-12(a)就是一个连通网,图7-12(b)、(c)、(d)是它的三棵生成树,每棵树的权都不同,分别为57、53和38。具有最小权的生成树称为图的最小生成树(Minimum Spanning Tree)。通过后面将要介绍的构造最小生成树的算法可知,图7-12(d)是图7-12(a)的最小生成树。

a668f8eca8e3ca803b8c43c49c9fe80d.png

连通网和它的生成树

求图的最小生成树很有实际意义。例如,若一个连通网表示城市之间的通讯系统,网的顶点代表城市,网的边代表城市之间架设通讯线路的造价,各城市之间的距离不同,地理条件不同,其造价也不同,即边上的权不同,现在要求既要连通所有城市、又要使总造价最低,这就是一个求图的最小生成树的问题。

求图的最小生成树的算法主要有两个:一是普里姆(Prim)算法。另一是克鲁斯卡尔(Kruskal)算法。

图的生成树不唯一。因为同一个图可以有不同的生成树,只要能够连通所有顶点又不产生回路的任何子图都是它的生成树,如深度优先生成树、广度优先生成树等。

克鲁斯卡尔算法是一种用于求解带权无向最小生成树的贪心算法。下面是用C语言实现克鲁斯卡尔算法的步骤: 1. 定义数据结构:首先,我们需要定义一个表示数据结构,包括顶点和边。可以使用邻接矩阵或邻接表来表示。 2. 初始化:将的边按照权值从小到大进行排序。 3. 创建并查集:创建一个并查集数据结构,用于判断两个顶点是否属于同一个连通分量。 4. 遍历边:从权值最小的边开始遍历,如果该边连接的两个顶点不在同一个连通分量,则将该边加入最小生成树,并将这两个顶点合并到同一个连通分量。 5. 终止条件:重复步骤4直到最小生成树的边数等于顶点数减一,或者遍历完所有的边。 6. 输出最小生成树:遍历最小生成树的边,输出每条边的起点、终点和权值。 下面是一个简单的C语言实现示例: ```c #include <stdio.h> #include <stdlib.h> // 定义边的结构体 typedef struct { int start; int end; int weight; } Edge; // 定义并查集的结构体 typedef struct { int *parent; int size; } UnionFind; // 初始化并查集 void initUnionFind(UnionFind *uf, int size) { uf->parent = (int *)malloc(sizeof(int) * size); uf->size = size; for (int i = 0; i < size; i++) { uf->parent[i] = i; } } // 查找根节点 int findRoot(UnionFind *uf, int x) { if (uf->parent[x] != x) { uf->parent[x] = findRoot(uf, uf->parent[x]); } return uf->parent[x]; } // 合并两个连通分量 void unionVertices(UnionFind *uf, int x, int y) { int rootX = findRoot(uf, x); int rootY = findRoot(uf, y); if (rootX != rootY) { uf->parent[rootX] = rootY; } } // 按权值从小到大排序的比较函数 int compare(const void *a, const void *b) { return ((Edge *)a)->weight - ((Edge *)b)->weight; } // 克鲁斯卡尔算法求解最小生成树 void kruskal(Edge *edges, int numEdges, int numVertices) { // 对边按权值进行排序 qsort(edges, numEdges, sizeof(Edge), compare); // 初始化并查集 UnionFind uf; initUnionFind(&uf, numVertices); // 遍历边 int count = 0; for (int i = 0; i < numEdges; i++) { int start = edges[i].start; int end = edges[i].end; int weight = edges[i].weight; // 判断两个顶点是否在同一个连通分量 if (findRoot(&uf, start) != findRoot(&uf, end)) { // 将边加入最小生成树 printf("Edge: %d - %d, Weight: %d\n", start, end, weight); count++; // 合并两个连通分量 unionVertices(&uf, start, end); } // 终止条件 if (count == numVertices - 1) { break; } } } int main() { int numVertices = 6; int numEdges = 9; Edge edges[] = { {0, 1, 4}, {0, 2, 3}, {1, 2, 1}, {1, 3, 2}, {2, 3, 4}, {2, 4, 3}, {3, 4, 2}, {3, 5, 1}, {4, 5, 6} }; kruskal(edges, numEdges, numVertices); return 0; } ``` 这是一个简单的克鲁斯卡尔算法的实现示例,你可以根据自己的需求进行修改和扩展。希望对你有帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值