数据结构-集合

一、并查集:

(洛谷P1551):例17-1:亲戚:

cpp 复制代码
#include <iostream>
using namespace std;
#define MAXN 5010
int n, m, p, x, y;
int fa[MAXN];
int find(int x) {
	if (x == fa[x]) return x;
	return fa[x] = find(fa[x]);//路径压缩优化
}
void join(int c1,int c2) {
	int f1 = find(c1), f2 = find(c2);
	if (f1 != f2) fa[f1] = f2;
}
int main() {
	cin >> n >> m >> p;
	for (int i = 1; i <= n; i++) fa[i] = i;
	for (int i = 0; i < m; i++) {
		cin >> x >> y;
		join(x, y);
	}
	for (int i = 1; i <= p; i++) {
		cin >> x >> y;
		if (find(x) == find(y)) {
			cout << "Yes" << endl;
		}
		else {
			cout << "No" << endl;
		}
	}
	return 0;
}

(洛谷P1536):例17-2:村村通:

cpp 复制代码
#include <iostream>
using namespace std;
#define MAXN 1010
int fa[MAXN];
int n,m;
int a, b;
#include <cstring>
#include <algorithm>
int tmp[MAXN];
int find(int x) {
	if (x == fa[x]) return x;
	return fa[x] = find(fa[x]);
}
void join(int c1, int c2) {
	int f1 = find(c1), f2 = find(c2);
	if (f1 != f2) fa[f1] = f2;
}
int main() {
	while (true) {
		cin >> n;
		if (n == 0) break;
		for (int i = 1; i <= n; i++) fa[i] = i;//并查集初始化
		memset(tmp, 0, sizeof(tmp));
		cin >> m;
		for (int i = 1; i <= m; i++) {
			cin >> a >> b;
			join(a, b);
		}
		for (int i = 1; i <= n; i++) {
			tmp[i] = find(i);
		}
		sort(tmp + 1, tmp + 1 + n);
		int set=unique(tmp+1,tmp+n+1)-(tmp+1);
		cout << set - 1 << endl;
	}
	return 0;
}

二、Hash表:

计数排序升级版:

值域[0,1e7]正常处理,值域[0,1e9]则可以取一个模数mod,定义一个大小为mod的数组,然后把每个数对mod取模。如果两个数对mod取模得到相同的值,那么就认为这两个数是相同的。

cpp 复制代码
#include <iostream>
using namespace std;
#define mod 233333
int n, x, ans, a[mod + 2];
int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> x;
		x %= mod;
		if (!a[x]) a[x] = 1, ans++;
	}
	cout << ans << endl;
	return 0;
}

这个处理方法优势和劣势都很明显。

优势:有效减少了空间的利用,只需要定义一个大小为mod的数组。

劣势:如果有两个不同的数恰好对mod取模之后得到了相同的结果,那这个算法的正确性就得不到保证了。

如何优化算法,能够扬长避短?

哈希表(Hash表):

cpp 复制代码
#include <iostream>
using namespace std;
#define mod 233333
int n, x, ans;
#include <vector>
vector <int> linker[mod + 2];
inline void insert(int x) {
	for (int i = 0; i < linker[x % mod].size(); i++) {
		if(linker[x%mod][i]==x) return;
	}
	linker[x % mod].push_back(x);
	ans++;
}
int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> x;
		insert(x);
	}
	cout << ans<<endl;
	return 0;
}

问题升级到字符串:(洛谷P3370):例17-3:字符串哈希:

cpp 复制代码
#include <iostream>
using namespace std;
#include <string>
#include <vector>
#define MAXN 1510
#define base 261
#define mod 23333
int n, ans;
char s[MAXN];
vector <string> linker[mod + 2];
inline void insert() {
	int hash = 1;
	for (int i = 0; s[i]; i++) {
		hash = (hash * base + s[i]) % mod;
	}
	string t = s;
	for (int i = 0; i < linker[hash].size(); i++) {
		if (linker[hash][i] == t) return;
	}
	linker[hash].push_back(t);
	ans++;
}
int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> s;
		insert();
	}
	cout << ans << endl;
	return 0;
}

不断进行迭代运算hash=(hash*k+s[i])%mod即可。

基数k可以任选,但是一般来说不少于128(ASCII字符集的数量)。当然,求Hash值的方法并不唯一,例如如果字符集局限于a到z的小写字母,也可以把每个数字映射为0到25,此时基数是26.

(洛谷P3405)例17-4:省市:

