<C/C++数据结构>并查集及其常见面试题

一,并查集的基本概念

并查集是一种树型的数据结构,用于处理不相交集合的合并和查询问题,速度很快,有很多应用,其中kruskal算法最为广泛。


1,三个基本操作

1),makeSet():

初始化每一个元素都各自为一个独立的集合,可以让每个元素的最高祖先为自身(parent[x] = x)。


2),findSet(x):
找到元素x所处在集合的最高祖先,这也是判断两个元素x和y是否在同一个集合中的主要依据。并且在寻找x的最高祖先的时候沿途遇到的元素也将会找到其最高祖先(见后后面路劲压缩详解)。


3),unionSet(x, y):
将元素x和y所在的集合进行合并(最高祖先之间的变更,见下面,谁集合谁其实都一样),利用findSet()判断x和y所在的集合是否相同,如果不同,则要把其中一个元素所处在集合的最高祖先指向另一个元素所处在集合的最高祖先。


2,合并两个集合unionSet

用一张很流行的图来简单说一下。如下图所示:开始的时候有{c、h、b、e}和{f、d、g}两个集合,c和f分别为两个集合的最高祖先,经过合并之后,把c也指向了f,这样形成了一棵树。




3,路径压缩findSet

如果查找b的祖先,需要沿着b -> h -> c -> f这条路径一直向上爬,是一个O(n)的时间复杂度,这就引出了路径压缩优化。就是在findSet的时候利用递归顺便把经过的点的祖先直接修改成最高祖先。比如下图执行过findSet(a)之后就可以将b和c的祖先修改成d,下次再查找的时候就不用慢慢向上爬了。

 


4,代码实现

下面给出C++的一个UFSet的一个类的具体代码。这里的代码没有考虑集合之间的大小关系,就是直接合并集合,随着问题的讨论的深入会逐渐添加内容,使之丰富


class UFSet
{
public:
	UFSet(int nsize)
	{
		size = nsize;
		parent = new int[size];
	};
	~UFSet()
	{
		delete[] parent;
		parent = NULL;
	};
	void makeSet(int n);初始化每个元素的祖先为自身
	int findSet(int x);//找到元素x的祖先元素
	void unionSet(int a, int b);//若两个元素的祖先不同,则将x元素的祖先设置为y元素的祖先
	int getSets(int n);//获取集合数量
private:
	int *parent;//存放祖先节点,例如x=parent[i],元素i的祖先节点为元素x
	int size;
};

void UFSet::makeSet(int n) //初始化
{
	//初始化每一个元素都各自为一个独立的集合,其祖先均设定为自身
	for (size_t i = 1; i <= n; i++)
		parent[i] = i;
}

int UFSet::findSet(int x)
{
	//找到元素所在的集合,也就是找到自己的最高的祖先,
	//这也是判断两个元素是否在同一个集合中的主要依据。
	if (parent[x] == x)
		return x;
	
	parent[x] = findSet(parent[x]);//找到祖先,而不是父节点
	return parent[x];
}

void UFSet::unionSet(int x, int y)
{
	//将x和y所在的集合进行合并,利用findSet()判断x和y所在的集合是否相同,
	//如果不同,则要把其中一个元素的祖先指向另一个元素的祖先。
	int ux = findSet(x);//获取节点x的祖先
	int uy = findSet(y);
	if (ux != uy)
		parent[ux] = uy;
}


二,并查集练习

练习1:朋友圈

题目描述:

假如已知有n个人和m对好友关系(存于数字r)。如果两个人是直接或间接的好友(好友的好友的好友...),则认为他们属于同一个朋友圈,请写程序求出这n个人里一共有多少个朋友圈。
假如:n = 5 , m = 3 , r = {{1 , 2} , {2 , 3} , {4 , 5}},表示有5个人,1和2是好友,2和3是好友,4和5是好友,则1、2、3属于一个朋友圈,4、5属于另一个朋友圈,结果为2个朋友圈。

输入:

输入包含多个测试用例,每个测试用例的第一行包含两个正整数 n、m,1=<n,m<=100000。接下来有m行,每行分别输入两个人的编号f,t(1=<f,t<=n),表示f和t是好友。 当n为0时,输入结束,该用例不被处理。

输出:

对应每个测试用例,输出在这n个人里一共有多少个朋友圈。

样例输入:
5 3
1 2
2 3
4 5
3 3
1 2
1 3
2 3
0
样例输出:
2
1

#include "vector"  
#include "string"  
#include "algorithm"  
#include <iostream>  
#include "stack"  
#include "map"
#include <cmath>  

using namespace std;

#define MAX 100

class UFSet
{
public:
	UFSet()
	{
		parent = new int[MAX];
	};
	~UFSet()
	{
		delete[] parent;
		parent = NULL;
	};
	void makeSet(int n);初始化每个元素的祖先  
	int findSet(int x);//找到元素x的祖先元素  
	void unionSet(int a, int b);//若两个元素的祖先不同,则将x元素的祖先设置为y元素的祖先  
	int getSets(int n);//获取集合数量  
private:
	int *parent;//存放祖先节点,例如x=parent[i],元素i的祖先节点为元素x  
};

