排序算法: 数据的离散化方式(数组与区间离散化,排序+去重 C++例题实现)

数据的离散化

离散化是指将一个无穷大的集合中的若干个元素映射到一个有限的集合中,以便于对那个无穷大的集合进行操作。

在很多情况下:对于一个规定在Z范围内的整数范围,他有可能包含非常多的重复的元素,真正不同的元素仅有M个,因此我们考虑把这Z个元素进行映射到只包含M个元素的集合中,即与 【1-M】个整数建立映射关系。因此如果一个算法的时间空间复杂度与Z有关,则会降低到与M有关,对于某些重复元素非常多的集合,将会大大优化算法的复杂度


那么显而易见:离散化的首要目的便是给包含Z个元素的集合去重,使得它不包含任意的重复元素,每个元素只出现一次,然后映射到另一个无任何重复元素的集合中,这个集合只包含M个元素。

  1. 我们把a集合中的某一个元素a[i]映射到b[j]中,b[j]中不含任意重复的元素,相当于是排序+去重后得a集合(可以使用STL的unique实现这个功能)
  2. 我们想要得知 j 这个序号表示的是哪一个a集合中的元素,即表示哪一个a[i],只需要返回b[j]即可
  3. 我们想要得知 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;
		}

例题:电影

AcWing 103 电影

这是一道利用了离散化数据的例题。

可以看到此题中关于语言这一个量的限制达到了10^9 ,并且对于这个语言,我们貌似还必须要用一个数组存储起来,否则就无法操作,因此我们的数组就会存储一个非常大的集合,并且我们的数组还不一定能装下,因此我们考虑使用离散化

做法:

  1. 首先收集所有的语言:使用ori数组。
  2. 然后对于ori数组进行离散化处理,保存离散化数据到uni中。
  3. 根据离散化的数据集合得到每一位科学家懂得的语言编号,保存在ans数组中。
  4. 遍历所有电影,对于每一个电影,分别求出能够使科学家达到很开心的科学家的人数,其次求出达到比较开心的人数,然后对于这两个数据和以前的值取一个最大的值,记录电影编号。

首先收集所有语言:全部存在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];
    }
}

/*---------------*/
注意:可以直接使用unique 来去重,具体看下方完整代码

注意:离散化的数据集合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, tot = 0;
int p[N], y[N], z[N], ans[3 * N];
int ori[N * 3], uni[3 * N]; //ori存放原始数据,uni存放离散数据  多给点空间
int k;
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);
    //离散化数据
	k = std::unique(ori+1,ori+1+tot)-ori;  //记得减去 ori ,k表示的是离散化后的数据 
    for (int i = 1; i <= n; i++)
    {
        //查询懂得第num种语言的人数有ans[num]人 
        int num=lower_bound(ori+1,ori+1+k,p[i])-ori;
        ans[num]++;
    }
    for (int i = 1; i <= m; i++)
    {
    	int yy=lower_bound(ori+1,ori+1+k,y[i])-ori;
    	int zz=lower_bound(ori+1,ori+1+k,z[i])-ori;
        resy = ans[yy];//懂得语音的人数
        resz = ans[zz];//懂得字幕的人数
        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 题解

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yuleo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值