因为值和前两个字母有关系,所以对每个字符串只保留前两个字母,相当于有N个二元组<ai,bi>,问<ai,bi>=<aj,bj>的(i,j)对数。

cpp 复制代码
#include <iostream>
using namespace std;
#define mod 233333
long long ans;
int n;
#include <string>
#include <vector>
vector < pair<int, int> > linker[mod + 2];
string a, b;
inline int gethash(string a, string b) {
	return (a[0] - 'A') + (a[1] - 'A') * 26 + (b[0] - 'A') * 26 * 26 + (b[1] - 'A') * 26 * 26 * 26;
}
inline void insert(int x) {
	for (int i = 0; i < linker[x % mod].size(); i++) {
		if (linker[x % mod][i].first == x) {
			linker[x % mod][i].second++;//把x所对应的出现次数++
			break;
		}
	}
	linker[x % mod].push_back(pair<int, int>(x, 1));
}
int find(int x) {
	for (int i = 0; i < linker[x % mod].size(); i++) {
		if (linker[x % mod][i].first == x) {
			return linker[x % mod][i].second;
		}
	}
	return 0;
}
int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a >> b;
		a[2] = 0;
		if (a[0] != b[0] || a[1] != b[1]) {
			ans += find(gethash(b, a));
		}
		insert(gethash(a, b));
	}
	cout << ans << endl;
	return 0;
}

三、集合应用实例:

(洛谷P5250)例17-5:木材仓库:

问题:维护一个集合,可以插入一个元素x,同时判断x是否已经存在;查询x的前驱后继,x的前驱定义为小于x的最大的数,x的后继定义为大于x的最小的数。

虽然可以使用比较高级的数据结构(如平衡树或Trie)来维护集合,但是比较难实现。不过,可以通过调用STL里面的set来很方便地解决这个问题。set的本质是红黑树(一种比较优秀的平衡二叉树)。

set集合需要用到的头文件是set,其方法如下:

set <int> ds;建立一个名字叫作ds的、元素类型为int的集合。

ds.insert(x);在集合中插入一个元素,如果这个元素已经存在,则什么都不干。

ds.erase(x);在集合中删除元素x,如果这个数不存在,则什么都不干。

ds.erase(it);删除集合中地址为it的元素。

ds.end();返回集合中最后一个元素的下一个元素的地址。不过这个很少直接使用,而是配合其他方法进行比较,以确认某个元素是否存在。

ds.find(x);查询x在集合中的地址,如果这个数不存在,则返回ds.end()。

ds.lower_bound(x);查询不小于(大于等于)x的最小的数在集合中的地址,如果这个数不存在,则返回ds.end()。

ds.upper_bound(x);查询大于x的最小的数在集合中的地址,如果这个数不存在,则返回ds.end()。

ds.empty();如果集合是空的,则返回1,否则返回0。

ds.size();返回集合中元素的个数。

cpp 复制代码
#include <iostream>
using namespace std;
#include <set>
set <int> ds;
int n;
int a, b;
int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >>a >>b;
		if (a == 1) {
			if (ds.find(b) == ds.end()) ds.insert(b);
			else cout << "Already Exist" << endl;
		}
		else if (ds.empty()) {
			cout << "Empty" << endl;
		}
		else  {
			set <int>::iterator i = ds.upper_bound(b);//i

			set <int>::iterator j = i;
			if (j != ds.begin()) j--;//j

			if (i != ds.end() && b - (*j) > (*i) - b) j = i;//*i要求i!=ds.end(),选择j的值

			cout << (*j) << endl;
			ds.erase(j);//去除j指向的元素	
			
		}
	}
	return 0;
}

lower_bound返回的迭代器,可以对其++找到后继元素的迭代器,也可以--找到前驱元素的迭代器。需要注意指向元素的迭代器,如果已经是begin(),则不能--,如果是end(),则不能++。

(洛谷P5266)例17-6:学籍管理:

map关联集合的本质也是一颗红黑树,可以看作一个下标可以是任意类型的数组。

其头文件是map,可以调用map实现如下几个基础功能:

map <A,B> ds;

建立一个名字叫作ds、下标类型为A,元素类型为B的映射表,例如map <string,int> 就是一个将string映射到int的映射表。

ds[A]=B;

把这个"数组"中下标为A的位置的值变成B,这里下标可以是任意类型,不一定先限定于大于0的整数,比如map<string,string>ds,就可以进行ds["kkksc03"]="mascot"的操作。

ds[A];

访问这个"数组"中下标为A的元素,比如可以进行cout<<ds["kkksc03"]<<endl;这样的操作。

