什麼是基數排序?

2021-03-02 趣談編程

老讀者可能比較熟悉,剛開始的時候寫了一個排序算法系列,把常見的排序算法都寫了,有興趣的可以在公眾號內的目錄菜單欄中選擇數據結構與算法查看。

但是還是有少數的排序算法沒寫,下面的一篇就是。這篇文章用漫畫的形式講解了基數排序,通俗易懂。

—————  第二天  —————

————————————

什麼是計數排序呢?讓我們舉例說明一下。

給定20個隨機整數的值如下:

9,3,5,4,9,1,2,7,8,1,3,6,5,3,4,0,10,9 ,7,9

如何最快地把這些無序的隨機整數排序?

由於這些整數的範圍是從0到10這11個數,我們可以創建一個長度11的空數組,數組從0到10的下標,對應著待排序的隨機整數值0到10:

接下來遍歷這個無序的隨機數列,每一個整數按照其值對號入座,對應數組下標的元素進行加1操作。

比如第一個整數是9,那麼數組下標為9的元素加1:

第二個整數是3,那麼數組下標為3的元素加1:

繼續遍歷數列並修改數組.

最終,數列遍歷完畢時,數組的狀態如下:

數組每一個下標位置的值,代表了數列中對應整數出現的次數。

有了這個「統計結果」,排序就很簡單了。直接遍歷數組,輸出數組元素的下標值,元素的值是幾,就輸出幾次:

0,1,1,2,3,3,3,4,4,5,5,6,7,7,8,9,9,9,9,10

顯然,這個輸出的數列已經是有序的了。

這就是計數排序的樸素版本


為了實現穩定排序(排序後,相等元素原本的先後順序不變),真正的計數排序要稍微複雜一些,感興趣的小夥伴可以讀一讀這篇:

漫畫:什麼是計數排序?

計數排序有什麼局限呢?讓我們看兩個特殊的需求:

需求A,為一組給定的手機號排序:

18914021920

13223132981

13566632981

13660891039

13361323035

...

...

按照計數排序的思路,我們要根據手機號的取值範圍,創建一個空數組。

可是,11位手機號有多少種組合?恐怕要建立一個大得不可想像的數組,才能裝下所有可能出現的11位手機號!

需求B,為一組英文單詞排序:

banana

apple

orange

peach

cherry

...

...

計數排序適合的場景是對整數做排序,如果遇到英文單詞,就無能為力了。

如何有效處理諸如手機號、英文單詞等複雜元素的排序呢?僅僅靠一次計數排序很難實現。

這時候,我們不妨把排序工作拆分成多個階段,每一個階段只根據一個字符進行計數排序,一共排序k輪(k是元素長度)。

或許這樣的描述有些抽象,我們來舉一個例子。

數組中有若干個字符串元素,每個字符串元素都是由三個英文字母組成:

bda,cfd,qwe,yui,abc,rrr,uee

如何將這些字符串按照字母順序排序呢?

由於每個字符串的長度是3個字符,我們可以把排序工作拆分成3輪:

第一輪:按照最低位字符排序。排序過程使用計數排序,把字母的ascii碼對應到數組下標,第一輪排序結果如下:

第二輪:在第一輪排序結果的基礎上,按照第二位字符排序。

需要注意的是,這裡使用的計數排序必須是穩定排序,這樣才能保證第一輪排出的先後順序在第二輪還能繼續保持。

比如在第一輪排序後,元素uue在元素yui之前。那麼第二輪排序時,兩者的第二位字符雖然同樣是u,但先後順序萬萬不能變,否則第一輪排序就白做了。

第三輪:在第二輪排序結果的基礎上,按照最高位字符排序。

如此一來,這些字符串的順序就排好了。

像這樣把字符串元素按位拆分,每一位進行一次計數排序的算法,就是基數排序(Radix Sort)

基數排序既可以從高位優先進行排序(Most Significant Digit first,簡稱MSD),也可以從低位優先進行排序(Least Significant Digit first,簡稱LSD)。

剛才我們所舉的例子,就是典型的LSD方式的基數排序。

什麼意思呢?比如給定如下幾個單詞:

banana

apple

orange

ape

he

這裡最長的單詞有6個字符,其餘不足6個字符的單詞在末尾補0即可:

banana

apple0

orange

ape000

he0000

在排序時,我們把字符0當做是比a更小的字符,排序結果如下:

ape000

apple0

banana

he0000

orange

//ascii碼的取值範圍

public static final int ASCII_RANGE = 128;

public static String[] radixSort(String[] array,int maxLength)

