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