二分图、匈牙利算法-程序员宅基地

技术标签: 算法  

目录

一,二分图

CodeForces 687A NP-Hard Problem

力扣 785. 判断二分图

二,完全二分图

1,完全二分图

2,K2,3

3,K3,3

三,匈牙利算法

1,二分图最大匹配

2,其他图论问题


一,二分图

一般我们研究二分图默认指的是无向图。

二分图是指,可以把图中的顶点分成两个集合,使得每个集合中的所有点之间都是互不相连的。

	//判断是不是二分图, 节点编号是0到n-1
	static bool isTwoDivGraph(int n, UndirectedGraphData<int> &g) {
		UnionDif unions(n);
		for (auto e : g.edges) {
			unions.find(e.a);
			unions.find(e.b);
			if (unions.inSame(e.a, e.b)) {
				if (int(unions.getDif(e.a) + unions.getDif(e.b)) % 2)return false;
			}
			else {
				unions.merge(e.a, e.b,1);
			}
		}
		return true;
	}

CodeForces 687A NP-Hard Problem

题目:

Description

Recently, Pari and Arya did some research about NP-Hard problems and they found the minimum vertex cover problem very interesting.

Suppose the graph G is given. Subset A of its vertices is called a vertex cover of this graph, if for each edge uv there is at least one endpoint of it in this set, i.e.  or  (or both).

Pari and Arya have won a great undirected graph as an award in a team contest. Now they have to split it in two parts, but both of them want their parts of the graph to be a vertex cover.

They have agreed to give you their graph and you need to find two disjoint subsets of its vertices A and B, such that both A and B are vertex cover or claim it's impossible. Each vertex should be given to no more than one of the friends (or you can even keep it for yourself).

Input

The first line of the input contains two integers n and m (2 ≤ n ≤ 100 000, 1 ≤ m ≤ 100 000) — the number of vertices and the number of edges in the prize graph, respectively.

Each of the next m lines contains a pair of integers ui and vi (1  ≤  ui,  vi  ≤  n), denoting an undirected edge between ui and vi. It's guaranteed the graph won't contain any self-loops or multiple edges.

Output

If it's impossible to split the graph between Pari and Arya as they expect, print "-1" (without quotes).

If there are two disjoint sets of vertices, such that both sets are vertex cover, print their descriptions. Each description must contain two lines. The first line contains a single integer k denoting the number of vertices in that vertex cover, and the second line contains kintegers — the indices of vertices. Note that because of m ≥ 1, vertex cover cannot be empty.

Sample Input

Input

4 2
1 2
2 3

Output

1

2
1 3 

Input

3 3
1 2
2 3
1 3

Output

-1

题目大意就是把图分成二分图,如果不能的话,输出-1,如果能的话,分别输出2个部分的点的数量和点的标号。

代码:

#include<iostream>
#include<string.h>
using namespace std;
 
int list[100005];
int fenzu[100005];
 
int main()
{
	int n, m;
	cin >> n >> m;
	int fx, fy;
	memset(list, 0, sizeof(list));
	memset(fenzu, 0, sizeof(fenzu));
	int x, y;
	int zu = 1;
	while (m--)
	{
		cin >> x >> y;		
		if (list[x] == 0)
		{
			if (list[y] == 0)
			{
				fenzu[x] = zu;
				fenzu[y] = zu;
				list[x] = 1;
				list[y] = 2;
				zu++;
			}
			else
			{
				fenzu[x] = fenzu[y];
				list[x] = 3 - list[y];
			}
		}
		else
		{
			if (list[y] == 0)
			{
				fenzu[y] = fenzu[x];
				list[y] = 3 - list[x];
			}
			else
			{
				fx = fenzu[x];
				fy = fenzu[y];
				if (fx == fy)
				{
					if (list[x] == list[y])
					{
						cout << "-1";
						return 0;
					}
				}
				else
				{					
					if (list[x] == list[y])
					{
						for (int i = 1; i <= n; i++)
						if (fenzu[i] == fy)
						{
							fenzu[i] = fx;
							list[i] = 3 - list[i];
						}
					}					
					else for (int i = 1; i <= n; i++)
						if (fenzu[i] == fy)fenzu[i] = fx;
				}
			}
		}
	}
	int sum = 0;
	for (int i = 1; i <= n; i++)if (list[i] == 1)sum++;
	cout << sum << endl;
	for (int i = 1; i <= n; i++)if (list[i] == 1)cout << i<<" ";
	cout << endl << n - sum << endl;
	for (int i = 1; i <= n; i++)if (list[i] != 1)cout << i << " ";
	return 0;
}

