首页 > 编程语言 >【知识】图论 朱刘算法梳理

【知识】图论 朱刘算法梳理

时间:2024-12-01 18:35:54浏览次数:7  
标签:图论 一条 int 树形图 算法 集合 去掉 梳理

朱刘算法:

树形图的定义:

  • 以某一个点为根的有向树,被称为 树形图

  • 一个有向图,满足无环且每个点的入度为 \(1\) (除了根节点),被称为 树形图

  • 最小树形图:对于所有树形图中,找到一个总权值和最小的树形图,被称为 最小树形图

最小树形图问题本质上其实就是有向图上的最小生成树问题。 Prim 算法和 Kruskal 算法可以解决无向图上的最小生成树问题。 朱刘算法可以解决有向图上的最小生成树问题。

朱刘算法

朱刘算法是一个迭代算法,每一次迭代:

  1. 除了根节点,对于每个点,找一下这个点的入边中权值最小的边

  2. 判断选出的边中是否存在环

    1. 无环,算法结束

    2. 有环,进入步骤 \(3\)

    3. 将所有环缩点,得到新图 \(G’\),对于每条边:

      • 环内部的边,删去
      • 边的终点 \(u\) 在环内,该边的权值变成原权值减去 \(u\) 在环内的边的权值,即 \(w - w_{环}\)
      • 其他边,不变

算法结束后,之前每一次迭代选出的所有边的总权值之和就是答案。

证明朱刘算法

  • 如果第一次选出的边中不存在环,就意味着当前选出的边满足两个条件,无环且每个点都有一个入边,说明我们选出的是一个树形图,由于每个点选出的边都是所有入边里面权值最小的边,所以一定不可能存在其他方案使得我们选择的边权和更小。

  • 如果有环,那么为什么算法还是对的呢?

假设原图是 \(G\),而缩完点且更新完边权之后的图是 \(G’\)。

我们考虑 \(G\) 中任意一个环,由于最终图中一定不能存在环,所以这个环一定存在两个性质,第一点是至少需要去掉一条边,第二点是必然存在一个最优解只去一条边。

假设我们现在任意去掉一条边 \(a\),那么必然会选一条新边 \(b\) 连向 \(a\) 的终点,此时如果我们在任意去掉另外一条边 \(c\),那么必然会再选一条新边 \(d\) 连向 \(c\) 的终点。此时由于 \(c\) 一定小于等于 \(d\),并且由于去掉了 \(a\),选上 \(c\) 并不能让图中存在环且权值会变小,因此我们一定可以把 \(d\) 换回 \(c\)。按照这个原理,任意给我们一个最优解,如果最优解中去掉的边数大于 \(1\),那么我们必然可以从新加的边去掉,换回环上的边,这样它仍然满足是一个树形图,但是总边权和不会变大。由此得出对于任意一个环,必然存在一个最优解只去一条边。

有了以上两个性质,我们可以进行证明。

假设图 \(G\) 中所有环里面只去掉环上一条边的树形图的集合放在左边,将 \(G’\) 里面所有树形图的集合放在右边。

对于左边集合中的图的任何一个环,我们只去掉一条边,然后连上一条新边,由于环已经被我们缩点,那么新边就会连向缩点后的新点,对于新点而言,入边就是唯一的,所以去掉一条边后图中无环且每个点的入度为 \(1\),所以去掉一条边后会构成一个树形图,说明左边集合的任意一个图,我们都可以转化成右边集合的一个树形图。

反过来,对于右边集合中任意一个树形图,我们找到一个不是根节点的缩点后的点,那么这个点必然存在一个入边,且这个入边必然是原图里的某一条边,且它一定连向缩点后这个点内部的某一个点,我们将这个点对应的内部的边去掉,将这条原图中的边加上。这样可以发现,任给我们一个右边集合的树形图,我们都可以转化成左边集合的一个满足两个性质的树形图。

因此两个集合是相互对应的。

然后看一下数量关系,可以发现左边集合加上了一条环外边 \(w\),去掉了一条环内边 \(w’\),因此整个操作等于是加上了 \(w-w’\),而右边集合中我们定义每条边就是 \(w-w’\),所以两个集合在数量关系上也是完全一样的。

综上所述,我们想求左边集合的最小树形图只需要求右边集合的最小树形图就行了,因此每次图中有环进行的处理是正确的。

每次迭代最多去掉一个点,最多迭代 \(n\) 次,每次迭代内部是时间复杂度是 \(O(m)\),因此整个算法时间复杂度是 \(O(nm)\)。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
 
typedef long long LL;
 
const int N = 105, M = 10005, INF = 1e9;
 
int n, m, rt = 1, X[N], Y[N], col, in[N];
 
int vis[N], id[N], pre[N];
 
struct E{
	int u, v, w;
} e[M];
 