void UFSet::makeSet(int n) //初始化  
{
	//初始化每一个元素都各自为一个独立的集合,其祖先均设定为自身  
	for (size_t i = 1; i <= n; i++)
		parent[i] = i;
}

int UFSet::findSet(int x)
{
	//找到元素所在的集合,也就是找到自己的最高的祖先,  
	//这也是判断两个元素是否在同一个集合中的主要依据。  
	if (parent[x] == x)//递归截止条件(最高祖先的祖先是其自身)  
		return x;

	parent[x] = findSet(parent[x]);//递归,最终找到x的最高祖先,并且沿途找到所有的最高祖先  
	return parent[x];
}

void UFSet::unionSet(int x, int y)
{
	//将x和y所在的集合进行合并,利用findSet()判断x和y所在的集合是否相同,  
	//如果不同,则要把其中一个元素的祖先指向另一个元素的祖先。  
	int ux = findSet(x);//获取节点x的祖先  
	int uy = findSet(y);
	if (ux != uy)  parent[ux] = uy;
}
int UFSet::getSets(int n)
{
	int count = 0;
	for (int i = 1; i <= n; i++)
	{//如果存在某一个节点的祖先是自身说明他是孤立的或者本身就是祖先  
     //其他一切情况说明在某一个集合中,只有最高祖先才能拥有以自己为祖先的能力
		if (parent[i] == i)
			count++;
	}
	return count;
}

int main()
{
	int m, n;
	while (cin >> n )
	{
		if (n == 0) break;
		cin >> m;
		if (m >= MAX) break;
		UFSet uset;
		uset.makeSet(n);//初始化  
		//接收m对关系  
		int x = 0;
		int	y = 0;
		for (int i = 0; i<m; i++)
		{
			cin >> x >> y;//注:这里数组下标代表人的对应编号  
			uset.unionSet(x, y);
		}
		cout << uset.getSets(n) << endl;

	}
	return 0;
}



练习2,畅通工程

题目描述:

    某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?

输入:

    测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。 
    注意:两个城市之间可以有多条道路相通,也就是说
    3 3
    1 2
    1 2
    2 1
    这种输入也是合法的
    当N为0时,输入结束,该用例不被处理。

输出:

    对每个测试用例,在1行里输出最少还需要建设的道路数目。

样例输入:
4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0
样例输出:
1
0
2
998

#include "vector"
#include "string"
#include "algorithm"
#include <iostream>
#include "stack"
#include <cmath>
#include <set>

using namespace std;


class UFSet
{
public:
	UFSet(int nsize)
	{
		size = nsize;
		parent = new int[size];
	};
	~UFSet()
	{
		delete[] parent;
		parent = NULL;
	};
	void makeSet(int n);初始化每个元素的祖先为自身
	int findSet(int x);//找到元素x的祖先元素parent[x]
	void unionSet(int a, int b);//若两个元素的祖先不同,则将x元素的祖先设置为y元素的祖先
	int getSets(int n);//获取独立的集合数量
private:
	int *parent;//存放祖先节点,例如x=parent[i],元素i的祖先节点为元素x
	int size;
};

void UFSet::makeSet(int n) //初始化
{
	//初始化每一个元素都各自为一个独立的集合,其祖先均设定为自身
	for (size_t i = 1; i <= n; i++)
		parent[i] = i;
}

int UFSet::findSet(int x)
{
	//找到元素所在的集合,也就是找到自己的最高的祖先,
	//这也是判断两个元素是否在同一个集合中的主要依据。
	if (parent[x] == x)//递归截止条件(最高祖先的祖先是其自身)
		return x;

	parent[x] = findSet(parent[x]);//递归,最终找到x的最高祖先,并且沿途找到所有的最高祖先
	return parent[x];
}

void UFSet::unionSet(int x, int y)
{
	//将x和y所在的集合进行合并,利用findSet()判断x和y所在的集合是否相同,
	//如果不同,则要把其中一个元素的祖先指向另一个元素的祖先。
	int ux = findSet(x);//获取节点x的祖先
	int uy = findSet(y);
	if (ux != uy)
		parent[ux] = uy;
}


int UFSet::getSets(int n)
{
	int count = 0;
	for (int i = 1; i <= n; i++)
	{//如果存在某一个节点的祖先是自身说明他是孤立的
		if (parent[i] == i)
			count++;
	}
	return count;
}

int main()
{
	int m, n;
	while (cin >> n >> m)
	{
		UFSet uset(100);
		uset.makeSet(n);//初始化
		//接收m对已经联通的城镇
		int x = 0, y = 0;
		for (int i = 0; i<m; i++)
		{
			cin >> x >> y;//注:这里数组下标位置代表城镇编号
			uset.unionSet(x, y);
		}
		cout << uset.getSets(n)-1 << endl;//两个独立的几何只需要一条路

	}
	return 0;
}