可惜超时了,在test14超时了,test14估计是n和m非常大的了。

前面13个test都过了,想必这个代码是没有问题的,就是效率的问题。

很显然,主要的效率问题就是,在唯一的while循环里面,有1个从1到n的循环,只为了检索一些特定的目标出来。

所以很显然,我应该使用队列。

抛弃这个思想,用队列来做,其实很简单,只要记录每个点的所有邻居即可。

代码:

#include<iostream>
#include<vector>
#include<queue>
#include<string.h>
using namespace std;
 
vector<int>v[100005];
queue<int>q;
int list[100005];
 
int main()
{
	int n, m;
	cin >> n >> m;
	int x, y;
	while (m--)
	{
		cin >> x >> y;	
		v[x].insert(v[x].end(),y);
		v[y].insert(v[y].end(),x);
	}
	int k, key = 1;
	memset(list, 0, sizeof(list));
	vector< int >::iterator p;
	while (1)
	{
		while (list[key])key++;
		if (key > n)break;
		q.push(key);
		list[key] = 1;
		while (!q.empty())
		{
			k = q.front();
			q.pop();
			for (p = v[k].begin(); p != v[k].end(); p++)
			{
				if (list[*p] == list[k])
				{
					cout << "-1";
					return 0;
				}
				if (list[*p] == 0)
				{
					q.push(*p);
					list[*p] = 3 - list[k];
				}
			}
		}
	}
	int sum = 0;
	for (int i = 1; i <= n; i++)if (list[i] == 1)sum++;
	cout << sum << endl;
	for (int i = 1; i <= n; i++)if (list[i] == 1)cout << i<<" ";
	cout << endl << n - sum << endl;
	for (int i = 1; i <= n; i++)if (list[i] != 1)cout << i << " ";
	return 0;
}

表面上有3层循环,实际上外面的2层while循环是有限制的,合起来也就是大约n而已。

里面的循环就更不用说了,3层循环合起来也就是大约n而已。

所以这个算法还是很快的,果然AC了。

力扣 785. 判断二分图

存在一个 无向图 ,图中有 n 个节点。其中每个节点都有一个介于 0 到 n - 1 之间的唯一编号。给你一个二维数组 graph ,其中 graph[u] 是一个节点数组,由节点 u 的邻接节点组成。形式上,对于 graph[u] 中的每个 v ,都存在一条位于节点 u 和节点 v 之间的无向边。该无向图同时具有以下属性:

  • 不存在自环(graph[u] 不包含 u)。
  • 不存在平行边(graph[u] 不包含重复值)。
  • 如果 v 在 graph[u] 内,那么 u 也应该在 graph[v] 内(该图是无向图)
  • 这个图可能不是连通图,也就是说两个节点 u 和 v 之间可能不存在一条连通彼此的路径。

二分图 定义:如果能将一个图的节点集合分割成两个独立的子集 A 和 B ,并使图中的每一条边的两个节点一个来自 A 集合,一个来自 B 集合,就将这个图称为 二分图 。

如果图是二分图,返回 true ;否则,返回 false 。

示例 1:

输入:graph = [[1,2,3],[0,2],[0,1,3],[0,2]]
输出:false
解释:不能将节点分割成两个独立的子集,以使每条边都连通一个子集中的一个节点与另一个子集中的一个节点。

示例 2:

输入:graph = [[1,3],[0,2],[1,3],[0,2]]
输出:true
解释:可以将节点分成两组: {0, 2} 和 {1, 3} 。

提示:

  • graph.length == n
  • 1 <= n <= 100
  • 0 <= graph[u].length < n
  • 0 <= graph[u][i] <= n - 1
  • graph[u] 不会包含 u
  • graph[u] 的所有值 互不相同
  • 如果 graph[u] 包含 v,那么 graph[v] 也会包含 u

c++代码:

