加载中...

第三章 图论与搜索三

最小生成树

最小生成树:由n个节点,和n-1条边构成的无向连通图被称为G的一颗生成树,在G的所有生成树中,边的权值之和最小的生成树,被称为G的最小生成树。(换句话说就是用最小的代价把n个点都连起来)

有两种常用算法:

  • Prim算法(普利姆)朴素版Prim(时间复杂度O(n2),适用于稠密图)堆优化版Prim(时间复杂度O(mlogn),适用于稀疏图)
  • Kruskal算法(克鲁斯卡尔)适用于稀疏图,时间复杂度O(mlogm)对于最小生成树问题,如果是稠密图,通常选用朴素版Prim算法,因为其思路比较简洁,代码比较短,如果是稀疏图,通常选用Kruskal算法,因为其思路比Prim简单清晰。堆优化版的Prim通常不怎么用。

朴素Prim

算法流程

  1. 初始化距离, 将所有点的距离初始化为INF

    (这里的距离指的是点到集合的距离,而Dijkstra的距离是到起点的距离)

  2. n次循环

    1. 找到不在集合s中, 且距离最近的点t

    2. 用t来更新其他点到的距离

      集合s

    3. 将t加入到集合s中

代码模板

// 时间复杂度是 O(n2+m)O(n2+m) , nn 表示点数,mm 表示边数 int n; // n表示点数 int g[N][N]; // 邻接矩阵,存储所有边 int dist[N]; // 存储其他点到当前最小生成树的距离 bool st[N]; // 存储每个点是否已经在生成树中 // 如果图不连通,则返回INF(值是0x3f3f3f3f), 否则返回最小生成树的树边权重之和 int prim() { memset(dist, 0x3f, sizeof dist); int res = 0; for (int i = 0; i < n; i ++ ) { int t = -1; for (int j = 1; j <= n; j ++ ) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; if (i && dist[t] == INF) return INF; if (i) res += dist[t]; st[t] = true; for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]); } return res; }

Kruskal

算法流程

  1. 先将所有边,按照权重,从小到大排序
  2. 从小到大枚举每条边(a,b,w),若a,b不连通,则将这条边,加入集合中(将a点和b点连接起来)

代码模板

int n, m; // n是点数,m是边数 int p[N]; // 并查集的父节点数组 struct Edge // 存储边 { int a, b, w; bool operator< (const Edge &W)const { return w < W.w; } }edges[M]; int find(int x) // 并查集核心操作 { if (p[x] != x) p[x] = find(p[x]); return p[x]; } int kruskal() { sort(edges, edges + m); for (int i = 1; i <= n; i ++ ) p[i] = i; // 初始化并查集 int res = 0, cnt = 0; for (int i = 0; i < m; i ++ ) { int a = edges[i].a, b = edges[i].b, w = edges[i].w; a = find(a), b = find(b); if (a != b) // 如果两个连通块不连通,则将这两个连通块合并 { p[a] = b; res += w; cnt ++ ; } } if (cnt < n - 1) return INF; return res; }

二分图

二分图

指的是,可以将一个图中的所有点,分成左右两部分,使得图中的所有边,都是从左边集合中的点,连到右边集合中的点。而左右两个集合内部都没有边。

Image

有两种常用的相关算法

  • 染色法
  • 匈牙利算法

其中染色法是通过深度优先遍历实现,时间复杂度是O(n×m);匈牙利算法的时间复杂度理论上是O(n×m),但实际运行时间一般远小于O(n×m)。

Image

图论中的一个重要性质:一个图是二分图,当且仅当图中不含奇数环奇数环,指的是这个环中边的个数是奇数。(环中边的个数和点的个数是相同的)

染色法

可以用染色法来判断一个图是否是二分图,使用深度优先遍历,从根节点开始把图中的每个节点都染色,保证每个节点与相邻节点的颜色不同(但是只有黑白两种),只要染色过程中没有出现矛盾,说明该图是一个二分图,否则,说明不是二分图。

匈牙利算法

解决的问题

匈牙利算法,是给定一个二分图,用来求二分图的最大匹配的。

二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。

二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。

代码模板

int n1, n2; // n1表示第一个集合中的点数,n2表示第二个集合中的点数 int h[N], e[M], ne[M], idx; // 邻接表存储所有边,匈牙利算法中只会用到从第一个集合指向第二个集合的边,所以这里只用存一个方向的边 int match[N]; // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个 bool st[N]; // 表示第二个集合中的每个点是否已经被遍历过 bool find(int x) { for (int i = h[x]; i != -1; i = ne[i]) { int j = e[i]; if (!st[j]) { st[j] = true; if (match[j] == 0 || find(match[j])) { match[j] = x; return true; } } } return false; } // 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点 int res = 0; for (int i = 1; i <= n1; i ++ ) { memset(st, false, sizeof st); if (find(i)) res ++ ; }

__EOF__

本文作者Chenjq
本文链接https://www.cnblogs.com/chenjq12/p/17115027.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   Chenjq12  阅读(104)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示