int inline edmonds() {
	int ans = 0;
	while (true) {
		for (int i = 1; i <= n; i++) in[i] = INF;
		memset(vis, 0, sizeof vis);
		memset(id, 0, sizeof id);
		for (int i = 1; i <= m; i++) 
			if (e[i].w < in[e[i].v]) in[e[i].v] = e[i].w, pre[e[i].v] = e[i].u;
		for (int i = 1; i <= n; i++)
			if (in[i] == INF && i != rt) return -1;
		col = 0;
		for (int i = 1; i <= n; i++) {
			if (i == rt) continue;
			ans += in[i];
			int v = i;
			while (!vis[v] && !id[v] && v != rt)
				vis[v] = i, v = pre[v];
 			if (v != rt && vis[v] == i) {
 				id[v] = ++col;
 				for (int x = pre[v]; x != v; x = pre[x]) id[x] = col;
 			}
		}
		if (!col) break;
		for (int i = 1; i <= n; i++) if (!id[i]) id[i] = ++col;
		int tot = 0;
		for (int i = 1; i <= m; i++) {
			int a = id[e[i].u], b = id[e[i].v];
			if (a == b) continue;
			e[++tot] = (E) { a, b, e[i].w - in[e[i].v] };
		}
		m = tot, n = col, rt = id[rt];
	}
	return ans;
}
 
int main() {
	scanf("%d%d%d", &n, &m, &rt);
	int tot = 0;
	for (int i = 1; i <= m; i++) {
		int a, b, c; scanf("%d%d%d", &a, &b, &c);
		if (b != rt && a != b) e[++tot] = (E) { a, b, c };
	}
	m = tot;
	printf("%d\n", edmonds());
	return 0;
}

标签:图论,一条,int,树形图,算法,集合,去掉,梳理
From: https://www.cnblogs.com/fanrunze/p/18580138

相关文章

  • 游戏防检查之易语言鼠标轨迹算法 - 模拟人工轨迹
    一.简介鼠标轨迹算法是一种模拟人类鼠标操作的程序,它能够模拟出自然而真实的鼠标移动路径。鼠标轨迹算法的底层实现采用C/C++语言,原因在于C/C++提供了高性能的执行能力和直接访问操作系统底层资源的能力。鼠标轨迹算法具有以下优势:模拟人工轨迹:算法能够模拟出非贝塞尔曲线......
  • 游戏防检查之C++鼠标轨迹算法 - 模拟人工轨迹
    一.简介鼠标轨迹算法是一种模拟人类鼠标操作的程序,它能够模拟出自然而真实的鼠标移动路径。鼠标轨迹算法的底层实现采用C/C++语言,原因在于C/C++提供了高性能的执行能力和直接访问操作系统底层资源的能力。鼠标轨迹算法具有以下优势:模拟人工轨迹:算法能够模拟出非贝塞尔曲线......
  • 60天学通算法day4
    1.两两交换链表中的节点这个题两两交换指的是1与2交换,3与4交换……以此类推,这个题是一个相对简单的题当然有两种比较好的解法解法一:临时指针交换法如果说链表这里什么东西比较实用,那一定是虚拟头指针以及temp临时指针了。为了不涉及原来头两个节点的交换,换句话说就是使后......
  • 代码随想录算法训练营第十四天 | 226.翻转二叉树、 101. 对称二叉树、104.二叉树的最
    文档讲解:代码随想录视频讲解:代码随想录状态:完成4道题226.翻转二叉树整体思路:交换每一个节点的左右孩子思考:使用哪种遍历方式?建议使用前序或后序遍历(中序遍历比较绕)​前序遍历#Definitionforabinarytreenode.#classTreeNode:#def__init__(self,va......
  • 常用算法函数
    C++提供了丰富的算法函数库,主要通过头文件<algorithm>和<numeric>来提供常用的算法函数1.排序算法sort对范围内的元素进行排序,时间复杂度为\((O(\frac{N}{logN}))\)。sort(vec.begin(),vec.end());sort(a.begin(),a.end());//less<int>() //12345sort(a......
  • 【数据结构与算法】链表之美-复杂链表的复制与链表的插入排序
    主页:HABUO......
  • 机器学习算法中的距离计算方式详解
    目录欧氏距离(EuclideanDistance)原理详解代码实现应用场景曼哈顿距离(ManhattanDistance)原理详解代码实现应用场景闵可夫斯基距离(MinkowskiDistance)原理详解代码实现应用场景马氏距离(MahalanobisDistance)原理详解代码实现应用场景汉明距离(HammingDistance......
  • 【优选算法篇】一文读懂滑动窗口:动态调整范围的算法利器(上篇)
    文章目录须知......
  • 算法日记 36-38day 动态规划
    今天把动态规划结束掉,包括子序列以及编辑距离题目:最长公共子序列1143.最长公共子序列-力扣(LeetCode)给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。一个字符串的 子序列 是指这样一个新的字符串......
  • MATLAB实现WOA-BP鲸鱼优化算法优化BP神经网络多输入单输出回归预测(多指标,多图)
    目录MATLAB实现WOA-BP鲸鱼优化算法优化BP神经网络多输入单输出回归预测(多指标,多图)    1项目背景介绍...1项目目标与意义...2项目挑战...4项目特点与创新...5项目模型架构...6项目模型描述及代码示例...7项目部署与应用...12项目扩展...15项目应该注意事......