簡介
離散化本質上可以看成是一種 哈希 ,其保證數據在哈希以后仍然保持原來的全/偏序關系。
通俗地講,就是當我們只關心數據的大小關系時,用排名代替原數據進行處理的一種預處理方法。離散化本質上是一種哈希,它在保持原序列大小關系的前提下把其映射成正整數。當原數據很大或含有負數、小數時,難以表示為數組下標,一些算法和數據結構(如BIT)無法運作,這時我們就可以考慮將其離散化。
用來離散化的可以是大整數、浮點數、字符串……等等。
實現
C++ 離散化有現成的 STL 算法:
離散化數組
將一個數組離散化,並進行查詢是比較常用的應用場景:
// a[i] 為初始數組,下標范圍為 [1, n]
// len 為離散化后數組的有效長度
std::sort(a + 1, a + 1 + n);
len = std::unique(a + 1, a + n + 1) - a -
1; // 離散化整個數組的同時求出離散化后本質不同數的個數。
在完成上述離散化之后可以使用 std::lower_bound
函數查找離散化之后的排名(即新編號):
std::lower_bound(a + 1, a + len + 1, x) - a; // 查詢 x 離散化后對應的編號
同樣地,我們也可以對 vector
進行離散化:
// std::vector<int> a, b; // b 是 a 的一個副本
std::sort(a.begin(), a.end());
a.erase(std::unique(a.begin(), a.end()), a.end());
for (int i = 0; i < n; ++i)
b[i] = std::lower_bound(a.begin(), a.end(), b[i]) - a.begin();
實際演示:
現在我們有序列 A=[10, 23, 35, 3, -40, 3]
。我們先復制一個同樣的序列:
int C[N];
memcpy(C, A, sizeof(A));
排序,去重:
sort(C, C + n);
int l = unique(C, C + n) - C; // l為不重復元素的數量
std::unique()的返回值是一個迭代器(對於數組來說就是指針了),它表示去重后容器中不重復序列的最后一個元素的下一個元素。所以可以這樣作差求得不重復元素的數量。現在我們有C=[-40, 3, 10, 23, 35]。
再用一個數組,儲存A中每個元素在C中的排名:
int L[MAXN];
for (int i = 0; i < n; ++i)
L[i] = lower_bound(C, C + l, A[i]) - C + 1; // 二分查找
這樣我們就實現了原序列的離散化。得到 L=[3, 4, 5, 2, 1, 2]
。
因為排序和n次二分查找的復雜度都是 \(\mathcal{O}(n\ log\ n)\) ,所以離散化的復雜度也是 \(\mathcal{O}(n\ log\ n)\) 。完整代碼很短:
int C[N], L[N];
// 在main函數中...
memcpy(C, A, sizeof(A)); // 復制
sort(C, C + n); // 排序
int l = unique(C, C + n) - C; // 去重
for (int i = 0; i < n; ++i)
L[i] = lower_bound(C, C + l, A[i]) - C + 1; // 查找
離散化也不一定要從小到大排序,有時候也需要從大到小。這時在排序和查找時相應地加上greater<int>()
就可以了。