牛客並查集 先練練手,因爲有測試數據
鏈接:https://www.nowcoder.com/questionTerminal/e7ed657974934a30b2010046536a5372
來源:牛客網
給定一個沒有重複值的整形數組arr,初始時認爲arr中每一個數各自都是一個單獨的集合。請設計一種叫UnionFind的結構,並提供以下兩個操作。
boolean isSameSet(int a, int b): 查詢a和b這兩個數是否屬於一個集合
void union(int a, int b): 把a所在的集合與b所在的集合合併在一起,原本兩個集合各自的元素以後都算作同一個集合
[要求]
如果調用isSameSet和union的總次數逼近或超過O(N),請做到單次調用isSameSet或union方法的平均時間複雜度爲O(1)
輸入描述:
第一行兩個整數N, M。分別表示數組大小、操作次數
接下來M行,每行有一個整數opt
若opt = 1,後面有兩個數x, y,表示查詢(x, y)這兩個數是否屬於同一個集合
若opt = 2,後面有兩個數x, y,表示把x, y所在的集合合併在一起
輸出描述:
對於每個opt = 1的操作,若爲真則輸出"Yes",否則輸出"No"
示例1
輸入
4 5
1 1 2
2 2 3
2 1 3
1 1 1
1 2 3
輸出
No
Yes
Yes
說明
每次2操作後的集合爲
({1}, {2}, {3}, {4})
({1}, {2, 3}, {4})
({1, 2, 3}, {4})
備註:
牛客並查集最初思路
並查集之前的實現也都是兩個函數,一個查找結合頭領的函數,一個合併集合的函數。
這裏N是在1到10^6之間,所以直接聲明個全局數組,全部賦值爲-1。
int pre[1000001];// 1- N 不使用0
// 在main函數裏面 fill(pre,pre+1000001,-1); 全設置成-1
int findP(int a) //查找a所在集合的頭領
{
int pos=a;//保存a的值是爲了下面的縮短路徑
while(pre[a]!=-1) //這兩行很容易考慮到,每一次找上一級,直到集合的頭領,sn[頭領]=-1
a=pre[a];
//這是a保存的就是集合的頭領編號了
while(pre[pos]!=-1) //從原先a到 頭領之間的所有小頭目都需要把上一級直接修改爲頭領
{
int curP=pre[pos];//保存上一級編號
pre[pos]=a;//修改當前編號的上一級爲集合頭領
pos=curP;
}
return a;
}
void union2(int a,int b)
{
if(!isSameSet(a,b))
{
//a的父親是b的父親了
int ap=findP(a);
sn[ap]=findP(b);
findP(a);//這裏是因爲a所在的集合頭領變爲b所在集合的頭領了,所以a到原先頭領之間的小頭目都需要把首領改爲b集合的首領
}
}
總AC代碼
#include <iostream>
#include <string>
using namespace std;
#include<algorithm>
#include<queue>
int pre[1000001];// 1- N 不使用0
int findP(int a)
{
int pos=a;
while(pre[a]!=-1)
a=pre[a];
while(pre[pos]!=-1)
{
int curP=pre[pos];
pre[pos]=a;
pos=curP;
}
return a;
}
bool isSameSet(int a, int b)
{
int ap=findP(a);
int bp=findP(b);
if(ap!=bp)
{
return false;
}
else
return true;
}
void union2(int a,int b)
{
if(!isSameSet(a,b))
{
//a的父親是b的父親了
int ap=findP(a);
pre[ap]=findP(b);
findP(a);
}
}
int main()
{
fill(pre,pre+1000001,-1);
int M,N;
cin>>N>>M;
for(int i=0;i<M;i++){
int op;
int a,b;
scanf("%d %d %d",&op,&a,&b);
if(op==1){
if(isSameSet(a,b)==1)
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
}
else{
union2(a,b);
}
}
return 0;
}
牛客並查集時間改進(可以直接看這個)
之後在牛客的測試發現,上述的代碼的時間有時候能AC有時候不能AC 小無奈,優化了一下。
並查集之前的實現也都是兩個函數,一個查找結合頭領的函數,一個合併集合的函數。
這裏N是在1到10^6之間,所以直接聲明個全局數組pre,pre[i] 保存編號i的頭領,默認自己是自己的頭領。
主要改進在findP函數,因爲自己是自己的首領了,直接遞歸調用搞定,很簡單,而且時間效率更高(這其實我有點不太理解,因爲我感覺遞歸時間效率並不高)
#include <iostream>
#include <string>
using namespace std;
#include<algorithm>
#include<queue>
int pre[1000001];// 1- N 不使用0
int findP(int x)
{
if (x != pre[x]) pre[x] = findP(pre[x]);
return pre[x];
}
void union2(int a,int b)
{
//a的父親是b的父親了
int ap=findP(a);
int bp=findP(b);
pre[ap]=findP(b);
findP(a);
}
int main()
{
int M,N;
cin>>N>>M;
for(int i=1; i<=N; i++)
{
pre[i]=i;
}
for(int i=0; i<M; i++)
{
int op;
int a,b;
scanf("%d %d %d",&op,&a,&b);
if(op==1)
{
if(findP(a)==findP(b))
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
}
else
{
union2(a,b);
}
}
return 0;
}
PAT
1107 Social Clusters (30分)
When register on a social network, you are always asked to specify your hobbies in order to find some potential friends with the same hobbies. A social cluster is a set of people who have some of their hobbies in common. You are supposed to find all the clusters.
Input Specification:
Each input file contains one test case. For each test case, the first line contains a positive integer N (≤1000), the total number of people in a social network. Hence the people are numbered from 1 to N. Then N lines follow, each gives the hobby list of a person in the format:
K
i
: h
i
[1] h
i
[2] … h
i
[K
i
]
where K
i
(>0) is the number of hobbies, and h
i
[j] is the index of the j-th hobby, which is an integer in [1, 1000].
Output Specification:
For each case, print in one line the total number of clusters in the network. Then in the second line, print the numbers of people in the clusters in non-increasing order. The numbers must be separated by exactly one space, and there must be no extra space at the end of the line.
Sample Input:
8
3: 2 7 10
1: 4
2: 5 3
1: 4
1: 3
1: 4
4: 6 8 1 5
1: 4
Sample Output:
3
4 3 1
做了牛客那道題,再看這題其實只要 考慮一件事,如何把興趣一樣的人關聯起來,我是設置了一個 hobby[1001]={0} 然後只要hobby[興趣編號]==0 就把hobby[興趣編號]=人的編號 ,這樣每一個性趣編號我們就有了一個頭領了,然後之後的人的編號就直接用並查集的合併函數就OK了
然後輸出的時候我不知道怎麼想的 人家map的有序是指的key有序,不是value有序,以爲那裏思路不對,驗證了好久。。。。無奈
AC代碼
#include <iostream>
#include<map>
#include<vector>
#include<algorithm>
using namespace std;
int pre[1001];// 1- N 不使用0
int findP(int x)
{
if (x != pre[x]) pre[x] = findP(pre[x]);
return pre[x];
}
void union2(int a,int b)
{
//a的父親是b的父親了
int ap=findP(a);
int bp=findP(b);
pre[ap]=bp;
findP(a);//這一步其實用不用都行,因爲後面會吧所有人的編號在進行一次find 壓縮路徑
}
int main()
{
int N;
cin>>N;
//初始化並查集使用的首領數組
for(int i=1; i<=N; i++)
{
pre[i]=i;
}
int hobby[1001]= {0};
for(int i=1; i<=N; i++)
{
int Num;
scanf("%d: ",&Num);
while(Num>0)
{
Num--;
int get;
cin>>get;
if(hobby[get]==0)
hobby[get]=i;
else{
union2(hobby[get],i);
}
}
}
for(int i=1; i<=N; i++)
{
findP(i);
}
//輸出
map<int,int> mapp;
for(int i=1; i<=N; i++)
{
mapp[pre[i]]++;
}
cout<<mapp.size()<<endl;
vector<int> vec;
for(auto it=mapp.rbegin(); it!=mapp.rend(); it++)
{
vec.push_back(it->second);
}
sort(vec.begin(),vec.end());
for(int i=vec.size()-1;i>=0;i--){
if(i==vec.size()-1){
cout<<vec[i];
}
else
cout<<' '<<vec[i];
}
return 0;
}