练习3,还是畅通工程

题目描述:
    某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小。请计算最小的公路总长度。
输入:

    测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( < 100 );随后的N(N-1)/2行对应村庄间的距离,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间的距离。为简单起见,村庄从1到N编号。
    当N为0时,输入结束,该用例不被处理。

输出:

    对每个测试用例,在1行里输出最小的公路总长度。

样例输入:
3
1 2 1
1 3 2
2 3 4
4
1 2 1
1 3 4
1 4 1
2 3 3
2 4 2
3 4 5
0
样例输出:
3
5

#include "vector"
#include "string"
#include "algorithm"
#include <iostream>
#include "stack"
#include <cmath>
#include <set>

using namespace std;



class Edge
{
public:
	int acity;//城市a  
	int bcity;//城市b  
	int cost;  //建成a到b的路的花费  
	bool operator < (const Edge &q) const//注意返回值的类型,运算符重载。  
	{
		return cost<q.cost;
	}
};

Edge edge[10000];

class UFSet
{
public:
	UFSet(int nsize)
	{
		size = nsize;
		parent = new int[size + 1];
	};
	~UFSet()
	{
		delete[] parent;
		parent = NULL;
	};
	
	// 初始化每个元素的祖先 
	void makeSet(int n);   

	// 找到元素x的祖先元素 
	int findSet(int x);   

	// 获取最小花费  
	int getMinCost(int m); 
private:
	int *parent;//存放祖先节点,例如x=parent[i],元素i的祖先节点为元素x    
	int size;
};

void UFSet::makeSet(int n) //初始化    
{
	//初始化每一个元素都各自为一个独立的集合,其祖先均设定为自身    
	for (size_t i = 1; i <= n; i++)
		parent[i] = i;
}

int UFSet::findSet(int x)
{
	//找到元素所在的集合,也就是找到自己的最高的祖先,    
	//这也是判断两个元素是否在同一个集合中的主要依据。    
	if (parent[x] == x)//递归截止条件(最高祖先的祖先是其自身)  
		return x;

	parent[x] = findSet(parent[x]);//递归,最终找到x的最高祖先,并且沿途找到所有的最高祖先    
	return parent[x];
}


int UFSet::getMinCost(int m)
{
	sort(edge, edge + m);//必须先对边排序(根据边的修建费用),这样才能贪心的形成最小花费  
	int sum = 0;
	for (int i = 0; i<m; i++)
	{
		int baseA = findSet(edge[i].acity);//找到城市a的祖先(要么是自身要么是城市b的编号)  
		int baseB = findSet(edge[i].bcity);
		if (baseA != baseB)
		{
			parent[baseA] = baseB;//将城市a的祖先设置成b的祖先这个式子等价于parent[edge[i].acity] = edge[i].bcity  
			sum += edge[i].cost;
		}
	}
	return sum;
}

int main()
{
	int n = 0;
	while (cin >> n, n > 0)
	{
		int m = n*(n - 1) / 2;

		UFSet uset(100);
		uset.makeSet(n);//初始化每个城市的祖先为自身  

		for (int i = 0; i < m; i++)
			cin >> edge[i].acity >> edge[i].bcity >> edge[i].cost;

		int mincost = uset.getMinCost(m);
		cout << mincost << endl;
	}

	return 0;
}

/**************************************************************
	Problem: 1017
	User: EbowTang
	Language: C++
	Result: Accepted
	Time:30 ms
	Memory:1636 kb
****************************************************************/





练习4,继续畅通工程

题目描述:
    省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可)。现得到城镇道路统计表,表中列出了任意两城镇间修建道路的费用,以及该道路是否已经修通的状态。现请你编写程序,计算出全省畅通需要的最低成本。
输入:
    测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( 1< N < 100 );随后的 N(N-1)/2 行对应村庄间道路的成本及修建状态,每行给4个正整数,分别是两个村庄的编号(从1编号到N),此两村庄间道路的成本,以及修建状态:1表示已建,0表示未建。

    当N为0时输入结束。
输出:
    每个测试用例的输出占一行,输出全省畅通需要的最低成本。
样例输入:
3
1 2 1 0
1 3 2 0
2 3 4 0
3
1 2 1 0
1 3 2 0
2 3 4 1
3
1 2 1 0
1 3 2 1
2 3 4 1
0
样例输出:
3
1
0


#include "algorithm"
#include <iostream>
#include "stack"
#include <cmath>
#include <set>

using namespace std;

class Edge
{
public:
	int acity;//城市a
	int bcity;//城市b
	int cost;  //建成a到b的路的花费
	bool isBuild; //标记路是否建成
	bool operator < (const Edge &q) const//注意返回值的类型,运算符重载。
	{  
		return cost<q.cost;
	}
};

Edge edge[100];