{

//排序結果數組,用於存儲每一次按位排序的臨時結果

String[] sortedArray = new String[array.length];

//從個位開始比較,一直比較到最高位

for(int k=maxLength-1;k>=0;k--)

{

//計數排序的過程,分成三步:

//1.創建輔助排序的統計數組,並把待排序的字符對號入座,

        //這裡為了代碼簡潔,直接使用ascii碼範圍作為數組長度

int[] count = new int[ASCII_RANGE];

for(int i=0;i<array.length;i++)

{

int index = getCharIndex(array[i],k);

count[index]++;

}

//2.統計數組做變形,後面的元素等於前面的元素之和

for(int i=1;i<count.length;i++)

{

count[i] = count[i] + count[i-1];

}

//3.倒序遍歷原始數列,從統計數組找到正確位置,輸出到結果數組

for(int i=array.length-1;i>=0;i--) {

int index = getCharIndex(array[i],k);

int sortedIndex = count[index]-1;

sortedArray[sortedIndex] = array[i];

count[index]--;

}

//下一輪排序需要以上一輪的排序結果為基礎,因此把結果複製給array

array = sortedArray.clone();

}

return array;

}

//獲取字符串第k位字符所對應的ascii碼序號

private static int getCharIndex(String str, int k){

//如果字符串長度小於k,直接返回0,相當於給不存在的位置補0

if(str.length() < k+1){

return 0;

}

return str.charAt(k);

}

public static void main(String[] args)

{

String[] array = {"qd","abc", "qwe","hhh","a","cws", "ope"};

System.out.println(Arrays.toString(radixSort(array, 3)));

}

這段代碼基於一個大循環來實現,循環進行k次,k就是數組中最長字符串元素的字符數。

在循環體內,執行的是計數排序的邏輯。這個穩定的計數排序算法不太好理解,在小灰往期的漫畫中有進行詳細講解(漫畫:什麼是計數排序?)。


—————END—————

讓天下沒有難懂的技術

覺得不錯,點個在看唄