class Solution {
public:
	bool isBipartite(vector<vector<int>>& graph) {
		map<int, vector<int>>m;
		for (int i = 0; i < graph.size(); i++)m[i] = graph[i];
		UndirectedGraphData<int> g(m);
		return UndirectedGraph::isTwoDivGraph(m.size(), g);
	}
};

rust代码:

struct Union{
    fa:Vec<usize>,
    dif:Vec<i32>,
}
impl Union{
    fn find(& mut self,x:usize)->usize{
        if self.fa[x] == x{
            return x;
        }
        Union::find(self,self.fa[x]);
        self.dif[x] += self.dif[self.fa[x]];
        self.fa[x] = self.fa[self.fa[x]];
        return self.fa[x];
    }
    fn in_same(& mut self,x:usize,y:usize)->bool{
        return Union::find(self,x) == Union::find(self,y);
    }
    fn merge(& mut self,x:usize,y:usize,x_sub_y:i32)->(){
        Union::find(self,x);
        let fax = self.fa[x];
        self.dif[fax] = x_sub_y - self.dif[x];
        self.fa[fax] = y;
    }
}

impl Solution {
    pub fn is_bipartite(graph: Vec<Vec<i32>>) -> bool {
        let mut fa=Vec::new();
        let mut dif=Vec::new();
        for i in 0..graph.len(){
            fa.push(i);
            dif.push(0);
        }
        let mut opt = Union{fa:fa,dif:dif};
        for x in 0..graph.len(){
            for y in graph[x].clone(){
                let y = y as usize;
                if opt.in_same(x, y){
                    if (opt.dif[x]+opt.dif[y])%2 == 0{
                        return false;
                    }
                }else{
                    opt.merge(x, y, 1);
                }
            }
        }
        return true;
    }
}

二,完全二分图

1,完全二分图

完全二分图是一种特殊的二分图,可以把图中的顶点分成两个集合,使得第一个集合中的所有顶点都与第二个集合中的所有顶点相连。

2,K2,3

3,K3,3

三,匈牙利算法

1,二分图最大匹配

思路:

从0开始,逐步扩展。

扩展方法:寻找类似于0-1-2-3-4-5的路径,把匹配“1-2 3-4”调整成匹配“0-1 2-3 4-5”,匹配数加1。

示例:

调整前:

寻找路径:

调整后:

模板代码:

参考ACM模板

2,其他图论问题

待更新

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/nameofcsdn/article/details/137339829

智能推荐

HTML之img展示base64图片_base64图片示例-程序员宅基地

文章浏览阅读3.5k次。base64图片示例_base64图片示例

一个安全架构师需要做什么?有什么能力要求?_安全系统设计岗位需要具备的能力-程序员宅基地

文章浏览阅读5.2k次。杭州或者北京的安全架构师,可以到p7及以上级别的工作内容包括如下:1、负责奇点云平台数据安全相关核心功能研发,参与产品需求分析、业务讨论、设计工作,评估需求的技术可行性和技术难点;2、负责产品的技术框架选型,编写接口规范、编码规范等文档;3、负责系统架构设计、搭建、数据库设计、核心功能代码的编写;4、为开发人员提供快速有效的开发框架、服务、公用组件;5、负责项目开发过程中的技术攻关,和运行中出现的技术问题;6、负责对架构进行优化,持续改进性能、可扩展性、稳定性、安全性。岗位需要具..._安全系统设计岗位需要具备的能力

utc时间 单位换算_时间系统转换_北斗开放实验室-程序员宅基地

文章浏览阅读1.9k次。1.下载包概述BDT采用国际单位制(SI)秒为基本单位连续累计,不闰秒,起始历元为2006年1月1日协调世界时(UTC)00时00分00秒,采用周和周内秒计数。BDT通过UTC(NTSC)与国际UTC建立联系,BDT与UTC的偏差保持在100纳秒以内(模1秒)。协调世界时(UniversalCoordinatedTime,UTC)是对原子时..._utc时间单位

Ubuntu 修改默认的PDF打开方式-程序员宅基地

文章浏览阅读119次。Ubuntu自带了可以打开PDF的文档编辑器,如GIMP和文档查看器,但这些自带的文档编辑器并不是很好用,很可能会产生乱码,以至于不方便…… 由于在WIN下对foxit的产品感觉很满意,并且知道它也为linux做了一个PDF查看器,于是google了一下foxit deb,并下载下来…… 安装完了之后,发现它不是默认的PDF查看器,每次都需要单击右键选择,很..._debian 修改默认pdf打开