注意:在使用ds[A]访问"数组"下标为A的元素时,如果这个下标对应的元素不存在,则会自动创建下标为A、值为默认值(例如,所有数值类型的默认值是0,string字符串是空字符串)的元素。

ds.end();

返回映射表中最后一个元素的下一个元素的地址。这个很少直接单独使用,而是配合其他方法进行比较,以确认某个元素是否存在。

ds.find(x);

查询x在映射表中的地址,如果这个数不存在,则返回ds.end().

ds.empty();

如果映射表是空的,则返回1,否则返回0。

ds.size();

返回映射表中的元素个数。

ds.erase(A);

删除这个"数组"中下标为A的元素。

cpp 复制代码
#include <iostream>
using namespace std;
int n;
int opt;
#include <map>
map <string, int> ds;
#include <string>
string name;
int score;
int main(){
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> opt;
		if (opt == 1) {
			cin >> name >> score;
			ds[name] = score;
			cout << "OK" << endl;
		}
		else if (opt == 2) {
			cin >> name;
			if (ds.find(name) == ds.end()) cout << "Not found" << endl;
			else cout << ds[name] << endl;
		}
		else if (opt == 3) {
			cin >> name;
			if (ds.find(name) == ds.end()) cout << "Not found" << endl;
			else {
				cout << "Deleted successfully" << endl;
				ds.erase(name);
			}
		}
		else {
			cout << ds.size() << endl;
		}
	}
	return 0;
}

(洛谷P1102)例17-7:A-B数对:

cpp 复制代码
#include <iostream>
using namespace std;
#include <map>
#define maxn 200010
map <int, int> ds;
int n, c;
int a[maxn];
long long ans;
int main() {
	cin >> n >> c;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		ds[a[i]]++;
	}
	for (int i = 1; i <= n; i++) {
		ans += ds[a[i] - c];
	}
	cout << ans << endl;
	return 0;
}

四、习题:

(洛谷P1918):习题17-1:保龄球:

cpp 复制代码
#include <iostream>
#include <map>
using namespace std;
#define maxn 100010
int n;
int a[maxn];
map <int, int> ds;
int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		ds[a[i]] = i;
	}
	int q;
	cin >> q;
	int a;
	for (int i = 1; i <= q; i++) {
		cin >> a;
		cout << ds[a] << endl;
	}
	return 0;
}

(洛谷P1525)习题17-2:关押罪犯:(世纪好题)

考察:并查集+排序

cpp 复制代码
#include <iostream>
using namespace std;
#include <algorithm>
int n, m;
int a[20010];//负责人
int b[20010];//敌人
typedef struct data {
	int x, y, z;
}Data;
Data t[100010];
inline bool cmp(Data a,Data b) {
	return a.z > b.z;
}
inline int find(int x) {
	if (x == a[x])return x;
	return a[x] = find(a[x]);
}
inline void join(int x,int y) {
	int a1 = find(x);
	int b1 = find(y);
	if (a1 != b1) a[a1] = b1;
}
inline bool check(int a,int b) {
	int a1 = find(a);
	int b1 = find(b);
	if (a1 == b1) return true;
	return false;
}
int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) a[i] = i;//并查集初始化
	for (int i = 1; i <= m; i++) {
		cin >> t[i].x >> t[i].y >> t[i].z;
	}
	sort(t + 1, t + 1 + m, cmp);
	for (int i = 1; i <= m+1; i++) {//细节m+1,如果本年内监狱中未发生任何冲突事件,请输出 0
		if (check(t[i].x, t[i].y)) {
			cout << t[i].z << endl;
			break;
		}
		else{
			if (!b[t[i].x]) b[t[i].x] = t[i].y;
			else join(b[t[i].x], t[i].y);
			if (!b[t[i].y]) b[t[i].y] = t[i].x;
			else join(b[t[i].y], t[i].x);
		}
	}
	return 0;
}

(洛谷P1621)习题17-3:集合:

80分,剩下两个点超时了,等学完素数筛后可以降低复杂度,目前无能为力。

