#数据结构与算法学习笔记#PTA15:并查集+按秩归并+路径压缩(C/C++)

2018.5.4

昨天写完最小堆之后,一兴起看了一晚上的区块链,总算对炒得火热的区块链有了一些大致的了解。今天有点晚,明天会发一篇关于区块链的文章,欢迎各位与我交流探讨。

这一题说的是,一个网络中有N台主机,当输入I的时候,对某两台主机进行网络连接;当输入C的时候,对两个主机是否连接进行检查;当输入S的时候,对整个网络的连接状态进行检查,并结束任务。本题主要考察并查集的知识,题目本身不难。如果能够想到用数组作为存储结构,那这道题就特别简单明了了,每一个数组下标代表主机编号,数组元素代表该主机结点连接的父结点,由此可以形成一棵普通树结构连接的集合网络。判断两个主机是否连接,只要判断其所在集合根节点是否相同即可。

这里还需要注意的问题是时间复杂度。在连接每一对主机时,若只是简单地进行集合归并,那会变成一棵左右子树及其不平衡的的树,这种一边倾斜的树结构在每次查找根节点操作时的复杂度都为o(N),整体复杂度接近o(N^2),当集合元素大到一定程度时基本走不通。因此要考虑“按秩归并”和“路径压缩”。

本题笔者所依赖的秩序是“规模”,即每一集合的结点个数。实际操作时,当主机1所属集合根节点规模小于主机2的(规模依靠根节点数组元素值存储,如根节点值为-5,代表该集合规模为5个元素,注意比较时两者为负数),则将主机1对应结合连接到主机2根节点上(将规模小的集合根节点连接到规模大的集合根节点上)。这种按秩归并方法可以使得树高最大限度上减小,提高查找效率,复杂度为o(logN),加上路径压缩后复杂度甚至可以达到o(log*N)(表示logloglog……取多次),但这题数据量较小,logN与log*N两者差别不大。

“按秩归并”和“路径压缩”原理图:



本题要求:

We have a network of computers and a list of bi-directional connections. Each of these connections allows a file transfer from one computer to another. Is it possible to send a file from any computer on the network to any other?

Input Specification:

Each input file contains one test case. For each test case, the first line contains N (2≤N≤10^​4), the total number of computers in a network. Each computer in the network is then represented by a positive integer between 1 and N. Then in the following lines, the input is given in the format:

I c1 c2

where I stands for inputting a connection between c1 and c2; or

C c1 c2

where C stands for checking if it is possible to transfer files between c1 and c2; or

S

where S stands for stopping this case.

Output Specification:

For each C case, print in one line the word "yes" or "no" if it is possible or impossible to transfer files between c1 and c2, respectively. At the end of each case, print in one line "The network is connected." if there is a path between any pair of computers; or "There are k components." where k is the number of connected components in this network.



实现代码:

// FileTransfer.cpp: 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <vector>

using namespace std;

void Read(int num, vector<int>& network);
void Check(int com1, int com2, vector<int>& network);			//检查两结点是否连通
void Connect(int com1, int com2, vector<int>& network);			//连接两个元素所在集合
int FindRoot(int index, vector<int>& network);					//获得根节点下标
void CheckNet(int num, vector<int>& network);					//检查网络整体连接情况

int main()
{
	int num;
	cin >> num;

	vector<int> network(num, -1);
	Read(num, network);

	system("pause");
    return 0;
}

void Read(int num, vector<int>& network) {
	char order;
	bool flag = true;
	while (flag) {
		int com1;
		int com2;
		cin >> order;
		switch (order)
		{
		case 'C':
			cin >> com1 >> com2;
			Check(com1-1, com2-1, network);			//注意元素下标要-1
			break;
		case 'I':
			cin >> com1 >> com2;
			Connect(com1-1, com2-1, network);
			break;
		case 'S':
			CheckNet(num, network);
			flag = false;
			break;
		default:
			break;
		}
	}
}

void Check(int com1, int com2, vector<int>& network) {
	int r1 = FindRoot(com1, network);
	int r2 = FindRoot(com2, network);
	if (r1 == r2) {
		cout << "yes" << endl;
	}
	else {
		cout << "no" << endl;
	}
}

void Connect(int com1, int com2, vector<int>& network) {
	int r1 = FindRoot(com1, network);
	int r2 = FindRoot(com2, network);
	//当两个结点属于不同集合时进行连接,按秩归并(比规模)与路径压缩
	if (r1 != r2) {
		//当com1所属集合根节点权重小于com2的(注意两者为负数),将com1对应结合连接到com2根节点上
		if (network[r1] >= network[r2]){
			network[r2] += network[r1];
			network[r1] = r2;
		}
		//否则,将com2对应结合连接到com1根节点上
		else {
			network[r1] += network[r2];
			network[r2] = r1;
		}
	}
}

int FindRoot(int index, vector<int>& network) {
	while (index >= 0) {
		if (network[index] < 0)break;
		index = network[index];
	}
	return index;
}

void CheckNet(int num, vector<int>& network) {
	int total = 0;
	for (int i = 0; i < num; i++) {
		if (network[i] < 0) {
			total++;
		}
	}
	//当结点连接成闭环时,也算网络完全连接
	if (total == 1 || total == 0) {
		cout << "The network is connected." << endl;
	}
	else {
		cout << "There are " << total << " components." << endl;
	}
}

猜你喜欢

转载自blog.csdn.net/qq_20304723/article/details/80201269