W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
先來看一個簡單的例子。給定一個長度為 nums
,其中的元素都是“非負整數(shù)”,計數(shù)排序的整體流程如圖
11-16 所示。
counter
。counter
統(tǒng)計 nums
中各數(shù)字的出現(xiàn)次數(shù),其中 counter[num]
對應數(shù)字 num
的出現(xiàn)次數(shù)。統(tǒng)計方法很簡單,只需遍歷 nums
(設當前數(shù)字為 num
),每輪將 counter[num]
增加 counter
的各個索引天然有序,因此相當于所有數(shù)字已經(jīng)被排序好了。接下來,我們遍歷 counter
,根據(jù)各數(shù)字的出現(xiàn)次數(shù),將它們按從小到大的順序填入 nums
即可。
圖 11-16 計數(shù)排序流程
counting_sort.cpp
/* 計數(shù)排序 */
// 簡單實現(xiàn),無法用于排序?qū)ο?void countingSortNaive(vector<int> &nums) {
// 1. 統(tǒng)計數(shù)組最大元素 m
int m = 0;
for (int num : nums) {
m = max(m, num);
}
// 2. 統(tǒng)計各數(shù)字的出現(xiàn)次數(shù)
// counter[num] 代表 num 的出現(xiàn)次數(shù)
vector<int> counter(m + 1, 0);
for (int num : nums) {
counter[num]++;
}
// 3. 遍歷 counter ,將各元素填入原數(shù)組 nums
int i = 0;
for (int num = 0; num < m + 1; num++) {
for (int j = 0; j < counter[num]; j++, i++) {
nums[i] = num;
}
}
}
計數(shù)排序與桶排序的聯(lián)系
從桶排序的角度看,我們可以將計數(shù)排序中的計數(shù)數(shù)組 counter
的每個索引視為一個桶,將統(tǒng)計數(shù)量的過程看作是將各個元素分配到對應的桶中。本質(zhì)上,計數(shù)排序是桶排序在整型數(shù)據(jù)下的一個特例。
細心的同學可能發(fā)現(xiàn),如果輸入數(shù)據(jù)是對象,上述步驟 ?3.? 就失效了。假設輸入數(shù)據(jù)是商品對象,我們想要按照商品價格(類的成員變量)對商品進行排序,而上述算法只能給出價格的排序結果。
那么如何才能得到原數(shù)據(jù)的排序結果呢?我們首先計算 counter
的“前綴和”。顧名思義,索引 i
處的前綴和 prefix[i]
等于數(shù)組前 i
個元素之和:
前綴和具有明確的意義,prefix[num] - 1
代表元素 num
在結果數(shù)組 res
中最后一次出現(xiàn)的索引。這個信息非常關鍵,因為它告訴我們各個元素應該出現(xiàn)在結果數(shù)組的哪個位置。接下來,我們倒序遍歷原數(shù)組 nums
的每個元素 num
,在每輪迭代中執(zhí)行以下兩步。
num
填入數(shù)組 res
的索引 prefix[num] - 1
處。prefix[num]
減小 num
的索引。遍歷完成后,數(shù)組 res
中就是排序好的結果,最后使用 res
覆蓋原數(shù)組 nums
即可。圖 11-17 展示了完整的計數(shù)排序流程。
圖 11-17 計數(shù)排序步驟
計數(shù)排序的實現(xiàn)代碼如下所示。
counting_sort.cpp
/* 計數(shù)排序 */
// 完整實現(xiàn),可排序?qū)ο?,并且是穩(wěn)定排序
void countingSort(vector<int> &nums) {
// 1. 統(tǒng)計數(shù)組最大元素 m
int m = 0;
for (int num : nums) {
m = max(m, num);
}
// 2. 統(tǒng)計各數(shù)字的出現(xiàn)次數(shù)
// counter[num] 代表 num 的出現(xiàn)次數(shù)
vector<int> counter(m + 1, 0);
for (int num : nums) {
counter[num]++;
}
// 3. 求 counter 的前綴和,將“出現(xiàn)次數(shù)”轉(zhuǎn)換為“尾索引”
// 即 counter[num]-1 是 num 在 res 中最后一次出現(xiàn)的索引
for (int i = 0; i < m; i++) {
counter[i + 1] += counter[i];
}
// 4. 倒序遍歷 nums ,將各元素填入結果數(shù)組 res
// 初始化數(shù)組 res 用于記錄結果
int n = nums.size();
vector<int> res(n);
for (int i = n - 1; i >= 0; i--) {
int num = nums[i];
res[counter[num] - 1] = num; // 將 num 放置到對應索引處
counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引
}
// 使用結果數(shù)組 res 覆蓋原數(shù)組 nums
nums = res;
}
nums
和遍歷 counter
,都使用線性時間。一般情況下
res
和 counter
。res
中填充元素的順序是“從右向左”的,因此倒序遍歷 nums
可以避免改變相等元素之間的相對位置,從而實現(xiàn)穩(wěn)定排序。實際上,正序遍歷 nums
也可以得到正確的排序結果,但結果是非穩(wěn)定的。看到這里,你也許會覺得計數(shù)排序非常巧妙,僅通過統(tǒng)計數(shù)量就可以實現(xiàn)高效的排序工作。然而,使用計數(shù)排序的前置條件相對較為嚴格。
計數(shù)排序只適用于非負整數(shù)。若想要將其用于其他類型的數(shù)據(jù),需要確保這些數(shù)據(jù)可以被轉(zhuǎn)換為非負整數(shù),并且在轉(zhuǎn)換過程中不能改變各個元素之間的相對大小關系。例如,對于包含負數(shù)的整數(shù)數(shù)組,可以先給所有數(shù)字加上一個常數(shù),將全部數(shù)字轉(zhuǎn)化為正數(shù),排序完成后再轉(zhuǎn)換回去即可。
計數(shù)排序適用于數(shù)據(jù)量大但數(shù)據(jù)范圍較小的情況。比如,在上述示例中
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: