离散化 算法 思路+模板代码

本文详细介绍了离散化技术的应用场景、核心思想及其实现步骤。适用于值域大但元素数量少的情况,通过排序、去重和映射,将原始序列转化为紧凑有序的序列,便于后续处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题背景

序列中的元素值域非常大,但是序列中元素的个数非常小
满足值域非常大,但个数非常小
非常的松散
序列元素的相对大小位置)的作用>绝对大小值是多少)的作用
因为非常的松散,开辟值域范围大小的数组来存储它们非常的不划算且不现实
所以应该想出一个方法,把松散的序列映射成等价紧凑的序列
这种方法,叫做离散化


思路(离散化的步骤)

首先,我们定义一个可变数组alls:

vector< int > alls //可变数组,存储序列中出现的元素的值,因为序列中元素的个数非常小,所以这个代价是非常小的

alls即为出现在序列中的元素的所有可能的值,但是是一步步加进去的,所以可能重复,而且加进去的顺序是无序的、松散的,我们想要把它映射成紧凑的序列,应该对这alls进行排序、去重

sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重复元素
//unique函数实现把不重复的元素放到前面,重复的元素放到后面,返回一个指向重复元素起始位置的尾部指针

经过排序去重后的alls,存储的是这个序列按从小到大顺序出现的元素值,且不含重复元素,从各个元素的值来看,它们依然是松散的,但是相邻元素已经被放到连续的数组空间,也即它们的存储是紧凑连续的,从数组下标的0开始,0,1,2,…,n-1
所以我们自然的能想到将序列中出现的元素值映射成他们对应的数组下标
那么这里的映射关系怎么实现呢?给出序列中一个元素的值,怎么去找到它对应的下标?
可以采用二分算法来实现:

// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于x的位置
{
    int l = 0, r = alls.size() - 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (alls[mid] >= x) r = mid;
        else l = mid + 1;
    }   //r即为x在离散化后的数组下标
    return r + 1; // 映射到1, 2, ...n   这里不同题目情况不同,返回值因情况而定
}                  

离散化的本质:
将数字本身key映射为它在数组中的索引index(1 based),所以通过二分求索引(value -> index)是离散化的本质。


代码模板
vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重复元素
//unique函数实现把不重复的元素放到前面,重复的元素放到后面,返回一个指向重复元素起始位置的尾部指针
// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于x的位置
{
    int l = 0, r = alls.size() - 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (alls[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return r + 1; // 映射到1, 2, ...n
}

作者:yxc
链接:https://www.acwing.com/blog/content/277/
来源:AcWing

<think>好的,我现在需要帮助用户理解离散化的基本思路。用户之前询问了一段C++代码的问题,现在转向了离散化,这可能是在处理算法数据结构相关的问题时遇到的。我需要先确定用户对离散化的了解程度,可能他们正在学习算法优化或者处理大数据问题。 离散化通常用于将大范围的数值映射到更小的区间,特别是当数值的范围很大但实际数量较少时。比如处理坐标压缩或者统计频率的情况。用户可能已经遇到了这样的场景,比如在解决区间问题或者需要排序和去重的时候。 首先,我应该解释离散化的核心思想,即映射。然后分步骤说明基本思路,确保结构清晰。用户可能需要知道具体的步骤,比如排序、去重、二分查找映射,这些是关键点。还需要提到为什么需要这样做,比如节省空间,提高效率。 然后,考虑到用户之前询问的是代码问题,可能他们希望看到一些应用场景的例子,比如求区间和或者计算逆序对。这些例子能帮助他们理解离散化的实际应用。同时,注意事项也很重要,比如保持顺序、处理重复元素,这些在实现时容易出错的地方需要强调。 用户可能还想知道离散化的优缺点,所以应该总结一下,比如提升效率但增加预处理步骤。最后,确保语言通俗易懂,避免使用过多术语,但又要准确。可能需要检查是否有遗漏的步骤或者常见错误,确保提供的信息全面且正确。</think>### 离散化的基本思路 **离散化**是一种将**大范围、稀疏的数值**映射到**紧凑、连续的整数区间**的技术,常用于处理数据范围极大但实际有效值较少的情况(如坐标压缩、统计频次等)。其核心思想是**保留数据的相对大小关系**,同时减少存储和计算开销。 --- #### 一、离散化的核心步骤 1. **收集所有待离散化的数值** 将所有需要处理的数值存入一个数组(包括查询的端点、操作的坐标等)。 2. **排序 + 去重** - 对数组进行**排序**,使数值有序。 - 使用`unique`或双指针法**去重**,确保每个值唯一。 3. **建立映射关系** 通过**二分查找**(如`lower_bound`),将原始数值映射到连续的整数索引(通常从1开始,避免边界问题)。 --- #### 二、典型应用场景 1. **区间求和问题** - **场景**:数值范围$[-10^9, 10^9]$,但实际操作的坐标仅有$10^5$个。 - **离散化后**:将坐标映射到$[1, 10^5]$,便于使用前缀和或树状数组统计。 2. **逆序对计数** - 将大范围数值压缩到小范围,利用树状数组高效统计。 3. **数据索引优化** - 当数据本身的值不重要,仅需保留**相对顺序**时(如某些图论问题中的节点编号)。 --- #### 三、具体实现示例 ```cpp // 离散化函数模板 vector<int> discrete(vector<int>& alls) { sort(alls.begin(), alls.end()); // 排序 alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去重 return alls; } // 查询离散化后的索引(二分查找) int get_id(int x, vector<int>& alls) { return lower_bound(alls.begin(), alls.end(), x) - alls.begin() + 1; // 映射到1-based索引 } ``` --- #### 四、关键注意事项 1. **保持原始顺序** 离散化后的索引必须**严格反映原数值的大小关系**,即若$a < b$,则离散化后$a$的索引也要小于$b$的索引。 2. **处理重复值** - 去重时需保留唯一值,但映射时不同位置的相同值需指向同一索引。 3. **边界处理** - 若离散化后的索引用于数组下标,建议使用**1-based索引**,避免下标为0时的计算错误。 --- #### 五、离散化的优缺点 | 优点 | 缺点 | |------|------| | 将稀疏数据压缩为稠密数据,节省空间 | 增加预处理步骤(排序、去重) | | 使算法复杂度与数据规模解耦 | 需额外维护映射关系 | | 便于使用数组、树状数组等结构 | 可能引入二分查找的额外时间 | --- ### 总结 离散化的本质是**通过映射保留数据关系,将问题规模从“数值范围”转移到“有效数据量”**。掌握这一技术能高效解决许多与大范围数值相关的算法问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Alkali!

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

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

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

打赏作者

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

抵扣说明:

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

余额充值