自己编写的matlab zigzag排序算法_matlab 矩阵zigzag排序-程序员宅基地

文章浏览阅读2.2k次。%%%%%%%%%%%%%%%%%Copyright:wzr1201 %%%Data:2013-05-08%%%%%%%%%%%%%%clear;clc;N=512;A=randint(N,N,[2,10]); %N-by-N, random array of integerslen=N*N;B=zeros(1,len);%zigzag排序后的_matlab 矩阵zigzag排序

R语言使用rnbinom函数生成符合负二项分布的随机数、使用plot函数可视化符合负二项分布的随机数(Negative binomial distribution)_r语言中怎么生成服从负二项分布的随机数-程序员宅基地

文章浏览阅读1.2k次,点赞12次,收藏11次。R语言使用rnbinom函数生成符合负二项分布的随机数、使用plot函数可视化符合负二项分布的随机数(Negative binomial distribution)_r语言中怎么生成服从负二项分布的随机数

随便推点

K近邻——KNN_kneighborsclassifier需要遍历所有样本吗-程序员宅基地

文章浏览阅读482次。KNN(emmmmm……..原计划要写的支持向量机,感觉内容有点多,先写个KNN过度一下吧,以后一定会补上的!)k近邻,简称kNN,全称k-Nearest Neighbor,是机器学习算法中最简单的算法之一。其基本原理是:根据与待测样本最近的k个邻居的标签来判断待测样本的标签,是一种监督学习方法。简单来说就是“物以类聚,人以群分”、“近朱者赤,近墨者黑”。这种算法严格来说甚至是不需要去..._kneighborsclassifier需要遍历所有样本吗

Ubuntu 搭建SVN服务器_ubuntu svn-程序员宅基地

文章浏览阅读1.9k次。为了学习SVN服务器是怎么搭建的,我在Ubuntu虚拟机上搭建了一个SVN服务器,可以作为项目开发的版本,记录下来防止自己遗忘,同时可以帮助更多的人。一:搭建环境查看Ubuntu系统的版本信息:可用cat /proc/version命令、uname -a命令与sb_release -a命令。Ubuntu:14.04 64位SVN:通过apt-get 安装二:搭建步骤..._ubuntu svn

Protostar Format Write Up_protostar format0 writeup-程序员宅基地

文章浏览阅读1k次。protostar format writeup_protostar format0 writeup

小孩-高性能Mysql-日志缓冲区-程序员宅基地

文章浏览阅读328次,点赞9次,收藏11次。日志缓冲区(Log Buffer)是 MySQL 数据库的一个关键组件,用于暂时存储事务日志(包括重做日志)的数据,以便后续写入到事务日志文件中。总之,日志缓冲区是 MySQL 数据库中用于暂时存储事务日志数据的重要组件,它在提高数据库性能、减少磁盘 I/O 开销以及支持事务的持久性方面发挥着关键作用。- 日志缓冲区用于临时存储正在进行的事务操作的相关信息,包括重做日志(Redo Log)中的条目。- 日志缓冲区的存在可以提高数据库的性能,因为它允许多个事务的日志条目在内存中缓冲,而不是立即写入到磁盘。

简单3步:微信语音转成mp3-程序员宅基地

文章浏览阅读1.7w次。ACM亚洲区域赛获奖者北漂女程序员 喜欢就关注我吧!在家人眼里,我学的专业就是打杂的。前天我的嫂子问我怎么把微信聊天语音转换成mp3格式保存分享。咔咔咔,教程来了。1,把微信语音信息保存..._微信语音转换mp3

Android 11 selinux 权限设置_android sepolicy private public-程序员宅基地

文章浏览阅读1.4w次,点赞3次,收藏19次。快速阅读框架SELinux介绍看Android怎么写?如何确认是SELinux 约束引起?怎么抓取SELinux Log?修改之后,怎么快速验证?怎么从log中提取有效信息?重点介绍参考文档架构从上层到驱动层的调用流程,但是我们重点关注sContext:注:file_contexts //系统中所有file_contexts安全上下文seapp_contexts //app安全上下文property_contexts //属_android sepolicy private public

推荐文章

热门文章

相关标签