class UFSet
{
public:
	UFSet(int nsize)
	{
		size = nsize;
		parent = new int[size+1];
	};
	~UFSet()
	{
		delete[] parent;
		parent = NULL;
	};
	void makeSet(int n);初始化每个元素的祖先  
	int findSet(int x);//找到元素x的祖先元素  
	void unionSet(int a, int b);//若两个元素的祖先不同,则将x元素的祖先设置为y元素的祖先  
	int getMinCost(int m);//获取最小花费 
private:
	int *parent;//存放祖先节点,例如x=parent[i],元素i的祖先节点为元素x  
	int size;
};

void UFSet::makeSet(int n) //初始化  
{
	//初始化每一个元素都各自为一个独立的集合,其祖先均设定为自身  
	for (size_t i = 1; i <= n; i++)
		parent[i] = i;
}

int UFSet::findSet(int x)
{
	//找到元素所在的集合,也就是找到自己的最高的祖先,  
	//这也是判断两个元素是否在同一个集合中的主要依据。  
	if (parent[x] == x)//递归截止条件(最高祖先的祖先是其自身)
		return x;

	parent[x] = findSet(parent[x]);//递归,最终找到x的最高祖先,并且沿途找到所有的最高祖先  
	return parent[x];
}

void UFSet::unionSet(int x, int y)
{
	//将x和y所在的集合进行合并,利用findSet()判断x和y所在的集合是否相同,  
	//如果不同,则要把其中一个元素的祖先指向另一个元素的祖先。  
	int ux = findSet(x);//获取节点x的祖先  
	int uy = findSet(y);
	if (ux != uy)
		parent[ux] = uy;
}
int UFSet::getMinCost(int m)
{
	sort(edge, edge + m);//必须先对边排序(根据边的修建费用),这样才能贪心的形成最小花费
	int sum = 0;
	for (int i = 0; i<m; i++)
	{
		int baseA = findSet(edge[i].acity);//找到城市a的祖先(要么是自身要么是城市b的编号)
		int baseB = findSet(edge[i].bcity);
		if (baseA != baseB)
		{
			parent[baseA] = baseB;//将城市a的祖先设置成b的祖先这个式子等价于parent[edge[i].acity] = edge[i].bcity
			sum += edge[i].cost;
		}
	}
	return sum;
}

int main()
{
	int n = 0;
	while (cin >> n, n > 0)
	{
		int m = n*(n - 1) / 2;

		UFSet uset(100);
		uset.makeSet(n);//初始化每个城市的祖先为自身
		for (int i = 0; i < m; i++)
		{
			cin>> edge[i].acity>> edge[i].bcity>> edge[i].cost>> edge[i].isBuild;
			if (edge[i].isBuild == 1)
				uset.unionSet(edge[i].acity, edge[i].bcity);//将已经建成的两个城市编号建立连接
		}
		int mincost = uset.getMinCost(m);
		cout << mincost << endl;
	}

	return 0;
}


练习5,Battle Over Cities

题目描述:

It is vitally important to have all the cities connected by highways in a war. If a city is occupied by the enemy, all the highways from/toward that city are closed. We must know immediately if we need to repair any other highways to keep the rest of the cities connected. Given the map of cities which have all the remaining highways marked, you are supposed to tell the number of highways need to be repaired, quickly.

For example, if we have 3 cities and 2 highways connecting city1-city2 and city1-city3. Then if city1 is occupied by the enemy, we must have 1 highway repaired, that is the highway city2-city3.

输入:

Each input file contains one test case. Each case starts with a line containing 3 numbers N (<1000), M and K, which are the total number of cities, the number of remaining highways, and the number of cities to be checked, respectively. Then M lines follow, each describes a highway by 2 integers, which are the numbers of the cities the highway connects. The cities are numbered from 1 to N. Finally there is a line containing K numbers, which represent the cities we concern.

输出:

For each of the K cities, output in a line the number of highways need to be repaired if that city is lost.

样例输入:
3 2 3
1 2
1 3
1 2 3
样例输出:
1
0
0
#include "vector"
#include "string"
#include "algorithm"
#include <iostream>
#include "stack"
#include <cmath>
#include <set>
 
using namespace std;
 
 
class UFSet
{
public:
    UFSet(int nsize)
    {
        size = nsize;
        parent = new int[size];
    };
    ~UFSet()
    {
        delete[] parent;
        parent = NULL;
    };
    void makeSet(int n);初始化每个元素的祖先为自身  
    int findSet(int x);//找到元素x的祖先元素parent[x]  
    void unionSet(int a, int b);//若两个元素的祖先不同,则将x元素的祖先设置为y元素的祖先  
    int getSets(int n);//获取独立的集合数量  
private:
    int *parent;//存放祖先节点,例如x=parent[i],元素i的祖先节点为元素x  
    int size;
};
 
void UFSet::makeSet(int n) 
{ 
    for (size_t i = 1; i <= n; i++)
        parent[i] = i;
}
 