相關焦點

  • 動畫:什麼是基數排序?
    基數排序 與基於比較的排序算法(歸併排序、堆排序、快速排序、冒泡排序、插入排序等等)相比,基於比較的排序算法的時間複雜度最好也就是 基數排序的總體思想就是從待排序數組當中,元素的最低有效位到最高有效位 逐位 進行比較排序;此外,基數排序使用計數排序作為一個排序的子過程。下面我們就以數組  [170, 45, 75, 90, 802, 24, 2, 66] ,快樂學習基數排序!
  • 動畫 | 什麼是基數排序?
    基數排序和計數排序一樣無需進行比較和交換,和桶排序一樣利用分布和收集兩種基本操作進行排序。基數排序是把每一個元素拆成多個關鍵字,一個關鍵字可以在每一個元素上同等的位置進行計數排序,一個元素拆成多個關鍵字可以看作是要進行幾輪分桶,以一個元素最長的長度為準。 基數排序可以看成多(單)關鍵字的排序,可以想像成桶排序那樣分桶排序,也可以像計數排序那樣歸約化分治。 基數排序的思想是將待排序序列中的每組關鍵字進行桶排序。
  • 基數排序
    號內回複數據結構,獲取整套算法視頻本文作者:靜默虛空歡迎點擊下方閱讀原文要點基數排序與本系列前面講解的七種排序方法都不同它是根據關鍵字中各位的值,通過對排序的N個元素進行若干趟「分配」與「收集」來實現排序的。 不妨通過一個具體的實例來展示一下,基數排序是如何進行的。 設有一個初始序列為: R {50, 123, 543, 187, 49, 30, 0, 2, 11, 100}。我們知道,任何一個阿拉伯數,它的各個位數上的基數都是以0~9來表示的。
  • Python實現基數排序
    基數排序將數據按位進行分桶,然後將桶中的數據合併。每次分桶只關注其中一位數據,其他位的數據不管,最大的數據有多少位,就進行多少次分桶和合併。基數排序除了用於對整數進行排序,也可以用於對浮點數、字符串進行排序。基數排序可以分為最高位優先法和最低位優先法,兩種方法的結果相同。最高位優先(Most Significant Digit first)法,簡稱MSD法。
  • 排序算法---基數排序,動圖詳解,Java實現!
    基數排序「基本思想:」將所有待比較數值統一為同樣的數位長度,數位較短的數前面補零。從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以後, 數列就變成一個有序序列。int[] bucketElementCounts = new int[10]; //桶中元素個數在此,我們還可以發現一個問題就是可能會存在空桶佔用空間資源,所以基數排序典型的佔用空間換取時間,文章末尾會對這種排序時間和空間複雜度的分析!
  • ...選擇,插入,希爾,歸併,快排,堆排序,計數排序,桶排序,基數排序)
    無論是什麼數據進去,時間複雜度都是O(n*2)。數據規模越小越好,唯一的好處就是不佔用額外的空間。思路:在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。然後再從剩餘未排序的數組匯總繼續尋找最小(大)元素,依次類推。
  • 基數排序算法詳解(C語言代碼實現)
    基數排序不同於之前所介紹的各類排序,前邊介紹到的排序方法或多或少的是通過使用比較和移動記錄來實現排序,而基數排序的實現不需要進行對關鍵字的比較,
  • 排序算法問題:穩定排序與不穩定排序
    (給算法愛好者加星標,修煉編程內功)作者:紫紅的淚https://www.cnblogs.com/codingmylife/archive/2012/10/21/2732980.html前言排序算法的穩定與不穩定究竟是指的什麼
  • 徹底搞懂穩定排序與不穩定排序
    排序算法如果是穩定的,那麼從一個鍵上排序,然後再從另一個鍵上排序,第一個鍵排序的結果可以為第二個鍵排序所用。基數排序就是這樣,先按低位排序,逐次按高位排序,低位相同的元素其順序再高位也相同時是不會改變的。另外,如果排序算法穩定,對基於比較的排序算法而言,元素交換的次數可能會少一些(個人感覺,沒有證實)。
  • JS家的排序算法 十大經典算法排序總結
    冒泡排序還有一種優化算法,就是立一個flag,當在一趟序列遍歷中元素沒有發生交換,則證明該序列已經有序。但這種改進對於提升性能來說並沒有什麼太大作用。。。什麼時候最快(Best Cases):當輸入的數據已經是正序時(都已經是正序了,我還要你冒泡排序有何用啊。。。。)
  • 幾句話搞清八大排序算法,讓您在生活學習中不懼各種排序
    八大排序算法,分別是插入排序,冒泡排序,選擇排序,希爾排序,快速排序,堆排序,歸併排序,基數排序,搞了數年也沒有靜下心來,思考一下這些基本的算話,用到時,面試時,考試時,細細的看百度出的博客,即是耐下心看了一兩個,但是,還是一頭霧水!WHY?
  • 八大排序算法的 Python 實現
    [j] lists[j] = key j -= 1 return lists2、希爾排序希爾排序Shell Sort是插入排序的一種。 lists[i], lists[j] = lists[j], lists[i] return lists4、快速排序通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然後再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列
  • 高手才能看懂的JAVA排序算法圖解
    背景排序算法是JAVA面試中經常被問到的面試題,但排序算法很多,全部記下是一件很困難的事情。現整理多種排序算法圖解幫助JAVA面試者理解和掌握排序算法,能看懂下面的算法圖解,說明你已經掌握了該排序算法。
  • 十大經典排序算法(下)
    計數排序不是比較排序,排序的速度快於任何比較排序算法。由於用來計數的數組C的長度取決於待排序數組中數據的範圍(等於待排序數組的最大值與最小值的差加上1),這使得計數排序對於數據範圍很大的數組,需要大量時間和內存。最佳情況:T(n) = O(n+k) 最差情況:T(n) = O(n+k) 平均情況:T(n) = O(n+k)桶排序是計數排序的升級版。
  • Python 再牛,在字符串排序上還是被 Julia 和 R 碾壓
    然而,最初的調查顯示,在對具有大量重複值的字符串進行排序時,與 R 相比,Julia 中的字符串排序較慢。這可能是通過 R 將 Julia 與 C 進行比較,但從用戶的角度來看,直言不諱地說,他們可能並不關心。 對 Julia 來說,雖然有 radixsort 的3倍性能加持,但畢竟還是比不過 R。
  • 統治世界的十大排序算法!
    作為最簡單的排序算法之一,冒泡排序給我的感覺就像 Abandon 在單詞書裡出現的感覺一樣,每次都在第一頁第一位,所以最熟悉。冒泡排序還有一種優化算法,就是立一個 flag,當在一趟序列遍歷中元素沒有發生交換,則證明該序列已經有序。但這種改進對於提升性能來說並沒有什麼太大作用。1. 算法步驟比較相鄰的元素。
  • 程式設計師必知必會的十大排序算法
    我們正常講的十大排序算法是內部排序,我們更多是基於「比較和非比較」這個維度將它們分為兩大類:「非比較類的有桶排序、基數排序、計數排序」。也有很多人將排序歸納為8大排序,那就是因為基數排序、計數排序是建立在桶排序之上或者是一種特殊的桶排序,但是基數排序和計數排序有它特有的特徵,所以在這裡就將它們分別列出來。
  • 數據結構常見的八大排序算法
    希爾排序的算法思想:將待排序數組按照步長gap進行分組,然後將每組的元素利用直接插入排序的方法進行排序;每次將gap折半減小,循環上述操作;當gap=1時,利用直接插入,完成排序。基數排序算法思想基數排序:通過序列中各個元素的值,對排序的N個元素進行若干趟的「分配」與「收集」來實現排序。
  • Python實現八大經典排序算法
    排序算法可以分為內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存。常見的內部排序算法有:插入排序、希爾排序、選擇排序、冒泡排序、歸併排序、快速排序、堆排序、基數排序等。
  • 快速排序算法介紹
    快速排序(QuickSort) 是對冒泡排序的一種改進。由 C. A. R. Hoare 在1962年提出。它的基本思想是:通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然後再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。