#include
#include
#define maxn 10001
int S[maxn]; //S[i]中,负数表示是根结点,正数表示父结点
int connected;
int myFind(int x)
{
int parent = x;
while((parent=S[x]) > 0)
{
x = parent;
}
return x;
}
void myUnion(int x1, int x2)
{
int root1 = myFind(x1);
int root2 = myFind(x2);
if(root1 != root2)
{
S[root2] = root1;
connected--;
}
}
int main()
{
//freopen("set.txt","r",stdin);
int N;
char op;
int num1,num2;
int root1,root2;
scanf("%d",&N);
memset(S,-1,sizeof(int)*N);
connected = N;
while(1)
{
scanf("%c", &op);
if(op == 'S') break;
else if(op == 'I'){
scanf("%d%d",&num1,&num2);
myUnion(num1,num2);
}else if(op == 'C'){
scanf("%d%d",&num1,&num2);
root1 = myFind(num1);
root2 = myFind(num2);
if(root1 == root2) printf("yes\n");
else printf("no\n");
}
}
if(connected == 1){
printf("The network is connected.\n");
}else{
printf("There are %d components.\n",connected);
}
return 0;
}
程序基本就是这样子的,提交之后是部分正确,因为没有对归并(并集)操作进行优化,会超时。
我们具体来看一下并集操作
void myUnion(int x1, int x2)
{
int root1 = myFind(x1);
int root2 = myFind(x2);
if(root1 != root2)
{
S[root2] = root1;
connected--;
}
}
我们每次归并都是将 第二个集合 贴到 第一个集合上面,如果每次都是正好将 高度很高的集合 贴到了 高度比较低的集合上,那么集合树的高度一直在增加,甚至可能高度就是N,所以每次myFind操作都需要遍历很高的树,对N次归并,时间复杂度是O(N^2)。N是10的4次方数量级,平方之后显然会超时。
如何进行优化呢?
我们首先可以想到可以将矮树贴到高树上面去。或者将规模小的树贴到规模大的树。
那么如何保存树的高度或规模呢?我们之前,S[root]中保存的都是-1,负数就表示是根结点,那么我们可以用这个负数来保存树高或规模。
根据树高进行归并伪代码:
//S[i] 都初始化为 -1
if(root1高度 > root2高度)
S[root2] = root1;
else{
if(root2高度 == root1高度) 树高++
S[root1] = root2;
}
根据树高进行归并具体代码:
if(S[root1] < S[root2]){//1树高
S[root2] = root1;
}else{
if(S[root1] == S[root2]) S[root2]--; //树高加一
S[root1] = root2;
}
同理很容易得出根据树的规模进行归并的代码:
if(S[root1] < S[root2]){//1规模大
S[root1] += S[root2]; //更新规模
S[root2] = root1;
}else{
S[root2] += S[root1];
S[root1] = root2;
}
这两种方式都属于 按秩归并,按秩归并最坏情况下的树高是O(logN),那么N次归并,时间复杂度就是O(N*logN)
除了这样按秩归并的优化外,我们还可以压缩路径。
这样使得查找更快,因为压缩路径之后树高明显降低了。
具体代码:
//路径压缩
int myFind(int x)
{
if(S[x] < 0) return x;
return S[x] = myFind(S[x]);
}
是一种尾递归的方式来写的,虽然压缩路径的时候会稍微花一些时间,但是之后的查找速度快了很多。
数学可以证明,无论N有多大(int范围内),树高不会超过4。那么N次归并,时间复杂度就是O(N*一个常数),相当于O(N)。不过这道题不进行路径压缩也可以通过。
#include
#include
#define maxn 10001
int S[maxn]; //S[i]中,负数表示高度,即S[root]=-树高,正数表示父结点
int connected;
int myFind(int x)
{
int parent = x;
while((parent=S[x]) > 0)
{
x = parent;
}
return x;
}
void myUnion(int x1, int x2)
{
int root1 = myFind(x1);
int root2 = myFind(x2);
if(root1 != root2)
{
if(S[root1] < S[root2]){//1树高
S[root2] = root1;
}else{
if(S[root1] == S[root2]) S[root2]--; //树高加一
S[root1] = root2;
}
connected--;
}
}
int main()
{
//freopen("set.txt","r",stdin);
int N;
char op;
int num1,num2;
int root1,root2;
scanf("%d",&N);
memset(S,-1,sizeof(int)*N);
connected = N;
while(1)
{
scanf("%c", &op);
if(op == 'S') break;
else if(op == 'I'){
scanf("%d%d",&num1,&num2);
myUnion(num1,num2);
}else if(op == 'C'){
scanf("%d%d",&num1,&num2);
root1 = myFind(num1);
root2 = myFind(num2);
if(root1 == root2) printf("yes\n");
else printf("no\n");
}
}
if(connected == 1){
printf("The network is connected.\n");
}else{
printf("There are %d components.\n",connected);
}
return 0;
}
#include
#include
#define maxn 10001
int S[maxn]; //S[i]中,负数表示高度,即S[root]=-规模,正数表示父节点
int connected;
//路径压缩
int myFind(int x)
{
if(S[x] < 0) return x;
return S[x] = myFind(S[x]);
}
void myUnion(int x1, int x2)
{
int root1 = myFind(x1);
int root2 = myFind(x2);
if(root1 != root2)
{
if(S[root1] < S[root2]){//1规模大
S[root1] += S[root2]; //更新规模
S[root2] = root1;
}else{
S[root2] += S[root1];
S[root1] = root2;
}
connected--;
}
}
int main()
{
//freopen("set.txt","r",stdin);
int N;
char op;
int num1,num2;
int root1,root2;
scanf("%d",&N);
memset(S,-1,sizeof(int)*N);
connected = N;
while(1)
{
scanf("%c", &op);
if(op == 'S') break;
else if(op == 'I'){
scanf("%d%d",&num1,&num2);
myUnion(num1,num2);
}else if(op == 'C'){
scanf("%d%d",&num1,&num2);
root1 = myFind(num1);
root2 = myFind(num2);
if(root1 == root2) printf("yes\n");
else printf("no\n");
}
}
if(connected == 1){
printf("The network is connected.\n");
}else{
printf("There are %d components.\n",connected);
}
return 0;
}
一般来说,按规模归并与压缩路径配合使用更方便,想一想为什么?