int UFSet::findSet(int x)
{
     
    if (parent[x] == x)  
        return x;
 
    parent[x] = findSet(parent[x]);
    return parent[x];
}
 
void UFSet::unionSet(int x, int y)
{
    int ux = findSet(x);
    int uy = findSet(y);
    if (ux != uy)
        parent[ux] = uy;
}
 
 
int UFSet::getSets(int n)
{
    int count = 0;
    for (int i = 1; i <= n; i++)
    {//如果存在某一个节点的祖先是自身说明他是孤立的  
        if (parent[i] == i)
            count++;
    }
    return count;
}
 
int main()
{
    int N = 0, M = 0, K = 0;
    while (cin >> N >> M >> K)
    {
        UFSet uset(10000);
        //接收m对已经联通的城市
        vector<int> xvec(M,0), yvec(M,0);
 
        for (int i = 0; i<M; i++)
            cin >> xvec[i] >> yvec[i];
         
        vector<int> vec(K,0);
 
        for (int i = 0; i < K; i++)
            cin >> vec[i];
         
        for (int i = 0; i < K; i++)
        {
            uset.makeSet(N);//初始化  
            for (int j = 0; j < M; j++)
            {
                if (vec[i] != xvec[j] && vec[i] != yvec[j])//如果这个城市被占领,就不要建立连接了。模拟切断
                    uset.unionSet(xvec[j], yvec[j]);
            }
            int highways = uset.getSets(N) - 2;//为什么是2?去除被占城市,并且两个独立的集合修一条路
            cout << highways  << endl;
        }
    }
    return 0;
}
/**************************************************************
    Problem: 1325
    User: EbowTang
    Language: C++
    Result: Accepted
    Time:490 ms
    Memory:5416 kb
****************************************************************/


练习6,孤岛连通工程

题目描述:

现在有孤岛n个,孤岛从1开始标序一直到n,有道路m条(道路是双向的,如果有多条道路连通岛屿i,j则选择最短的那条),请你求出能够让所有孤岛都连通的最小道路总长度。

输入:

数据有多组输入。
每组第一行输入n(1<=n<=1000),m(0<=m<=10000)。
接着m行,每行输入一条道路i j d(0<=d<=1000),(i,j表示岛屿序号,d表示道路长度)。

输出:

对每组输入输出一行,如果能连通,输出能连通所有岛屿的最小道路长度,否则请输出字符串"no"。

样例输入:
3 5
1 2 2
1 2 1
2 3 5
1 3 3
3 1 2
4 2
1 2 3
3 4 1
样例输出:
3
no

#include <cstdio>  
#include <ctype.h>
#include <cstdlib>  
#include "queue"
#include "vector"
#include "string"
#include "algorithm"
#include <iostream>
#include "stack"
#include <cmath>
#include <set>


using namespace std;


class Edge
{
public:
	Edge()
	{
		dst = 1000000;
	}
	int acity;//岛屿a    
	int bcity;//岛屿b    
	int dst;  //岛屿a到b的距离    
	
};

Edge edge[100];

bool cmp(Edge mode1, Edge mode2)
{
	return mode1.dst < mode2.dst;
}


class UFSet
{
public:
	UFSet(int nsize)
	{
		size = nsize;
		parent = new int[size + 1];
	}
	~UFSet()
	{
		delete[] parent;
		parent = NULL;
	}

	// 初始化每个元素的祖先   为自身
	void makeSet(int n);

	// 找到元素x的祖先元素   
	int findSet(int x);

	// 获取最小生成数路劲
	void getMinWay(int m, int n);
private:
	int *parent;//存放祖先节点,例如x=parent[i],元素i的祖先节点为元素x      
	int size;
};

void UFSet::makeSet(int n) //初始化      
{   
	for (size_t i = 1; i <= n; i++)
		parent[i] = i;
}

int UFSet::findSet(int x)
{
	    
	if (parent[x] == x)   
		return x;

	parent[x] = findSet(parent[x]);     
	return parent[x];
}

void UFSet::getMinWay(int m,int n)
{
	sort(edge+1, edge + m+1,cmp);//必须先对边排序(根据边的修建费用),这样才能贪心的形成最小花费    
	int sum = 0;
	int ct = 0;
	for (int i = 1; i<=m; i++)
	{
		int baseA = findSet(edge[i].acity);//找到城市a的祖先  
		int baseB = findSet(edge[i].bcity);
		if (baseA != baseB)
		{
			ct++;
			parent[baseA] = baseB;
			sum += edge[i].dst;
		}
	}
	if (ct == n - 1) 
		printf("%d\n", sum);
	else 
		printf("no\n");

}