cpp 复制代码
#include <iostream>
using namespace std;
int a, b, p;
int fa[100010];
int tmp[100010];
int c[100010];
bool check_prime(int x) {
	if (c[x] == 1) return true;
	if (x == 0 || x == 1) return false;
	for (int i = 2; i * i <= x; i++) {
		if (x % i == 0) return false;
	}
	c[x] = 1;
	return true;
}
bool check(int x,int y) {
	for (int i = y; i >= p; i--) {
		if (x % i == 0 && y % i == 0 && check_prime(i) )  return true;
	}
	return false;
}
int find(int x) {
	if (x == fa[x]) return x;
	return fa[x] = find(fa[x]);
}
void join(int x,int y) {
	int f1 = find(x), f2 = find(y);
	if (f1 != f2) fa[f1] = f2;
}
int main() {
	cin >> a >> b >> p;
	for (int i = a; i <= b; i++) fa[i] = i;
	for (int i = b; i > a; i--) {
		for (int j = i - 1; j >= a; j--) {
			if (check(i,j)) join(i, j);
		}
	}
	int num = 0;
	for (int i = a; i <= b; i++) {
		tmp[find(i)]++;
	}
	for (int i = 1; i <= 100002; i++) {
		if (tmp[i] != 0) num++;
	}
	cout << num << endl;
	return 0;
}

(洛谷P1892):习题17-4:团伙:

注意并查集的初始化,差点忘了。vector的使用很精妙。

cpp 复制代码
#include <iostream>
using namespace std;
int n,m;
char opt;
int o1, o2;
int fa[1010];
#include <vector>
vector <int> b[1010];
int find(int x) {
	if (x == fa[x]) return x;
	return fa[x] = find(fa[x]);
}
void join(int x, int y) {
	int f1 = find(x), f2 = find(y);
	if (f1 != f2) fa[f1] = f2;
}
int tmp[1010];
int ans;
int main() {
	cin >> n>>m;
	for (int i = 1; i <= n; i++) {
		fa[i] = i;
	}
	for (int i = 1; i <= m; i++) {
		cin >> opt;
		if (opt == 'F') {
			cin >> o1 >> o2;
			join(o1, o2);
		}
		else {
			cin >> o1 >> o2;
			b[o1].push_back(o2);
			b[o2].push_back(o1);
 		}
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j < b[i].size(); j++) {
			for (int k = 0; k < b[b[i][j]].size(); k++) {
				join(b[b[i][j]][k], i);
			}
		}
	}
	for (int i = 1; i <= n; i++) {
		tmp[find(i)]++;
	}
	for (int i = 1; i <= 1002; i++) {
		if (tmp[i] != 0) ans++;
	}
	cout << ans << endl;
	return 0;
}

(洛谷P1955)习题17-5:程序自动分析:

没有学到离散化,1e9很难受,受限了。

(洛谷P4305):习题17-6:不重复数字:

60分map做法:

cpp 复制代码
#include <iostream>
using namespace std;
#include <cstdio>
#include <map>
int t, n, num;
int main() {
	cin >> t;
	for (int i = 1; i <= t; i++) {
		map <int, int> ds;
		cin >> n;
		for (int j = 1; j <= n; j++) {
			cin >> num;
			if (ds[num] == 0) ds[num]++, printf("%d ",num);
		}
		printf("\n");
	}
	return 0;
}

60分小丑hash做法:

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define mod 233333
#include <vector>
int t, n, num;
vector <int> ds[mod + 2];
int gethash(int x) {
	return (x % mod + mod) % mod;
}
int main() {
	cin >> t;
	for (int i = 1; i <= t; i++) {
		memset(ds, 0, sizeof(ds));
		cin >> n;
		for (int j = 1; j <= n; j++) {
			cin >> num;
			int flag = 0;
			for (int k = 0; k < ds[gethash(num)].size(); k++) {
				if (ds[gethash(num)][k] == num) {
					flag = 1;
					break;
				}
			}
			if (flag == 0) {
				ds[gethash(num)].push_back(num);
				printf("%d ", num);
			}
		}
		printf("\n");
	}
	return 0;
}

存数组里输出玄学过了:

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define mod 233333
#include <vector>
int  t, n, num;
int ans[50010];
vector <int> ds[mod + 2];
int gethash(int x) {
	return (x % mod + mod) % mod;
}
int add;
int main() {
	cin >> t;
	for (int i = 1; i <= t; i++) {
		memset(ds, 0, sizeof(ds));
		memset(ans, 0, sizeof(ans));
		add = 0;
		cin >> n;
		for (int j = 1; j <= n; j++) {
			cin >> num;
			int flag = 0;
			for (int k = 0; k < ds[gethash(num)].size(); k++) {
				if (ds[gethash(num)][k] == num) {
					flag = 1;
					break;
				}
			}
			if (flag == 0) {
				ds[gethash(num)].push_back(num);
				ans[add++] = num;
			}
		}
		for (int i = 0; i < add; i++) printf("%d ", ans[i]);
		printf("\n");
	}
	return 0;
}

