进阶数据结构——离散化
创作时间:
作者:
@小白创作中心
进阶数据结构——离散化
引用
CSDN
1.
https://blog.csdn.net/ycs66/article/details/145595750
离散化是一种将连续数据映射到有限离散值的技术,通常用于处理大规模数据或稀疏数据。通过将原始数据重新编号或映射,可以减少数据的规模和复杂度,同时保留数据的相对关系。
离散化的应用场景
区间查询与更新:在线段树或树状数组中,离散化可以将大范围的坐标映射到小范围的索引,节省空间和时间。例如LeetCode 315. 计算右侧小于当前元素的个数。
统计与计数:在统计频次或计数问题中,离散化可以减少数据规模,提高效率。例如LeetCode 493. 翻转对。
图论与网络流:在图论问题中,离散化可以将大范围的节点编号映射到小范围,简化问题。例如LeetCode 685. 冗余连接 II。
几何问题:在几何问题中,离散化可以将连续的坐标映射到离散的网格点,简化计算。例如LeetCode 850. 矩形面积 II。
离散化的实现步骤
数据收集:收集所有需要离散化的数据点(如坐标、值等)。
排序与去重:对数据点进行排序,并去除重复值。
映射与编号:将原始数据点映射到离散的索引(通常从 0 或 1 开始)。
应用离散化:使用映射后的索引代替原始数据点进行计算或存储。
离散化的复杂度分析
时间复杂度:
排序与去重:O(nlogn),其中 n 是数据点的数量。
映射与编号:O(n)。
空间复杂度:
存储映射关系:O(n)。
离散化的优化技巧
二分查找优化:在映射过程中,使用二分查找快速确定数据点的离散索引。
动态离散化:在动态数据流中,实时维护离散化映射。
多维度离散化:在多维数据中,分别对每个维度进行离散化。
常见误区与调试技巧
- 误区一:离散化会丢失信息
- 澄清:离散化仅改变数据的表示方式,不改变数据的相对关系。
- 误区二:离散化适用于所有问题
- 澄清:离散化适用于数据范围大但数据点稀疏的问题,对于密集数据可能不适用。
- 调试方法
- 打印映射关系:在离散化后输出映射关系,检查是否正确。
- 可视化数据分布:绘制原始数据和离散化后的数据分布,检查一致性。
代码模版(C++)
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
template<class T>
class Dicretizer {
private:
vector<T>m_data;
public:
void addData(T v);
void process();
int getData(T v) const;
int size()const;
};
template<class T>
void Dicretizer<T>::addData(T v) {
m_data.push_back(v);
}
template<class T>
void Dicretizer<T>::process() {
sort(m_data.begin(), m_data.end());
int lastId = 0;
for (int i = 1; i < m_data.size(); i++) {
T t = m_data[i];
if (t != m_data[lastId]) {
m_data[++lastId] = t;
}
}
while (lastId < m_data.size() - 1) {
m_data.pop_back();
}
}
template<class T>
int Dicretizer<T>::getData(T v) const {
int l = -1, r = m_data.size();
while (l + 1 < r) {
int mid = (l + r) / 2;
if (m_data[mid] >= v)r = mid;
else l = mid;
}
if (m_data[r] != v || r == m_data.size())return -1;
return r;
}
template<class T>
int Dicretizer<T>::size() const {
return m_data.size();
}
int main() {
return 0;
}
经典例题
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
template<class T>
class Dicretizer {
private:
vector<T>m_data;
public:
void addData(T v);
void process();
int get(T v) const;
int size() const;
};
template<class T>
void Dicretizer<T>::addData(T v) {
m_data.push_back(v);
}
template<class T>
void Dicretizer<T>::process() {
sort(m_data.begin(), m_data.end());
int lastId = 0;
for (int i = 1; i < m_data.size(); i++) {
T x = m_data[i];
if (x != m_data[lastId]) {
m_data[++lastId] = x;
}
}
while (lastId < m_data.size() - 1) {
m_data.pop_back();
}
}
template<class T>
int Dicretizer<T>::get(T v) const {
int l = -1, r = m_data.size();
while (l + 1 < r) {
int mid = (l + r) / 2;
if (m_data[mid] >= v)r = mid;
else l = mid;
}
if (r == m_data.size() || m_data[r] != v)return -1;
return r;
}
template<class T>
int Dicretizer<T>::size() const {
return m_data.size();
}
int a[100001];
int main() {
int n;
cin >> n;
while (n--) {
int s;
cin >> s;
Dicretizer<int> d;
for (int i = 0; i < s; i++) {
int x;
cin >> x;
a[i] = x;
d.addData(x);
}
d.process();
for (int i = 0; i < s; i++) {
cout << d.get(a[i]) + 1 << ' '; //因为数组下标是从0开始,所以加一
}
cout << endl;
}
return 0;
}
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
template<class T>
class Dicretizer {
private:
vector<T>m_data;
public:
void addData(T v);
void process();
int getData(T v) const;
int size()const;
};
template<class T>
void Dicretizer<T>::addData(T v) {
m_data.push_back(v);
}
template<class T>
void Dicretizer<T>::process() {
sort(m_data.begin(), m_data.end());
int lastId = 0;
for (int i = 1; i < m_data.size(); i++) {
T t = m_data[i];
if (t != m_data[lastId]) {
m_data[++lastId] = t;
}
}
while (lastId < m_data.size() - 1) {
m_data.pop_back();
}
}
template<class T>
int Dicretizer<T>::getData(T v) const {
int l = -1, r = m_data.size();
while (l + 1 < r) {
int mid = (l + r) / 2;
if (m_data[mid] >= v)r = mid;
else l = mid;
}
if (m_data[r] != v || r == m_data.size())return -1;
return r;
}
template<class T>
int Dicretizer<T>::size() const {
return m_data.size();
}
#define maxn 200001
struct HB {
int h, b;
}hb[maxn];
int k[maxn], maxv[maxn]; //k作为询问数组,maxv代表限高的最大美丽值
bool cmp(const HB& a, const HB& b) {
return a.h < b.h;
}
int main() {
Dicretizer<int>d;
int n, q;
cin >> n >> q;
for (int i = 0; i < n; i++) {
cin >> hb[i].h;
d.addData(hb[i].h);
}
for (int i = 0; i < n; i++) {
cin >> hb[i].b;
}
for (int i = 0; i < q; i++) {
cin >> k[i];
d.addData(k[i]);
}
d.process();
for (int i = 0; i < n; i++) {
hb[i].h = d.getData(hb[i].h);
}
for (int i = 0; i < q; i++) {
k[i] = d.getData(k[i]);
}
sort(hb, hb + n, cmp);
maxv[d.size()] = -1;
int j = n - 1;
for (int i = d.size() - 1; i >= 0; i--) {
maxv[i] = maxv[i + 1];
while(j >= 0 && hb[j].h == i) {
if(hb[j].b > maxv[i]) {
maxv[i] = hb[j].b;
}
j--;
}
}
for (int i = 0; i < q; i++) {
int x = k[i];
cout << maxv[x] << endl;
}
return 0;
}
总结与学习建议
核心总结:离散化是一种将连续数据映射到离散值的技术,适用于处理大规模或稀疏数据。通过排序、去重和映射,可以高效实现离散化。
学习建议:
- 分类刷题:按问题类型集中练习(如区间查询、统计计数、几何问题)。
- 理解算法原理:掌握离散化的实现步骤和优化技巧。
- 手写模拟过程:在纸上模拟离散化的过程,加深直观理解。
热门推荐
德清县公安局雷甸派出所成功劝阻一起电信网络诈骗,挽回群众损失
进制的基本介绍以及进制转换和计算
AI又一突破!谷歌推出“咳嗽模型”HeAR,一声咳嗽,告知你的身体健康
科学饮食锻炼,暑期助你轻松瘦
科学运动——糖友的“良药”
新二批055大驱呼之欲出,四大武器全面升级,或成美航母克星?
国家电网首个基于ESG理念的省级电网企业社会责任报告在浙发布
一个比特币需要多久才能挖到?深入分析比特币挖矿的难度与时间因素
卖沙棘的宇航人疑涉传销,IPO前产品铅浓度超标被罚
订婚男方要准备6样礼?易学者杨道明风水布局揭秘传统定亲习俗背后的深意
走进安哥拉:解锁非洲西南部投资新蓝海
扩大研究生培养规模是为了延缓就业吗?
张文宏、雷军、靳东深受其害,AI仿冒名人到底有多真?
中国历史上25位历史人物评价
有效解决手机内存不足问题的实用技巧与管理方法总结
当阳气不足时,身体会发出4个提醒信号,千万别忽视
肺穿孔:从症状到治疗的全面解析
武书连2025中国大学排名发布:清华浙大北大位列综合实力前三
胸痛、胸闷、心慌:心血管疾病的预警信号不容忽视
九首经典诗词,让你学会笑看人生
集合全球9大长寿饮食习惯!2种浓味食物通便又防癌
老子:道家哲学的奠基人与其不朽之作道德经
抖音电商首份知识产权保护观察报告出炉
跨省户口迁移流程是怎样的?迁移过程中有哪些关键步骤和注意事项?
桂枝汤的历史与现代应用探析
精灵:力量、传说与词源
农村兴起“一日丧”,气的老人直骂“不孝”,真的有违孝道吗?
女生专用:茶叶水洗脸的护肤秘诀与实践
复旦大学附属中山医院厦门医院成功完成首批PADN手术
IM吃贴水策略一点思考