int main()
{
	int n = 0;//岛屿数目
	int m = 0;//道路数目
	while (cin >> n >> m)
	{
		UFSet uset(n);
		uset.makeSet(n);//初始化每个城市的祖先为自身    
		int a = 0, b = 0, ndst = 0;

		for (int i = 1; i <= m; i++)
		{	//cin >> edge[i].acity >> edge[i].bcity >> edge[i].dst;
			scanf("%d%d%d", &edge[i].acity, &edge[i].bcity, &edge[i].dst);
			//麻痹,不是说好多条边联通两个岛屿时,选择最小的边么?麻痹!
			//不应该这样么:
			/*
			int a = 0, b = 0, ndst = 0;
			for (int i = 1; i <= m; i++)
			{
				cin >> a >> b >> ndst;
				edge[i].acity = a;
				edge[i].bcity = b;
				if (edge[i].dst > ndst)
					edge[i].dst = ndst;
			}
			*/
		}
		uset.getMinWay(m, n);
	}

	return 0;
}


练习7,连通图

题目描述:

    给定一个无向图和其中的所有边,判断这个图是否所有顶点都是连通的。

输入:

    每组数据的第一行是两个整数 n 和 m(0<=n<=1000)。n 表示图的顶点数目,m 表示图中边的数目。如果 n 为 0 表示输入结束。随后有 m 行数据,每行有两个值 x 和 y(0<x, y <=n),表示顶点 x 和 y 相连,顶点的编号从 1 开始计算。输入不保证这些边是否重复。

输出:

    对于每组输入数据,如果所有顶点都是连通的,输出"YES",否则输出"NO"。

样例输入:
4 3
1 2
2 3
3 2
3 2
1 2
2 3
0 0
样例输出:
NO
YES

#include "vector"
#include "string"
#include "algorithm"
#include <iostream>
#include "stack"
#include <cmath>
#include <set>
 
 
using namespace std;
 
class UFSet
{
public:
    UFSet(int nsize)
    {
        parent = new int[nsize + 1];
    }
    ~UFSet()
    {
        delete[] parent;
        parent = NULL;
    }
 
    // 初始化每个顶点的祖先为自身
    void makeSet(int n);
 
    // 找到元素x的祖先顶点   
    int findSet(int x);
 
    void unionSet(int x, int y);
     
    int getSets(int n);
private:
    int *parent;//存放祖先顶点,例如x=parent[i],元素i的祖先顶点为元素x      
};
 
void UFSet::makeSet(int n) //初始化      
{
    for (size_t i = 1; i <= n; i++)
        parent[i] = i;
}
 
int UFSet::findSet(int x)
{
 
    if (parent[x] == x)
        return x;
 
    parent[x] = findSet(parent[x]);
    return parent[x];
}
 
void UFSet::unionSet(int x, int y)
{ 
    int ux = findSet(x);
    int uy = findSet(y);
    if (ux != uy)
        parent[ux] = uy;
}
 
int UFSet::getSets(int n)
{
    int count = 0;
    for (int i = 1; i <= n; i++)
    {//如果存在某一个顶点的祖先是自身说明他是孤立的或者本身就是祖先  
        if (parent[i] == i)
            count++;
    }
    return count;
}
 
int main()
{
    int n = 0, m = 0;
    while (cin >> n >> m,n>0)
    {
        UFSet uset(n);
        uset.makeSet(n);//初始化每个顶点(顶点编号为1到n)的祖先为自身  
        int avex = 0,bvex=0;
        for (int i = 1; i <= m; i++)
        {
            cin >> avex >> bvex;
            uset.unionSet(avex,bvex);
        }
 
        if (uset.getSets(n) == 1)
            cout << "YES" << endl;
        else
            cout << "NO" << endl;
    }
 
    return 0;
}
 
/**************************************************************
    Problem: 1109
    User: EbowTang
    Language: C++
    Result: Accepted
    Time:40 ms
    Memory:1520 kb
****************************************************************/


练习8,奇怪的连通图

题目描述:

已知一个无向带权图,求最小整数k。使仅使用权值小于等于k的边,节点1可以与节点n连通。

输入:

输入包含多组测试用例,每组测试用例的开头为一个整数n(1 <= n <= 10000),m(1 <= m <= 100000),代表该带权图的顶点个数,和边的个数。
接下去m行,描述图上边的信息,包括三个整数,a(1 <= a <= n),b(1 <= b <= n),c(1 <= c <= 1000000),表示连接顶点a和顶点b的无向边,其权值为c。

输出:

输出为一个整数k,若找不到一个整数满足条件,则输出-1。

样例输入:
3 3
1 3 5
1 2 3
2 3 2
3 2
1 2 3
2 3 5
3 1
1 2 3 
样例输出:
3
5
-1

#include <cstdio>    
#include <ctype.h>  
#include <cstdlib>    
#include "queue"  
#include "vector"  
#include "string"  
#include "algorithm"  
#include <iostream>  
#include "stack"  
#include <cmath>  
#include <set>  
 
 
using namespace std;
 
class Edge
{
public:
    Edge()
    {
        dst = 0;
    }
    int avex;
    int bvex;
    int dst;
    bool operator <(const Edge &mode) const
    {
        return dst<mode.dst;
    }
};
 
Edge edge[111000];
 
