常用非比較類排序算法

前言

本篇完全轉(zhuǎn)載于常用排序算法總結(jié)(二)。

其中有部分代碼進(jìn)行了更改,更改成java語(yǔ)言。

非對(duì)比類排序算法

非比較排序算法:計(jì)數(shù)排序,基數(shù)排序,桶排序。在一定條件下,它們的時(shí)間復(fù)雜度可以達(dá)到O(n)。

計(jì)數(shù)排序

計(jì)數(shù)排序用到一個(gè)額外的計(jì)數(shù)數(shù)組C,根據(jù)數(shù)組C來(lái)將原數(shù)組A中的元素排到正確的位置。

通俗地理解,例如有10個(gè)年齡不同的人,假如統(tǒng)計(jì)出有8個(gè)人的年齡不比小明大(即小于等于小明的年齡,這里也包括了小明),那么小明的年齡就排在第8位,通過(guò)這種思想可以確定每個(gè)人的位置,也就排好了序。當(dāng)然,年齡一樣時(shí)需要特殊處理(保證穩(wěn)定性):通過(guò)反向填充目標(biāo)數(shù)組,填充完畢后將對(duì)應(yīng)的數(shù)字統(tǒng)計(jì)遞減,可以確保計(jì)數(shù)排序的穩(wěn)定性。

計(jì)數(shù)排序的步驟如下:

  • 統(tǒng)計(jì)數(shù)組A中每個(gè)值A(chǔ)[i]出現(xiàn)的次數(shù),存入C[A[i]]
  • 從前向后,使數(shù)組C中的每個(gè)值等于其與前一項(xiàng)相加,這樣數(shù)組C[A[i]]就變成了代表數(shù)組A中小于等于A[i]的元素個(gè)數(shù)
  • 反向填充目標(biāo)數(shù)組temp:將數(shù)組元素A[i]放在數(shù)組B的第C[A[i]]個(gè)位置(下標(biāo)為C[A[i]] - 1),每放一個(gè)元素就將C[A[i]]遞減
// 分類 ------------ 內(nèi)部非比較排序
// 數(shù)據(jù)結(jié)構(gòu) --------- 數(shù)組
// 最差時(shí)間復(fù)雜度 ---- O(n + k)
// 最優(yōu)時(shí)間復(fù)雜度 ---- O(n + k)
// 平均時(shí)間復(fù)雜度 ---- O(n + k)
// 所需輔助空間 ------ O(n + k)
// 穩(wěn)定性 ----------- 穩(wěn)定
public static void countingSort(int A[], int n) {
    int[] C = new int[99];
    int[] temp = new int[n];

    for (int i : A) {
        C[i]++;
    }

    for (int i = 1; i < C.length; i++) {
        C[i] = C[i] + C[i - 1];
    }

    for (int i = n - 1; i >= 0; i--) {
        temp[--C[A[i]]] = A[i];
    }

    for (int i = 0; i < temp.length; i++) {
        A[i] = temp[i];
    }
}

計(jì)數(shù)排序的時(shí)間復(fù)雜度和空間復(fù)雜度與數(shù)組A的數(shù)據(jù)范圍(A中元素的最大值與最小值的差加上1)有關(guān),因此對(duì)于數(shù)據(jù)范圍很大的數(shù)組,計(jì)數(shù)排序需要大量時(shí)間和內(nèi)存。

例如:對(duì)0到99之間的數(shù)字進(jìn)行排序,計(jì)數(shù)排序是最好的算法,然而計(jì)數(shù)排序并不適合按字母順序排序人名,將計(jì)數(shù)排序用在基數(shù)排序算法中,能夠更有效的排序數(shù)據(jù)范圍很大的數(shù)組。

單純的計(jì)數(shù)排序,只適用于非負(fù)數(shù)數(shù)列排序,而且temp數(shù)組的大小與A中元素的最大值和最小值差有關(guān)系,不確定性因素很強(qiáng)。

基數(shù)排序

基數(shù)排序?qū)⑺写容^正整數(shù)統(tǒng)一為同樣的數(shù)位長(zhǎng)度,數(shù)位較短的數(shù)前面補(bǔ)零。然后,從最低位開(kāi)始進(jìn)行基數(shù)為10的計(jì)數(shù)排序,一直到最高位計(jì)數(shù)排序完后,數(shù)列就變成一個(gè)有序序列(利用了計(jì)數(shù)排序的穩(wěn)定性)。

// 分類 ------------- 內(nèi)部非比較排序
// 數(shù)據(jù)結(jié)構(gòu) ---------- 數(shù)組
// 最差時(shí)間復(fù)雜度 ---- O(n * dn)
// 最優(yōu)時(shí)間復(fù)雜度 ---- O(n * dn)
// 平均時(shí)間復(fù)雜度 ---- O(n * dn)
// 所需輔助空間 ------ O(n * dn)
// 穩(wěn)定性 ----------- 穩(wěn)定
public static void lsdRadixSort(int arr[], int n) {
    int dn = 3; // 假設(shè)最高是三位數(shù)排序
    for (int d = 1; d <= dn; d++) {
        countingSort(arr, n, d);
    }
}

// 計(jì)數(shù)排序
public static void countingSort(int arr[], int n, int d) {
    int[] C = new int[10]; // 0-9共十個(gè)數(shù)
    int[] temp = new int[n];

    for (int i : arr) {
        C[getDigit(i, d)]++;
    }

    for (int i = 1; i < C.length; i++) {
        C[i] = C[i] + C[i - 1];
    }

    for (int i = n - 1; i >= 0; i--) {
        int digit = getDigit(arr[i], d);
        temp[--C[digit]] = arr[i];
    }

    for (int i = 0; i < temp.length; i++) {
        arr[i] = temp[i];
    }
}

// 獲取第n位數(shù)
public static int getDigit(int x, int d) {
    int[] radix = {1, 1, 10, 100};
    return (x/radix[d]) % 10;
}

下圖給出了對(duì){ 329, 457, 657, 839, 436, 720, 355 }進(jìn)行基數(shù)排序的簡(jiǎn)單演示過(guò)程

基數(shù)排序.jpg

基數(shù)排序的時(shí)間復(fù)雜度是O(n * dn),其中n是待排序元素個(gè)數(shù),dn是數(shù)字位數(shù)。這個(gè)時(shí)間復(fù)雜度不一定優(yōu)于O(nlogn),dn的大小取決于數(shù)字位的選擇(比如比特位數(shù)),和待排序數(shù)據(jù)所屬數(shù)據(jù)類型的全集的大??;dn決定了進(jìn)行多少輪處理,而n是每輪處理的操作數(shù)目。

如果考慮和比較排序進(jìn)行對(duì)照,基數(shù)排序的形式復(fù)雜度雖然不一定更小,但由于不進(jìn)行比較,因此其基本操作的代價(jià)較小,而且如果適當(dāng)?shù)倪x擇基數(shù),dn一般不大于log n,所以基數(shù)排序一般要快過(guò)基于比較的排序,比如快速排序。由于整數(shù)也可以表達(dá)字符串(比如名字或日期)和特定格式的浮點(diǎn)數(shù),所以基數(shù)排序并不是只能用于整數(shù)排序。

桶排序

桶排序也叫箱排序。工作的原理是將數(shù)組元素映射到有限數(shù)量個(gè)桶里,利用計(jì)數(shù)排序可以定位桶的邊界,每個(gè)桶再各自進(jìn)行桶內(nèi)排序(使用其它排序算法或以遞歸方式繼續(xù)使用桶排序)。

// 分類 ------------- 內(nèi)部非比較排序
// 數(shù)據(jù)結(jié)構(gòu) --------- 數(shù)組
// 最差時(shí)間復(fù)雜度 ---- O(nlogn)或O(n^2),只有一個(gè)桶,取決于桶內(nèi)排序方式
// 最優(yōu)時(shí)間復(fù)雜度 ---- O(n),每個(gè)元素占一個(gè)桶
// 平均時(shí)間復(fù)雜度 ---- O(n),保證各個(gè)桶內(nèi)元素個(gè)數(shù)均勻即可
// 所需輔助空間 ------ O(n + bn)
// 穩(wěn)定性 ----------- 穩(wěn)定

/* 本程序用數(shù)組模擬桶 */
const int bn = 5;    // 這里排序[0,49]的元素,使用5個(gè)桶就夠了,也可以根據(jù)輸入動(dòng)態(tài)確定桶的數(shù)量
int C[bn];           // 計(jì)數(shù)數(shù)組,存放桶的邊界信息

void InsertionSort(int A[], int left, int right)
{
    for (int i = left + 1; i <= right; i++)  // 從第二張牌開(kāi)始抓,直到最后一張牌
    {
        int get = A[i];
        int j = i - 1;
        while (j >= left && A[j] > get)
        {
            A[j + 1] = A[j];
            j--;
        }
        A[j + 1] = get;
    }
}

int MapToBucket(int x)
{
    return x / 10;    // 映射函數(shù)f(x),作用相當(dāng)于快排中的Partition,把大量數(shù)據(jù)分割成基本有序的數(shù)據(jù)塊
}

void CountingSort(int A[], int n)
{
    for (int i = 0; i < bn; i++)
    {
        C[i] = 0;
    }
    for (int i = 0; i < n; i++)     // 使C[i]保存著i號(hào)桶中元素的個(gè)數(shù)
    {
        C[MapToBucket(A[i])]++;
    }
    for (int i = 1; i < bn; i++)    // 定位桶邊界:初始時(shí),C[i]-1為i號(hào)桶最后一個(gè)元素的位置
    {
        C[i] = C[i] + C[i - 1];
    }
    int *B = (int *)malloc((n) * sizeof(int));
    for (int i = n - 1; i >= 0; i--)// 從后向前掃描保證計(jì)數(shù)排序的穩(wěn)定性(重復(fù)元素相對(duì)次序不變)
    {
        int b = MapToBucket(A[i]);  // 元素A[i]位于b號(hào)桶
        B[--C[b]] = A[i];           // 把每個(gè)元素A[i]放到它在輸出數(shù)組B中的正確位置上
                                    // 桶的邊界被更新:C[b]為b號(hào)桶第一個(gè)元素的位置
    }
    for (int i = 0; i < n; i++)
    {
        A[i] = B[i];
    }
    free(B);
}

void BucketSort(int A[], int n)
{
    CountingSort(A, n);          // 利用計(jì)數(shù)排序確定各個(gè)桶的邊界(分桶)
    for (int i = 0; i < bn; i++) // 對(duì)每一個(gè)桶中的元素應(yīng)用插入排序
    {
        int left = C[i];         // C[i]為i號(hào)桶第一個(gè)元素的位置
        int right = (i == bn - 1 ? n - 1 : C[i + 1] - 1);// C[i+1]-1為i號(hào)桶最后一個(gè)元素的位置
        if (left < right)        // 對(duì)元素個(gè)數(shù)大于1的桶進(jìn)行桶內(nèi)插入排序
            InsertionSort(A, left, right);
    }
}

下圖給出了對(duì){ 29, 25, 3, 49, 9, 37, 21, 43 }進(jìn)行桶排序的簡(jiǎn)單演示過(guò)程

桶排序.jpg

桶排序不是比較排序,不受到O(nlogn)下限的影響,它是鴿巢排序的一種歸納結(jié)果,當(dāng)所要排序的數(shù)組值分散均勻的時(shí)候,桶排序擁有線性的時(shí)間復(fù)雜度。

計(jì)數(shù)排序是根據(jù)統(tǒng)計(jì)量來(lái)進(jìn)行排序,不進(jìn)行比較,適用于更廣的場(chǎng)景。

基數(shù)排序是對(duì)計(jì)數(shù)排序的優(yōu)化,因?yàn)橛?jì)數(shù)排序的空間負(fù)責(zé)度取決于數(shù)列中最大數(shù)值和最小數(shù)值的差,如果相差非常大,耗費(fèi)空間就非常多。

桶排序也可以視為對(duì)計(jì)數(shù)排序的優(yōu)化,只要桶的間隔選擇的好,時(shí)間復(fù)雜度能得到很大的優(yōu)化。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評(píng)論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,559評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,442評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,835評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,581評(píng)論 6 412
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,922評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,096評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,639評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,374評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,591評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,789評(píng)論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,196評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,524評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,322評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,554評(píng)論 2 379

推薦閱讀更多精彩內(nèi)容

  • Ba la la la ~ 讀者朋友們,你們好啊,又到了冷鋒時(shí)間,話不多說(shuō),發(fā)車! 1.冒泡排序(Bub...
    王飽飽閱讀 1,809評(píng)論 0 7
  • 排序算法說(shuō)明 (1)排序的定義:對(duì)一序列對(duì)象根據(jù)某個(gè)關(guān)鍵字進(jìn)行排序; 輸入:n個(gè)數(shù):a1,a2,a3,…,an輸出...
    BULL_DEBUG閱讀 796評(píng)論 0 3
  • 總結(jié)一下常見(jiàn)的排序算法。 排序分內(nèi)排序和外排序。內(nèi)排序:指在排序期間數(shù)據(jù)對(duì)象全部存放在內(nèi)存的排序。外排序:指在排序...
    jiangliang閱讀 1,369評(píng)論 0 1
  • 大家好,我是軍哥,筆名劉慕青城。 今天再跟你聊聊「寫(xiě)作那點(diǎn)事兒」。 以下為正文: 一、好作品都是類似的 昨天做賊似...
    牛爸愛(ài)學(xué)習(xí)閱讀 533評(píng)論 2 5