玄学求解 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)和这个一样,不知道为啥。。。

(洛谷P3879)例17-7:阅读理解:

0分答案全WA,不知道为啥错,样例过了。

cpp 复制代码
#include <iostream>
using namespace std;
#include <string>
int n,l,m;
#define mod 233333
string a;
#include <vector>
vector < pair< string,vector<int> > > ds[mod + 2];
int gethash(string s){
	int ans=0;
	for (int i = 0; i < s.size(); i++) {
		ans = (ans * 26 + s[i]-'a') % mod;
	}
	return ans;
}
int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> l;
		for (int j = 1; j <= l; j++) {
			cin >> a;
			int flag = 0;
			for (int k = 0; k < ds[gethash(a)].size(); k++) {
				if (ds[gethash(a)][k].first == a) {
					flag = 1;
					int flag1 = 0;
					for (int dd = 0; dd < (ds[gethash(a)][k].second).size(); dd++) {
						if ((ds[gethash(a)][k].second)[k] == i) {
							flag1 = 1;
							break;
						}
					}
					if (flag1 == 1) break;
					(ds[gethash(a)][k].second).push_back(i);
					break;
				}
			}
			vector <int> q;
			q.push_back(i);
			if(flag==0)
			    ds[gethash(a)].push_back(pair< string,vector<int> >(a,q));
		}
	}
	cin >> m;
	for (int i = 1; i <= m; i++) {
		cin >> a;
		int flag = 0;
		for (int j = 0; j < ds[gethash(a)].size(); j++) {
			if (ds[gethash(a)][j].first == a) {
				vector <int> y = ds[gethash(a)][j].second;
				for (int k = 0; k <y.size(); k++) {
					if(k!= y.size()-1)
					    cout << y[k]<<' ';
					else 
						cout << y[k]<<(i==m ? "" : "\n");
				}
				flag = 1;
				break;
			}
		}
		if (flag == 0) cout << "\n";
	}
	return 0;
}

(洛谷P17-9):习题17-9:家谱:

cpp 复制代码
#include <iostream>
using namespace std;
#include <string>
string a,fa;
#include <map>
map <string, string> ds;
string find(string s) {
	if (s == ds[s]) return s;
	return ds[s] = find(ds[s]);
}
int main() {
	char ch;
	cin >> ch;
	while ( ch!= '$') {
		cin >> a;
		if (ch== '#') {
			fa = a;
			if (ds[a] == "") ds[a] = a;
		}
		else if (ch == '+') {
			ds[a] = fa;
		}
		else {
			cout << a << ' ' << find(a) << endl;
		}
		cin >> ch;
	}
	return 0;
}

若用到getchar() ,超时0分:

cpp 复制代码
#include <iostream>
using namespace std;
#include <cstdio>
char ch;
#include <string>
string a,fa;
#include <map>
map <string, string> ds;
string find(string s) {
	if (s == ds[s]) return s;
	return ds[s] = find(ds[s]);
}
int main() {
	while ((ch = getchar()) != '$') {
		cin >> a;
		if (ch== '#') {
			fa = a;
			if (ds[a] == "") ds[a] = a;
		}
		else if (ch == '+') {
			ds[a] = fa;
		}
		else {
			cout << a << ' ' << find(a) << endl;
		}
		getchar();
	}
	return 0;
}
相关推荐
ahadee3 分钟前
蓝桥杯每日真题 - 第12天
c++·vscode·算法·蓝桥杯
zhentiya16 分钟前
微积分第五版课后习题答案详解PDF电子版 赵树嫄
算法·pdf
vortex525 分钟前
解决 VSCode 中 C/C++ 编码乱码问题的两种方法
c语言·c++·vscode
luky!1 小时前
算法--解决熄灯问题
python·算法
鸽鸽程序猿1 小时前
【算法】【优选算法】二分查找算法(下)
java·算法·二分查找算法
_OLi_1 小时前
力扣 LeetCode 150. 逆波兰表达式求值(Day5:栈与队列)
算法·leetcode·职场和发展
远望清一色1 小时前
基于MATLAB身份证号码识别
开发语言·图像处理·算法·matlab
醉颜凉2 小时前
【NOIP提高组】潜伏者
java·c语言·开发语言·c++·算法
hunandede2 小时前
FFmpeg 4.3 音视频-多路H265监控录放C++开发十三.2:avpacket中包含多个 NALU如何解析头部分析
c++·ffmpeg·音视频
lapiii3582 小时前
图论-代码随想录刷题记录[JAVA]
java·数据结构·算法·图论