class UFSet
{
public:
    UFSet(int nsize)
    {
        parent = new int[nsize + 1];
    }
    ~UFSet()
    {
        delete[] parent;
        parent = NULL;
    }
 
    // 初始化每个顶点的祖先为自身  
    void makeSet(int n);
 
    // 找到元素x的祖先元素     
    int findSet(int x);
 
    void makeMST(int m, int n);
private:
    int *parent;//存放祖先节点,例如x=parent[i],元素i的祖先节点为元素x    
 
};
 
void UFSet::makeSet(int n) //初始化        
{
    for (size_t i = 1; i <= n; i++)
        parent[i] = i;
}
 
int UFSet::findSet(int x)
{
 
    if (parent[x] == x)
        return x;
 
    parent[x] = findSet(parent[x]);
    return parent[x];
}
 
 
void UFSet::makeMST(int m, int n)
{
    sort(edge + 1, edge + m + 1);//必须先对边排序(根据边的修建费用),这样才能贪心的形成最小花费      
 
    for (int i = 1; i <= m; i++)
    {
        int baseA = findSet(edge[i].avex);//找到集合中的最高祖先
        int baseB = findSet(edge[i].bvex);
 
        if (baseA != baseB)//两个顶点只要不在一个集合就可以采用这条边,并合并两个集合
            parent[baseA] = baseB;//合并两个最高祖先
 
        if (findSet(n) == findSet(1))
        {
            cout << edge[i].dst << endl;
            return;
        }
    }
    cout << "-1" << endl;
}
int main()
{
    int n = 0, m = 0;
    while (cin >> n >> m)
    {
        UFSet uset(n);
        uset.makeSet(n);//初始化每个城市的祖先为自身    
        for (int i = 1; i <= m; i++)
            scanf("%d%d%d", &edge[i].avex, &edge[i].bvex, &edge[i].dst);
        uset.makeMST(m, n);
    }
    return 0;
}
/**************************************************************
    Problem: 1545
    User: EbowTang
    Language: C++
    Result: Accepted
    Time:650 ms
    Memory:2820 kb
****************************************************************/




练习9,畅通工程

题目描述:
    省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可)。经过调查评估,得到的统计表中列出了有可能建设公路的若干条道路的成本。现请你编写程序,计算出全省畅通需要的最低成本。
输入:
    测试输入包含若干测试用例。每个测试用例的第1行给出评估的道路条数 N、村庄数目M (N, M < =100 );随后的 N 行对应村庄间道路的成本,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间道路的成本(也是正整数)。为简单起见,村庄从1到M编号。当N为0时,全部输入结束,相应的结果不要输出。
输出:
    对每个测试用例,在1行里输出全省畅通需要的最低成本。若统计数据不足以保证畅通,则输出“?”。
样例输入:
3 3
1 2 1
1 3 2
2 3 4
1 3
2 3 2
0 100
样例输出:
3
?

#include <cstdio>  
#include <ctype.h>
#include <cstdlib>  
#include "queue"
#include "vector"
#include "string"
#include "algorithm"
#include <iostream>
#include "stack"
#include <cmath>
#include <set>
 
 
using namespace std;
 
class Edge
{
public:
    Edge()
    {
        dst = 0;
    }
    int avex;
    int bvex;
    int dst;
    bool operator <(const Edge &mode) const
    {
        return dst<mode.dst;
    }
};
 
Edge edge[1001];
 
class UFSet
{
public:
    UFSet(int nsize)
    {
        parent = new int[nsize+1];
    }
    ~UFSet()
    {
        delete[] parent;
        parent = NULL;
    }
 
    // 初始化每个顶点的祖先为自身
    void makeSet(int n);
 
    // 找到元素x的祖先元素   
    int findSet(int x);
 
    int getMinCost(int m, int n);
 
    int getSets(int n);
private:
    int *parent;//存放祖先节点,例如x=parent[i],元素i的祖先节点为元素x  
     
};
 
void UFSet::makeSet(int n) //初始化      
{
    for (size_t i = 1; i <= n; i++)
        parent[i] = i;
}
 
int UFSet::findSet(int x)
{
 
    if (parent[x] == x)
        return x;
 
    parent[x] = findSet(parent[x]);
    return parent[x];
}
 
 
int UFSet::getMinCost(int nedge, int nvex)
{
    sort(edge + 1, edge + nedge + 1);//必须先对边排序(根据边的修建费用),这样才能贪心的形成最小花费    
    int minCost = 0;
    for (int i = 1; i <= nedge; i++)
    {
        int baseA = findSet(edge[i].avex);
        int baseB = findSet(edge[i].bvex);
        if (baseA != baseB)//在两个集合中,可以选择这条边
        {
            parent[baseA] = baseB;//联合两个顶点 
            minCost += edge[i].dst;                     
        }
    }
 
    if ((nedge + 1) >= nvex && getSets(nvex) == 1)
        cout << minCost << endl;
    else
        cout << "?" << endl;
 
    return minCost;
}
 
