排序算法: 数据的离散化方式(数组与区间离散化,排序+去重 C++例题实现)
数据的离散化
离散化是指将一个无穷大的集合中的若干个元素映射到一个有限的集合中,以便于对那个无穷大的集合进行操作。
在很多情况下:对于一个规定在Z范围内的整数范围,他有可能包含非常多的重复的元素,真正不同的元素仅有M个,因此我们考虑把这Z个元素进行映射到只包含M个元素的集合中,即与 【1-M】个整数建立映射关系。因此如果一个算法的时间空间复杂度与Z有关,则会降低到与M有关,对于某些重复元素非常多的集合,将会大大提高算法的复杂度。
那么显而易见:离散化的首要目的便是给包含Z个元素的集合去重,使得它不包含任意的重复元素,每个元素只出现一次,然后映射到另一个无任何重复元素的集合中,这个集合只包含M个元素。
- 我们把a集合中的某一个元素a[i]映射到b[j]中,b[j]中不含任意重复的元素,相当于是排序+去重后得a集合(可以使用STL的unique实现这个功能)
- 我们想要得知 j 这个序号表示的是哪一个a集合中的元素,即表示哪一个a[i],只需要返回b[j]即可
- 我们想要得知 a[i]这个元素,在b集合中被哪一个序号i所表示,我们就可以使用二分查找来找到它的位置。
离散化方式(一)
int a[11] = { 0,1,1,2,3,4,5,6,9,2,7 }; //注意下标从1开始,a[0]是凑数的 int b[11], m = 0; void discrete() { //排序,去重,实现离散化 sort(a+1,a+1+10); for (int i=1;i<=n;i++) { if (i==1 || a[i]!=a[i-1]) { b[++m]=a[i]; } } } int find(int x) { return lower_bound(b+1,b+1+m,x)-b; }
举例:
int main() { discrete(); cout << b[8] << endl; //查询i=8(1<=i<=m)代替的是哪一个数值:直接返回b[i] cout << query(5) << endl; //查询a[j]的值被哪个i替代,只需要二分查找a[j]即可 return 0; }
离散化方式(二)
struct nums { int val,num; }nums[N]; int dst[N]; bool comp(nums a,nums b) { if (a.val==a.val) { return a.num<b.num; } return a.val<b.val; } for (int i=1;i<=n;i++) { cin>>nums[i].val; nums[i].num=i; } sort(nums+1,nums+1+n,comp); //利用自定义的比较规则,按值排序 for (int i=1;i<=n;i++) { dst[nums[i].num]=i; //按值排序后的数组的元素再重新按照从小到大的顺序排序,离散化 }
利用STL离散化(三)
时间复杂度O(NlogN)
for (int i=1;i<=n;i++) { cin>>a[i]; //a保存原始数据 b[]i]=a[i]; //b也保存一份,便于一会排序 } sort(b+1,b+1+n); //给b排序 unique(b+1,b+1+n); //给b去重 for (int i=1;i<=n;i++) { int pos=lower_bound(b+1,b+1+n,a[i])-b; //查找b数组中a某元素的位置 a[i]=pos; //数据离散化!!!!!!!! a存储的是元素的**相对大小** }
对于区间两端的离散化
要注意,带有区间的问题的区间范围离散化,如例题:区间染色问题 —线段树,我们需要把区间进行离散化。
注意:区间右端点多开一个范围,否则会造成区间的重叠:
区间 [4,9] [6,15] 他们是有重叠的,如果不多开一个右范围,则会造成区间的一些错误(具体什么错误自己尝试)
for (int j=1;j<=n;j++) { scanf("%lld%lld",&nums[j].l,&nums[j].r); temp[++cnt]=nums[j].l; temp[++cnt]=nums[j].r; temp[++cnt]=nums[j].r+1; } /* 对区间端点的离散化!!! */ sort(temp+1,temp+1+cnt); len=unique(temp+1,temp+1+cnt)-temp-1; for (int k=1;k<=n;k++) { int posl=lower_bound(temp+1,temp+1+len,nums[k].l)-temp; int posr=lower_bound(temp+1,temp+1+len,nums[k].r)-temp; nums[k].l=posl; nums[k].r=posr; }
例题:电影
这是一道利用了离散化数据的例题。
可以看到此题中关于语言这一个量的限制达到了10^9 ,并且对于这个语言,我们貌似还必须要用一个数组存储起来,否则就无法操作,因此我们的数组就会存储一个非常大的集合,并且我们的数组还不一定能装下,因此我们考虑使用离散化。
做法:
- 首先收集所有的语言:使用ori数组。
- 然后对于ori数组进行离散化处理,保存离散化数据到uni中。
- 根据离散化的数据集合得到每一位科学家懂得的语言编号,保存在ans数组中。
- 遍历所有电影,对于每一个电影,分别求出能够使科学家达到很开心的科学家的人数,其次求出达到比较开心的人数,然后对于这两个数据和以前的值取一个最大的值,记录电影编号。
首先收集所有语言:全部存在ori数组里,数组的下标使用tot记录
cin >> n; //n:n位科学家 for (int i = 1; i <= n; i++) { //科学家懂得的语言编号 cin >> p[i]; ori[++tot] = p[i]; //原始数据全部存在ori中 } cin >> m; //m:m个电影 for (int i = 1; i <= m; i++) { //每一部电影的语音语言 cin >> y[i]; ori[++tot] = y[i]; } for (int i = 1; i <= m; i++) { //每一部电影的字幕语言 cin >> z[i]; ori[++tot] = z[i]; }
然后进行离散化数据!!!!!!!!!!
把离散化数据存放在uni数组中
//排序 sort(ori + 1, ori + 1 + tot); //离散化数据 for (int i = 1; i <= tot; i++) { if (i == 1 || ori[i] != ori[i - 1]) { uni[++k] = ori[i]; } }
注意:离散化的数据集合uni中只包含了所有的语言编号,并不包含任何其他数据,如人数等等,统计各个人数是在下一步进行的
统计懂得每种语言的科学家分别有多少人???
使用find进行查询:给出p[i]表示的是未离散化的大集合,要找到这个p[i]在离散化后被哪个编号i所映射,使用find函数。 得到的是一个映射标号i,然后ans[i]表示的是懂得第i种语言的人数。
for (int i = 1; i <= n; i++) { //查询懂得某种语言的人数有多少 ans[find(p[i])]++; }
统计电影的语音和字幕的人数
同理:
- ans[find(y[i])]:表示懂得第i部电影的语音的人数,因为在上一步我们已经预处理了懂得每种语言的人数,所有说把一个语言编号i传递给ans,得到的就是懂得 i语言的人数。
- ans[find(x[i])]:懂得第i部电影的字幕的人数。
统计完成后,如果这一部电影的resy(懂得语音的人数)比上一部电影的resy多(注意:上一部电影的语音用res1保存,上一部的字幕用res2保存),那么更新电影编号;
如果相等,那么再比较字幕。。。
for (int i = 1; i <= m; i++) { resy = ans[find(y[i])];//懂得语音的人数 resz = ans[find(z[i])];//懂得字幕的人数 if (resy > res1 || (resy == res1 && resz > res2)) { res = i; //记录电影编号 res1 = resy;//记录这部电影的懂得语音的人数 res2 = resz;//记录这部电影的懂得字幕的人数 } }
最后答案有可能会出现 没有一个人懂得某部电影的语音和字幕,则随便输出一个。
完整代码
!!!!!!!!!! 注意理解这些变量的名字,不要弄混了!!!!!!!!!!!
#include <bits/stdc++.h> using namespace std; /* 请你帮忙选择一部电影,可以让观影很开心的人最多。 如果有多部电影满足条件,则在这些电影中挑选观影比较开心的人最多的那一部。 */ const int N = 200005; int n, m, resy, resz, res, res1, res2, k = 0, tot = 0; int p[N], y[N], z[N], ans[3 * N]; int ori[N * 3], uni[3 * N]; //ori存放原始数据,uni存放离散数据 多给点空间 int find(int x) { //根据a[j]查询替代此a[j]值的序号j return lower_bound(uni + 1, uni + 1 + k, x) - uni; } int main() { cin >> n; //n:n位科学家 for (int i = 1; i <= n; i++) { //科学家懂得的语言编号 cin >> p[i]; ori[++tot] = p[i]; //原始数据全部存在ori中 } cin >> m; //m:m个电影 for (int i = 1; i <= m; i++) { //每一部电影的语音语言 cin >> y[i]; ori[++tot] = y[i]; } for (int i = 1; i <= m; i++) { //每一部电影的字幕语言 cin >> z[i]; ori[++tot] = z[i]; } //排序 sort(ori + 1, ori + 1 + tot); //离散化数据 for (int i = 1; i <= tot; i++) { if (i == 1 || ori[i] != ori[i - 1]) { uni[++k] = ori[i]; } } for (int i = 1; i <= n; i++) { //查询懂得某种语言的人数有多少 ans[find(p[i])]++; } for (int i = 1; i <= m; i++) { resy = ans[find(y[i])];//懂得语音的人数 resz = ans[find(z[i])];//懂得字幕的人数 if (resy > res1 || (resy == res1 && resz > res2)) { res = i; //记录电影编号 res1 = resy;//记录这部电影的懂得语音的人数 res2 = resz;//记录这部电影的懂得字幕的人数 } } if (res == 0) { cout << 1; } else { cout << res; } return 0; }
参考:
《算法竞赛进阶指南》
AcWing 103 题解
本文来自博客园,作者:hugeYlh,转载请注明原文链接:https://www.cnblogs.com/helloylh/p/17209617.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)