基數排序(Radix Sorting),又稱 桶排序(bucket sorting)是以前的各種排序方法徹底不一樣的一中排序方法,在以前的排序方法中,主要是經過元素之間的比較和移動兩種操做來實現排序的。基數排序不須要進行元素之間的比較,而是根據關鍵字的每一個位上的有效數字的值,藉助「分配」和 「收集」兩種操做來進行排序的一中內部排序方法。html
在具體介紹基數排序算法前,首先先介紹兩個兩個關鍵詞:單關鍵字和多關鍵字。序列中任一記錄的關鍵字均有 d 個份量 ki0 ki1 …… kid-1 構成,若 d 個份量中每一個份量都是一個獨立的關鍵字,則文件是多關鍵字的(如撲克牌有兩個關鍵字:點數和花色、漢子的筆畫和拼音);不然是文件時單關鍵字的(如數值和字符串)。在多關鍵字的排序中,每一個關鍵字都能決定記錄的大小,如撲克牌的花色黑桃比花色方片大;在花色相同的狀況下,在比較點數的大小,如紅桃 9 比紅桃 8 大。 kij (0<= j < d) 是關鍵字中的其中一位(如字符串,十進制整數等)。多關鍵字中的每一個關鍵字的取值範圍通常不一樣,如撲克牌的花色取值只有 4 種,而點數則有 13 種。對於單關鍵字序排列能夠利用多關鍵字排序的方法,只是單關鍵字的每位通常取值範圍相同。git
在介紹一下基數的概念。設單關鍵字的每一個份量的取值範圍均爲 C0 <= kj<= Crd-1 (0 <= j <=d),則每一個記錄中份量的可能取值的個數 rd 稱爲基數。基數的選擇和關鍵字的分解因關鍵字的類型而異:算法
(1)若關鍵字是十進制整數,則按個、十等位進行分解,基數 rd = 10, C0 = 0,C9 = 9, d 爲最長整數的位數;數據結構
(2)若關鍵字是小寫的由於字符串,則 rd = 26,C0 = 'a' ,C25 = 'z', d爲字符串的最大長度。ide
(3)在撲克牌花色和點數的排序中,花色的基數 rd1 = 4 ,而點數的 rd2 = 13, d 同時使用撲克牌的副數。spa
基數排序時一種藉助於過關鍵字排序的思想,將單關鍵字按基數分紅「多關鍵字」進行排序的方法。好比字符串 "abcd" "acsc" "dwsc" "rews" 就能夠把每一個字符當作一個關鍵字,另外還有整數 425 、32一、23五、432也能夠將每一個位上的數字做爲一個關鍵字。指針
通常狀況下,假定有一包含 n 個對象的序列 { V0 , V1 , …… ,Vn-1 } ,且每一個對象 Vi 中包含 d 個關鍵字 ( ki1 ,ki2 , …… ,kid ), 若是對於序列中任意兩個對象 Vi 和 Vj (0 <= i < j <= n - 1 ) 都知足: ( ki1 ,ki2 , …… ,kid ) < ( kj1 ,kj2 , …… ,kjd ) ,則稱序列對關鍵字 ( k1 ,k2 , …… ,kd ) 有序。其中,k1 稱爲最高位關鍵字,k2 稱爲次高位關鍵字, kd 稱爲 最低位關鍵字。code
基數排序方法有兩種:最高位優先法(MSD:Most Significant Digit First)和最低位優先法(LSD:Least Significant Digit First)。htm
最高位優先法,簡稱MSD法:即先按 k1 排序分組,同一組中記錄的關鍵字 k1 相等,在對各組按 k2 分紅子組,以後對其餘的關鍵字繼續這樣的排序分組,直到按最次位關鍵字 kd 對各子組排序後。再將各組鏈接起來,獲得一個有序序列。對象
最低位優先法,簡稱LSD法: 即先從 kd 開始排序,在對 kd-1 進行排序,一次重複,直到對 k1 排序後便獲得一個有序序列。
如今已下面的序列爲例簡述一下 MSD 方法和 LSD方法: ead, bed, dad, add, bee, abc, dbe, dae, cda, eba, ccd 共 n = 11 個字符串
上面的每一個字符串每一個字符串包含 3 個字符,所以 d = 3, 這些字符的取值 爲 { a, b, c, d, e} 共 5 種取值, rd = 5
【注:這是針對這個例子而言 rd = 5,字符串的 rd 通常爲 26 】
MSD 方法的排序過程以下:
第一個字母排序: 將第一個字母相同的元素放在同一個隊列,咱們就能夠獲得:
第一個字母 元素
a add abc
b bed bee
c cda ccd
d dad dbe dae
e ead eda
第二個字母排序:將上述隊列中元素,第二個字母相同的元素放在同一個隊列中,咱們能夠獲得
第一個字母 第二個字母 元素
a b abc
a d add
b e bed bee
c c ccd
c d cda
d a dad dae
d b dbe
e a ead
e b eba
第三個字母排序:將上述隊裏中的元素,第三個字母相同的元素放在同一個隊列中,咱們能夠獲得
第一個字母 第二個字母 第三個字母 元素
a b c abc
a d d add
b e d bed
b e e bee
c c d ccd
c d a cda
d a d dad
d a e dae
d b e dbe
e a d ead
e b a eba
因爲每一個欄中只有一個元素,故從上到下鏈接起來就獲得有序隊列: abc, add, bed, bee, ccd, cda, dad, dae, dbe, ead, eba
使用LSD排序過程以下:
首先根據第三個字母排序,字母相同的放在一塊兒,獲得序列: cda, eba, abc, ead, bed, dad, add, ccd, bee, dbe, dae
而後對獲得的序列根據第二個字母排序獲得:ead, dad, dae, eba, abc, dbe, ccd, cda, add, bed, bee
最後獲得的序列根據第一個字母排列獲得: abc, add, bed, bee, ccd, cda, dad, dae, dbe, ead, eba
咱們能夠看出使用LSD排序方法排序結果和 MSD排序方法是同樣的,只是排序過程當中元素交換次序有些區別。MSD 是對在上一次分配好的隊列(或初始序列)中對元素進行分配排序,每一次分配的元素愈來愈少,總數不變,可是分配次數增長;LSD是對上一次分配獲得的序列,收集後再進行分配排序,每一次分配元素和次數均相同。
上面講得是關於基數排序的基本思路和方法,下面咱們以另一個例子講一下基數排序的實現過程。
咱們以數值爲例,先假定每一個數值 只有兩位,所以數值包括 d = 2 個份量, 每一個數值的取值範圍是 0 ~ 9 ,共 rd = 10 種,如數值:
73, 22, 93, 43, 55, 14, 28, 65, 39,81
首先根據個位數的數值,對每一個數值查詢最末位的值,將它們分配到編號0 ~ 9 的隊列中
個位數 數值
0
1 81
2 22
3 43 93 73
4 14
5 65 55
6
7
8 28
9 39
將上面的隊裏的數值從新串起來,若是是鏈式隊列的話,這個過程將會很是簡單,只要把指針鏈接起來就行了。咱們獲得新的序列:
81, 22, 43, 93, 73, 14, 65, 55, 28, 39
接着對十位進行一次分配,咱們能夠獲得下面的結果:
十位數 數值
0
1 14
2 22 28
3 39
4 43
5 55
6 65
7 73
8 81
9 93
咱們將這些隊列中的數值從新串起來,獲得序列: 14, 22, 28, 39, 43, 55, 65, 73, 81, 93
這個時候整個序列已經排序完畢,若是排序的對象有三位數以上,則繼續進行以上的動做直至最高位爲止。
LSD的基數排序使用與位數小的數列,若是位數多的話,使用MSD的效率會比較好,MSD的方式剛好 與LSD相反,由最高位爲基底進行分配其餘的演算方式都相同。
對於記錄的長度不一樣的序列,經過在記錄前面加上相應數據類型最低位來進行處理,如數值類列前加0,字符串類型前加空字符。
參考代碼:(這是以LSD方式實現的)
1 #include <stdio.h> 2 3 #define MAX_NUM 80 4 5 int main(int argc, char* argv[]) 6 { 7 int data[MAX_NUM]; // 存儲數據 8 int temp[10][MAX_NUM]; // 隊列表 9 int order[10]={ 0}; // 用於記錄隊列的信息 10 11 int n; // 待排序個數 12 int i,j,k; // 用於排序 13 int d; // 用於表示待排序輸的位數 14 int lsd; // 用於記錄某位的數值 15 16 k = 0; 17 d = 1; 18 19 printf("輸入須要排序的個數(<=80),待排序數(<10000): "); 20 scanf("%d",&n); 21 22 if( n > MAX_NUM ) n = MAX_NUM; 23 24 for(i = 0; i < n;i++) 25 { 26 scanf("%d",&data[i]); 27 data[i] %= 10000; 28 } 29 30 31 while(d <= 10000) 32 { 33 for(i = 0; i < n; i++) 34 { 35 lsd = (data[i]/d)%10; 36 temp[lsd][order[lsd]] = data[i]; 37 order[lsd]++; 38 } 39 40 printf("\n從新排列:"); 41 42 for(i = 0; i < 10; i++ ) 43 { 44 if(order[i]!=0) 45 { 46 for(j = 0; j < order[i]; j++ ) 47 { 48 data[k] = temp[i][j]; 49 printf("%d ",data[k]); 50 k++; 51 } 52 } 53 order[i] = 0; 54 } 55 56 d *= 10; 57 k = 0; 58 } 59 60 printf("\n 排序後:"); 61 for(i = 0; i <n; i++) 62 printf("%d ",data[i]); 63 printf("\n"); 64 65 66 67 return 0; 68 }
代碼運行結果如下面的數值列爲例: 125 11 22 34 15 44 76 66 100 8 14 20 2 5 1 共 15 個元素
運行截圖:
基數排序算法的效率和穩定性
對於 n 個記錄,執行一次分配和收集的時間爲O(n+r)。若是關鍵字有 d 位,若是關鍵字有 d 位,則要執行 d 遍。 因此總的運算時間爲 O(d(n+r))。可見不一樣的基數 r 所用時間是不一樣的。當 r 或 d 較小時,這種算法較爲節省時間。上面討論的是順序表的基數排序,這個算法一樣也是用與鏈式的記錄排序,只是要求額外附加一下隊列的頭、尾指針。因此附加的存儲量爲 2r 個存儲單元。待排序的記錄以鏈表形式存儲的,相對於順序表,只是額外增長了 n 個指針域的空間。
基數排序在分配過程當中,對於相同關鍵字的記錄而言保持原有順序進行分配,故基數排序時穩定的排序方法。
注:主要參考彭軍、向毅主編的 《數據結構與算法》