int UFSet::getSets(int nvex)
{
    int count = 0;
    for (int i = 1; i <= nvex; i++)
    {//如果存在某一个节点的祖先是自身说明他是孤立的  
        if (parent[i] == i)
            count++;
    }
    return count;
}
 
int main()
{
    int nedge = 0;//边数
    int nvex= 0;//顶点数
    while (cin >> nedge >> nvex)
    {
        if (nedge == 0)
            break;
        UFSet uset(nvex);
        uset.makeSet(nvex);//初始化每个城市的祖先为自身  
        for (int i = 1; i <= nedge; i++)
            scanf("%d%d%d",&edge[i].avex, &edge[i].bvex, &edge[i].dst);
         
        uset.getMinCost(nedge, nvex);
    }
    return 0;
}
/**************************************************************
    Problem: 1024
    User: EbowTang
    Language: C++
    Result: Accepted
    Time:10 ms
    Memory:1532 kb
****************************************************************/


练习10,欧拉回路

题目描述:
    欧拉回路是指不令笔离开纸面,可画过图中每条边仅一次,且可以回到起点的一条回路。现给定一个图,问是否存在欧拉回路?
输入:
    测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是节点数N ( 1 < N < 1000 )和边数M;随后的M行对应M条边,每行给出一对正整数,分别是该条边直接连通的两个节点的编号(节点从1到N编号)。当N为0时输入结束。
输出:
    每个测试用例的输出占一行,若欧拉回路存在则输出1,否则输出0。
样例输入:
3 3
1 2
1 3
2 3
3 2
1 2
2 3
0
样例输出:
1
0

#include "vector"  
#include <iostream>  
 
using namespace std;
 
 
class Edge
{
public:
    int aVex;//顶点a    
    int bVex;//顶点b    
};
 
Edge edge[100000];
 
class UFSet
{
public:
    UFSet(int nsize)
    {
        size = nsize;
        parent = new int[size+1];
    };
    ~UFSet()
    {
        delete[] parent;
        parent = NULL;
    };
    void makeSet(int n);初始化每个元素的祖先为自身  
    int findSet(int x);//找到元素x的祖先元素  
    void unionSet(int a, int b);//若两个元素的祖先不同,则将x元素的祖先设置为y元素的祖先  
    int getSets(int n);//获取集合数量  
private:
    int *parent;//存放祖先节点,例如x=parent[i],元素i的祖先节点为元素x  
    int size;
};
 
void UFSet::makeSet(int n) //初始化  
{
    //初始化每一个元素都各自为一个独立的集合,其祖先均设定为自身  
    for (size_t i = 1; i <= n; i++)
        parent[i] = i;
}
 
int UFSet::findSet(int x)
{
    //找到元素所在的集合,也就是找到自己的最高的祖先,  
    //这也是判断两个元素是否在同一个集合中的主要依据。  
    if (parent[x] == x)
        return x;
 
    parent[x] = findSet(parent[x]);//找到祖先,而不是父节点  
    return parent[x];
}
 
void UFSet::unionSet(int x, int y)
{
    //将x和y所在的集合进行合并,利用findSet()判断x和y所在的集合是否相同,  
    //如果不同,则要把其中一个元素的祖先指向另一个元素的祖先。  
    int ux = findSet(x);//获取节点x的祖先  
    int uy = findSet(y);
    if (ux != uy)
        parent[ux] = uy;
}
 
 
int UFSet::getSets(int n)//获取集合数量 
{
    int count = 0;
    for (int i = 1; i <= n; i++)
    {
        if (parent[i] == i)
            count++;
    }
    return count;
}
 
int main()
{
    int nVex = 0;
    int mEdge = 0;
    while (cin >> nVex >> mEdge,nVex>0)
    {
        UFSet uset(nVex);
        uset.makeSet(nVex);
        vector<int> vecdegree(nVex+1,0);
        for (int i = 0; i < mEdge; i++)
        {
            cin >> edge[i].aVex >> edge[i].bVex;
            vecdegree[edge[i].aVex]++;
            vecdegree[edge[i].bVex]++;
            uset.unionSet(edge[i].aVex,edge[i].bVex);
        }
        bool flag = true;
        for (int i = 1; i <= nVex; i++)
        {
            if (vecdegree[i] % 2 != 0)//每个度必须为偶数
            {
                flag = false;
                break;
            }
        }
 
        if (flag && uset.getSets(nVex) == 1)//必须只有一个集合,即所有点相互可达
            cout << "1" << endl;
        else
            cout << "0" << endl;
    }
    return 0;
}
/**************************************************************
    Problem: 1027
    User: EbowTang
    Language: C++
    Result: Accepted
    Time:130 ms
    Memory:2300 kb
****************************************************************/






参考资源:

【1】作者:Dong | 新浪微博:西成懂 | 可以转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明
网址:
本博客的文章集合:

【2】九度OJ,http://ac.jobdu.com/problem.php?pid=1526


  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值