經典排序算法

參考地址:http://www.cnblogs.com/kkun/archive/2011/11/23/2260312.htmlhtml

1,快速排序 -- Quick sortios

原理,經過一趟掃描將要排序的數據分割成獨立的兩部分,其中一部分的全部數據都比另一部分的全部數據都要小,而後再按此方法對這兩部分數據分別進行快速排序,整個排序過程能夠遞歸進行,以此達到整個數據變成有序序列git

實現:算法

 1 int partition(int unsortded[],int low,int high)
 2 {
 3     int pivot = unsortded[low];
 4     while(low < high)
 5     {
 6         while(low<high && unsortded[high]>pivot) high --;
 7         unsortded[low] = unsortded[high];
 8         while(low<high && unsortded[low]<pivot) low ++;
 9         unsortded[high] = unsortded[low];
10     }
11     unsortded[low] = pivot;
12     return low;
13 }
14 
15 void quick_sort(int unsorted[],int low,int high)
16 {
17     int loc = 0;
18     if( low < high)
19     {
20         loc = partition(unsortded,low,high);
21         quick_sort(unsortded,low,loc-1);
22         quick_sort(unsortded,loc+1,high);
23     }
24 }

 

性能及優化:shell

  快速排序的最壞狀況基於每次劃分對主元的選擇。基本的快速排序選取第一個元素做爲主元。這樣在數組已經有序的狀況下,每次劃分將獲得最壞的結果。一種比較常見的優化方法是隨機化算法,即隨機選取一個元素做爲主元。這種狀況下雖然最壞狀況仍然是O(n^2),但最壞狀況再也不依賴於輸入數據,而是因爲隨機函數取值不佳。實際上,隨機化快速排序獲得理論最壞狀況的可能性僅爲1/(2^n)。因此隨機化快速排序能夠對於絕大多數輸入數據達到O(nlogn)的指望時間複雜度。一位前輩作出了一個精闢的總結:「隨機化快速排序能夠知足一我的一生的人品需求。」api

  隨機化快速排序的惟一缺點在於,一旦輸入數據中有不少的相同數據,隨機化的效果將直接減弱。對於極限狀況,即對於n個相同的數排序,隨機化快速排序的時間複雜度將毫無疑問的下降到O(n^2)。解決方法是用一種方法進行掃描,使沒有交換的狀況下主元保留在原位置數組

平衡快排:數據結構

    每次儘量地選擇一個可以表明中值的元素做爲關鍵數據,而後遵循普通快排的原則進行比較、替換和遞歸。一般來講,選擇這個數據的方法是取開頭、結尾、中間3個數據,經過比較選出其中的中值。取這3個值的好處是在實際問題中,出現近似順序數據或逆序數據的機率較大,此時中間數據必然成爲中值,而也是事實上的近似中值。萬一遇到正好中間大兩邊小(或反之)的數據,取的值都接近最值,那麼因爲至少能將兩部分分開,實際效率也會有2倍左右的增長,並且利於將數據略微打亂,破壞退化的結構app

外部快排:less

    與普通快排不一樣的是,關鍵數據是一段buffer,首先將以前和以後的M/2個元素讀入buffer並對該buffer中的這些元素進行排序,而後從被排序數組的開頭(或者結尾)讀入下一個元素,假如這個元素小於buffer中最小的元素,把它寫到最開頭的空位上;假如這個元素大於buffer中最大的元素,則寫到最後的空位上;不然把buffer中最大或者最小的元素寫入數組,並把這個元素放在buffer裏。保持最大值低於這些關鍵數據,最小值高於這些關鍵數據,從而避免對已經有序的中間的數據進行重排。完成後,數組的中間空位必然空出,把這個buffer寫入數組中間空位。而後遞歸地對外部更小的部分,循環地對其餘部分進行排序。

三路基數快排:

    Three-way Radix Quicksort,也稱做Multikey Quicksort、Multi-key Quicksort):結合了基數排序(radix sort,如通常的字符串比較排序就是基數排序)和快排的特色,是字符串排序中比較高效的算法。該算法被排序數組的元素具備一個特色,即multikey,如一個字符串,每一個字母能夠看做是一個key。算法每次在被排序數組中任意選擇一個元素做爲關鍵數據,首先僅考慮這個元素的第一個key(字母),而後把其餘元素經過key的比較分紅小於、等於、大於關鍵數據的三個部分。而後遞歸地基於這一個key位置對「小於」和「大於」部分進行排序,基於下一個key對「等於」部分進行排序

 

2 桶排序 Bucket sort

特色:

     桶排序是穩定的

     桶排序是常見排序裏最快的一種,比快排還快(大多數狀況下)

     桶排序很是快,可是同時也很是耗空間,基本上是最耗空間的一種排序算法

基本實現:

1 /// 桶排序
2 int * bucket_sort(int unsorted[],int len,int maxNumber=99)
3 {
4     int *sorted = new int[maxNumber+1];
5     for(int i = 0; i < len; i ++ )
6     {
7         sorted[unsortded[i]] = unsorted[i];
8     }
9 }

上面這個列子是簡單的思路,確定不實用,要想深一層瞭解桶排序,參考:http://www.cnblogs.com/hxsyl/p/3214379.html

桶排序主要應用在海量數據處理

 

3,插入排序 -- Insertion Sort

插入排序就是每一步都將一個待排數據按其大小插入到已經排序的數據中的適當位置,直到所有插入完畢

插入排序方法分直接插入排序和折半插入排序兩種,這裏只介紹直接插入排序,折半插入排序留到「查找」內容中進行

實現:

 1 void insertion_sort(int unsorted[],int len)
 2 {
 3     for(int i = 0 ; i < len; i ++ )
 4         if(unsorted[i - 1] > unsortded[i])
 5         {
 6             int temp = unsorted[i];
 7             int j = i;
 8             while( j > 0 && unsortded[j-1]>temp)
 9             {
10                 unsortded[j] = unsortded[j-1];
11                 j --;
12             }
13             unsortded[j] = temp;
14         }
15 }

複雜度: 

    若是目標是把n個元素的序列升序排列,那麼採用插入排序存在最好狀況和最壞狀況。最好狀況就是,序列已是升序排列了,在這種狀況下,須要進行的比較操做需(n-1)次便可。最壞狀況就是,序列是降序排列,那麼此時須要進行的比較共有n(n-1)/2次。插入排序的賦值操做是比較操做的次數加上 (n-1)次。平均來講插入排序算法的時間複雜度爲O(n^2)。於是,插入排序不適合對於數據量比較大的排序應用。可是,若是須要排序的數據量很小,例如,量級小於千,那麼插入排序仍是一個不錯的選擇

穩定性:

    插入排序是在一個已經有序的小序列的基礎上,一次插入一個元素。固然,剛開始這個有序的小序列只有1個元素,就是第一個元素。比較是從有序序列的末尾開始,也就是想要插入的元素和已經有序的最大者開始比起,若是比它大則直接插入在其後面,不然一直往前找直到找到它該插入的位置。若是遇見一個和插入元素相等的,那麼插入元素把想插入的元素放在相等元素的後面。因此,相等元素的先後順序沒有改變,從原無序序列出去的順序就是排好序後的順序,因此插入排序是穩定的

 

4,基數排序 -- Radix Sort

原理相似桶排序,這裏老是須要10個桶,屢次使用

首先以個位數的值進行裝桶,即個位數爲1則放入1號桶,爲9則放入9號桶,暫時忽視十位數,再次入桶,不過此次以十位數的數字爲準,進入相應的桶

這是基本桶排序的一個升級版

最高位優先(Most Significant Digit first)法,簡稱MSD法:先按k1排序分組,同一組中記錄,關鍵碼k1相等,再對各組按k2排序分紅子組,以後,對後面的關鍵碼繼續這樣的排序分組,直到按最次位關鍵碼kd對各子組排序後。再將各組鏈接起來,便獲得一個有序序列。
最低位優先(Least Significant Digit first)法,簡稱LSD法:先從kd開始排序,再對kd-1進行排序,依次重複,直到對k1排序後便獲得一個有序序列

實現:

 1 //約定:待排數字中沒有0,若是某桶內數字爲0則表示該桶未被使用,輸出時跳過便可
 2 // unsorted 待排數組  array_x 桶數組第一維長度   array_y 桶數組第二維長度
 3 void radix_sort(int unsorted[],int len,int array_x=10,int array_y=100);
 4 {
 5     for(int i = 0 ; i < array_x; /* 最大數字不超過999999999...(array_x個9) */ i ++ )
 6     {
 7         int bucket[arry_x][array_y];
 8         for(int j = 0 ; j < len;j ++)
 9         {
10             int temp = (unsortded[j]/(int)pow(10,i))%10;
11             for(int l = 0; l < array_y; l ++ )
12             {
13                 if(bucket[temp][l] == 0)
14                 {
15                     bucket[temp][l] = unsortded[j];
16                     break;
17                 }
18             }
19         }
20         for(int o = 0,x = 0; x < array_x ; x ++ )
21         {
22             for(int y = 0; y < array_y ; y ++ )
23             {
24                 if(bucket[x][y] == 0) continue;
25                 unsorted[o ++] = bucket[x][y];
26             }
27         }
28     }
29 }

效率分析:

    時間效率[1]  :設待排序列爲n個記錄,d個關鍵碼,關鍵碼的取值範圍爲radix,則進行鏈式基數排序的時間複雜度爲O(d(n+radix)),其中,一趟分配時間複雜度爲O(n),一趟收集時間複雜度爲O(radix),共進行d趟分配和收集。 空間效率:須要2*radix個指向隊列的輔助空間,以及用於靜態鏈表的n個指針

5,鴿巢排序 -- Pigeonhole Sort

原理相似桶排序,一樣須要一個很大的鴿巢[桶排序裏管這個叫桶,名字無所謂了]

鴿巢其實就是數組啦,數組的索引位置就表示值,該索引位置的值表示出現次數,若是所有爲1次或0次那就是桶排序

樣例實現:

1 int * pogeon_sort(int unsorted[],int len,int maxNumber = 100)
2 {
3     int * pogeonHole = new int[maxNumber+1];
4     for(int i = 0 ; i < len; i ++ )
5     {
6         pogeonHole[unsortded[i]] ++;
7     }
8     return pogeonHole;
9 }

算法效率:

    最壞時間複雜度: O(N+n)

    最好時間複雜度: O(N+n)

    平均時間複雜度: O(N+n)

    最壞空間複雜度: O(N*n)

6,歸併排序 -- Merge Sort

原理,把原始數組分紅若干子數組,對每個子數組進行排序,繼續把子數組與子數組合並,合併後仍然有序,直到所有合併完,造成有序的數組

歸併排序中中兩件事情要作:

            第一: 「分」,  就是將數組儘量的分,一直分到原子級別。

            第二: 「並」,將原子級別的數兩兩合併排序,最後產生結果。

實現:

 1 void merge(int unsorted[],int first,int mid,int last,int sorted[])
 2 {
 3     int i = first,j = mid;
 4     int k = 0 ;
 5     while( i < mid && j < last )
 6         if(unsortded[i] < unsortded[j])
 7             sorted[k++] = unsorted[i++];
 8         else
 9             sorted[k++] = unsortded[j++];
10     while( i < mid )
11         sorted[k++] = unsorted[i++];
12     while( j < last)
13         sorted[k++] = unsortded[j++]
14     for( int v = 0 ; v < k ; v ++ )
15         unsorted[first + v] = sorted[v];
16 }
17 void merge_sort(int unsorted[],int first,int last,int sorted[])
18 {
19     if( first+1 < last )
20     {
21         int mid = ( first + last ) / 2;
22         merge_sort(unsortded,first,mid,sorted);
23         merge_sort(unsortded,mid,last,sorted);
24         merge(unsorted,first,mid,last,sorted);
25     }
26 }

負責度:

    時間複雜度爲 O(nlogn) 這是該算法中最好,最壞,和平均的時間性能

    空間複雜度爲 O(n)

    比較操做的次數介於 (nlogn)/2 和 nlongn -n + 1

    賦值操做的次數是 2nlogn。

    歸併算法的空間複雜度爲 O(n)

    歸併排序比較佔用內存,但倒是一種效率高切穩定的算法

7,冒泡排序 -- Bubble Sort

原理是臨近的數字兩兩進行比較,按照從小到大或者從大到小的順序進行交換,樣一趟過去後,最大或最小的數字被交換到了最後一位,而後再從頭開始進行兩兩比較交換,直到倒數第二位時結束,其他相似

實現:

 1 void bubble_sort(int unsorted[],int len)
 2 {
 3     for(int i = 0 ; i < len; i ++ )
 4     {
 5         for(int j = i ; j < len; j ++ )
 6         {
 7             if( unsorted[i] > unsortded[j])
 8             {
 9                 int temp = unsortded[i];
10                 unsortded[i] = unsortded[j];
11                 unsortded[j] = temp;
12             }
13         }
14     }
15 }

存在不足:就是原本位於前面的較小數被交換到後面

時間複雜度:

     若文件的初始狀態是正序的,一趟掃描便可完成排序。所需的關鍵字比較次數 C 和記錄移動次數 M 均達到最小值:

    

     因此,冒泡排序的最好的時間複雜度爲 O(n)

     若初始文件是反序的,須要進行 n-1 趟排序。每趟排序要進行 n-i 次關鍵字的比較(1 <= 1 <= n-1),且每次都必須移動記錄三次來達到交換記錄位置。這種狀況下,比較和移動次數均達到最大值:

    

    

     冒泡排序的最歡時間複雜度爲 O(n2)

     綜上,所以冒泡排序總得時間複雜度爲 O(n2)

算法穩定性:

     冒泡排序就是把小的元素往前調或者把大的元素日後調。比較是相鄰的兩個元素比較,交換也發生在這兩個元素之間。因此,若是兩個元素相等,我想你是不會再無聊地把他們倆交換一下的;若是兩個相等的元素沒有相鄰,那麼即便經過前面的兩兩交換把兩個相鄰起來,這時候也不會交換,因此相同元素的先後順序並無改變,因此冒泡排序是一種穩定排序算法

冒泡排序動畫演示:

 

8,選擇排序 -- Selection Sort

顧名思意,就是直接從待排序數組裏選擇一個最小(或最大)的數字,每次都拿一個最小數字出來,順序放入新數組,直到所有拿完

再簡單點,對着一羣數組說,大家誰最小出列,站到最後邊,而後繼續對剩餘的無序數組說,大家誰最小出列,站到最後邊,再繼續剛纔的操做,一直到最後一個,繼續站到最後邊,如今數組有序了,從小到大

實現:

 1 void selection_sort(int unsorted[],int len)
 2 {
 3     for(int i = 0; i < len; i ++ )
 4     {
 5         int min = unsorted[i],min_index = i;
 6         for(int j = i ; j < len; j ++ )
 7         {
 8             if( unsortded[j] < min )
 9             {
10                 min = unsortded[j];
11                 min_index = j;
12             }
13         }
14         if( min_index != i )
15         {
16             int temp = unsorted[i];
17             unsortded[i] = unsortded[min_index];
18             unsortded[min_index] = temp;
19         }
20     }
21 }

分析:

   從選擇排序的思想或者是上面的代碼中,咱們都不難看出,尋找最小的元素須要一個循環的過程,而排序又是須要一個循環的過程。所以顯而易見,這個算法的時間複雜度也是O(n*n)的。這就意味值在n比較小的狀況下,算法能夠保證必定的速度,當n足夠大時,算法的效率會下降。而且隨着n的增大,算法的時間增加很快。所以使用時須要特別注意。

9,雞尾酒排序 -- Cocktail Sort

雞尾酒排序是基於冒泡排序,雙向循環

樣例實現:

 1 void cocktail_sort(int unsorted[],int len)
 2 {
 3     bool swapped = false;
 4     do
 5     {
 6         for(int i = 0; i < len; i ++ )
 7         {
 8             if(unsorted[i] > unsorted[i+1])
 9             {
10                 int temp = unsorted[i];
11                 unsorted[i] = unsorted[i+1];
12                 unsorted[i+1] = temp;
13                 swapped = true;
14             }
15         }
16         swapped = false;
17         for(int j = len; j > 1; j -- )
18         {
19             if(unsortded[j] < unsortded[j-1])
20             {
21                 int tmep = unsorted[j];
22                 unsorted[j] = unsorted[j=1];
23                 unsortded[j-1] = temp;
24                 swapped = true;
25             }
26         }
27     }while(swapped);
28 }

複雜度:

    雞尾酒排序最糟或是平均所花費的次數都是O(n²),但若是序列在一開始已經大部分排序過的話,會接近O(n)。

10,希爾排序 -- Shell Sort

希爾排序 是基於插入排序的一種改動,一樣分爲兩部分,希爾排序和關鍵字的選取

(參考:http://www.cnblogs.com/jingmoxukong/p/4303279.html)

觀察一下」插入排序「:其實不難發現她有個缺點:

     若是當數據是」5, 4, 3, 2, 1「的時候,此時咱們將「無序塊」中的記錄插入到「有序塊」時,估計俺們要崩盤,每次插入都要移動位置,此時插入排序的效率可想而知。

     shell根據這個弱點進行了算法改進,融入了一種叫作「縮小增量排序法」的思想,其實也蠻簡單的,不過有點注意的就是:  增量不是亂取,而是有規律可循的

 

首先要明確一下增量的取法:

      第一次增量的取法爲: d=count/2;

      第二次增量的取法爲:  d=(count/2)/2;

      最後一直到: d=1;

看上圖觀測的現象爲:

        d=3時:將40跟50比,因50大,不交換。

                   將20跟30比,因30大,不交換。

                   將80跟60比,因60小,交換。

        d=2時:將40跟60比,不交換,拿60跟30比交換,此時交換後的30又比前面的40小,又要將40和30交換,如上圖。

                   將20跟50比,不交換,繼續將50跟80比,不交換。

        d=1時:這時就是前面講的插入排序了,不過此時的序列已經差很少有序了,因此給插入排序帶來了很大的性能提升。

第一塊希爾排序介紹

準備待排數組[6 2 4 1 5 9]

首先須要選取關鍵字,例如關鍵是3和1(第一步分紅三組,第二步分紅一組),那麼待排數組分紅了如下三個虛擬組:

[6 1]一組

[2 5]二組

[4 9]三組

看仔細啊,不是臨近的兩個數字分組,而是3(分紅了三組)的倍數的數字分紅了一組,

就是每隔3個數取一個,每隔三個再取一個,這樣取出來的數字放到一組,

把它們當成一組,但不實際分組,只是當成一組來看,因此上邊的"組"實際上並不存在,只是爲了說明分組關係

對以上三組分別進行插入排序變成下邊這樣

[1 6] [2 5] [4 9]

具體過程:

[6 1]6和1交換變成[1 6]

[2 5]2與5不動仍是[2 5]

[4 9]4與9不動仍是[4 9]

第一趟排序狀態演示:

待排數組:[6 2 4 1 5 9]

排後數組:[1 2 4 6 5 9]

第二趟關鍵字取的是1,即每隔一個取一個組成新數組,實際上就是隻有一組啦,隔一取一就所有取出來了嘛

此時待排數組爲:[1 2 4 6 5 9]

直接對它進行插入排序

還記得插入排序怎麼排不?複習一下

[1 2 4]都不用動,過程省略,到5的時候,將5取出,在前邊的有序數組裏找到適合它的位置插入,就是4後邊,6前邊

後邊的也不用改,因此排序完畢

順序輸出結果:[1 2 4 5 6 9]

第二塊希爾排序的關鍵是如何取關鍵字,由於其它內容與插入排序同樣

那麼如何選取關鍵字呢?就是分紅三組,一組,這個分組的依據是什麼呢?爲何不是二組,六組或者其它組嘞?

好的增量序列的共同特徵:

① 最後一個增量必須爲1

② 應該儘可能避免序列中的值(尤爲是相鄰的值)互爲倍數的狀況

參見 http://baike.baidu.com/view/2217047.htm

這麼關鍵的問題居然沒有一個公式,只給出了兩個斷定標準

好吧,通常10個待排數字的話,關鍵依次選取5 3 1便可,其它的狀況只能本身判斷了,而後看是否符合上述兩條"好"的標準

增量的取值規則爲第一次取總長度的一半,第二次取一半的一半,依次累推直到1爲止

圖示以下:

實現:

 1 void shell_sort(int unsorted[],int len)
 2 {
 3     int group,i,j,temp;
 4     for(group = len/2,grop > 0; group /= 2)
 5     {
 6         for(i = group; i < len; i ++ )
 7         {
 8             for(j = i - group; j  >=0 ; j -= group )
 9             {
10                 if(unsortded[j] > unsortded[j+group])
11                 {
12                     temp = unsortded[j];
13                     unsortded[j] = unsortded[j + group];
14                     unsortded[j + group] = temp;
15                 }
16             }
17         }
18     }
19 }

 

希爾排序的算法性能:

排序類別 排序方法 時間複雜度 空間複雜度 穩定性 複雜性
平均狀況 最壞狀況 最好狀況
插入排序 希爾排序
O(Nlog 2N)
O(N 1.5)
 
O(1)
不穩定 較複雜

 

時間複雜度:

步長的選擇是希爾排序的重要部分。只要最終步長爲1任何步長序列均可以工做。

 

算法最開始以必定的步長進行排序。而後會繼續以必定步長進行排序,最終算法以步長爲1進行排序。當步長爲1時,算法變爲插入排序,這就保證了數據必定會被排序。
Donald Shell 最初建議步長選擇爲N/2而且對步長取半直到步長達到1。雖然這樣取能夠比O(N2)類的算法(插入排序)更好,但這樣仍然有減小平均時間和最差時間的餘地。可能希爾排序最重要的地方在於當用較小步長排序後,之前用的較大步長仍然是有序的。好比,若是一個數列以步長5進行了排序而後再以步長3進行排序,那麼該數列不只是以步長3有序,並且是以步長5有序。若是不是這樣,那麼算法在迭代過程當中會打亂之前的順序,那就不會以如此短的時間完成排序了。

步長序列 最壞狀況下複雜度
{n/2^i} \mathcal{O}(n^2)
2^k - 1 \mathcal{O}(n^{3/2})
2^i 3^j \mathcal{O}( n\log^2 n )
已知的最好步長序列是由Sedgewick提出的(1, 5, 19, 41, 109,...),該序列的項來自
這兩個算式。

這項研究也代表「比較在希爾排序中是最主要的操做,而不是交換。」用這樣步長序列的希爾排序比插入排序和堆排序都要快,甚至在小數組中比快速排序還快,可是在涉及大量數據時希爾排序仍是比快速排序慢。

算法穩定性

由上文的希爾排序算法演示圖便可知,希爾排序中相等數據可能會交換位置,因此希爾排序是不穩定的算法。

11,堆排序 -- Heap Sort

堆排序有點小複雜,分紅三塊 : 什麼是堆,什麼是最大堆    怎麼將堆調整爲最大堆  堆排序

什麼是堆:

這裏的堆(二叉堆),指得不是堆棧的那個堆,而是一種數據結構。堆其實是一棵徹底二叉樹,其任何一非葉節點知足性質:

Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]或者Key[i]>=Key[2i+1]&&key>=key[2i+2]  即任何一非葉節點的關鍵字不大於或者不小於其左右孩子節點的關鍵字

堆分爲大頂堆和小頂堆,知足Key[i]>=Key[2i+1]&&key>=key[2i+2]稱爲大頂堆,知足 Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]稱爲小頂堆。由上述性質可知大頂堆的堆頂的關鍵字確定是全部關鍵字中最大的,小頂堆的堆頂的關鍵字是全部關鍵字中最小的。

堆能夠視爲一棵徹底的二叉樹,徹底二叉樹的一個「優秀」的性質是,除了最底層以外,每一層都是滿的,這使得堆能夠利用數組來表示,每個結點對應數組中的一個元素.數組與堆之間的關係

二叉堆通常分爲兩種:最大堆和最小堆。

最大堆:堆中每一個父節點的元素值都大於等於其孩子結點(若是存在),這樣的堆就是一個最大堆。所以,最大堆中的最大元素值出如今根結點(堆頂)

節點與數組索引關係:對於給定的某個結點的下標i,能夠很容易的計算出這個結點的父結點、孩子結點的下標,並且計算公式很漂亮很簡約

怎麼將堆調整爲最大堆

個過程以下圖所示

在4,14,7這個小堆裏邊,父節點4小於左孩子14,因此二者交換

在4,2,8這個小堆裏邊,父節點4小於右孩子8,因此二者交換

上圖展現了一趟調整的過程,這個過程遞歸實現,直到調整爲最大堆爲止

堆排序介紹:

堆排序就是把堆頂的最大數取出,將剩餘的堆繼續調整爲最大堆,具體過程在第二塊有介紹,以遞歸實現

剩餘部分調整爲最大堆後,再次將堆頂的最大數取出,再將剩餘部分調整爲最大堆,這個過程持續到剩餘數只有一個時結束

下邊三張圖詳細描述了整個過程

如下參考:http://www.cnblogs.com/dolphin0520/archive/2011/10/06/2199741.html

.堆排序的思想

   利用大頂堆(小頂堆)堆頂記錄的是最大關鍵字(最小關鍵字)這一特性,使得每次從無序中選擇最大記錄(最小記錄)變得簡單。

    其基本思想爲(大頂堆):

    1)將初始待排序關鍵字序列(R1,R2....Rn)構建成大頂堆,此堆爲初始的無序區;

    2)將堆頂元素R[1]與最後一個元素R[n]交換,此時獲得新的無序區(R1,R2,......Rn-1)和新的有序區(Rn),且知足R[1,2...n-1]<=R[n]; 

    3)因爲交換後新的堆頂R[1]可能違反堆的性質,所以須要對當前無序區(R1,R2,......Rn-1)調整爲新堆,而後再次將R[1]與無序區最後一個元素交換,獲得新的無序區(R1,R2....Rn-2)和新的有序區(Rn-1,Rn)。不斷重複此過程直到有序區的元素個數爲n-1,則整個排序過程完成。

    操做過程以下:

     1)初始化堆:將R[1..n]構造爲堆;

     2)將當前無序區的堆頂元素R[1]同該區間的最後一個記錄交換,而後將新的無序區調整爲新的堆。

    所以對於堆排序,最重要的兩個操做就是構造初始堆和調整堆,其實構造初始堆事實上也是調整堆的過程,只不過構造初始堆是對全部的非葉節點都進行調整。

    下面舉例說明:

     給定一個整形數組a[]={16,7,3,20,17,8},對其進行堆排序。

    首先根據該數組元素構建一個徹底二叉樹,獲得

 而後須要構造初始堆,則從最後一個非葉節點開始調整,調整過程以下:

20和16交換後致使16不知足堆的性質,所以需從新調整

這樣就獲得了初始堆

即每次調整都是從父節點、左孩子節點、右孩子節點三者中選擇最大者跟父節點進行交換(交換以後可能形成被交換的孩子節點不知足堆的性質,所以每次交換以後要從新對被交換的孩子節點進行調整)。有了初始堆以後就能夠進行排序了

      

此時3位於堆頂不滿堆的性質,則需調整繼續調整

這樣整個區間便已經有序了。

從上述過程可知,堆排序其實也是一種選擇排序,是一種樹形選擇排序。只不過直接選擇排序中,爲了從R[1...n]中選擇最大記錄,需比較n-1次,而後從R[1...n-2]中選擇最大記錄需比較n-2次。事實上這n-2次比較中有不少已經在前面的n-1次比較中已經作過,而樹形選擇排序剛好利用樹形的特色保存了部分前面的比較結果,所以能夠減小比較次數。對於n個關鍵字序列,最壞狀況下每一個節點需比較log2(n)次,所以其最壞狀況下時間複雜度爲nlogn。堆排序爲不穩定排序,不適合記錄較少的排序

實現:

 1 //堆排序(大頂堆)
 2 void HeapAdjust(int *a,int i,int size)  //調整堆 
 3 {
 4     int lchild = 2 * i;                 //i的左孩子節點序號
 5     int rchild = 2 * i + 1;             //i的右孩子節點序號
 6     int max = i;                        //臨時變量
 7     if( i < size/2 )                    //若是i是葉節點就不用進行調整
 8     {
 9         if( lchild <= size && a[lchild] > a[max] )
10         {
11             max = lchild;
12         }
13         if( rchild <= size && a[rchild] > a[max] )
14         {
15             max = rchild;
16         }
17         if( max != i )
18         {
19             swap(a[i],a[max]);
20             HeapAdjust(a,max,size);     //避免調整以後以max爲父節點的子樹不是堆  
21         }
22     }
23 }
24 void BuildHeap(int *a,int size)         //創建堆 
25 {
26     int i;
27     for( i=size/2; i >= 1 ; i -- )      //非葉節點最大序號值爲size/2 
28 
29     {
30         HeapAdjust(a,i,size);
31     }
32 }
33 void HeapSort(int *a,int size)          //堆排序 
34 {
35     int i;
36     BuildHeap(a,size);
37     for(i = size; i >= 1; i --)
38     {
39         swap(a[1],a[i]);                //交換堆頂和最後一個元素,即每次將剩餘元素中的最大者放到最後面 
40         HeapAdjust(a,1,i-1);            //從新調整堆頂節點成爲大頂堆
41     }
42 }

算法分析:

     堆排序的時間,主要由創建初始堆和反覆重建堆這兩部分的時間開銷構成,它們均是經過調用Heapify實現的

平均性能:

     O(N*logN)

其餘性能:

     因爲建初始堆所需的比較次數較多,因此堆排序不適宜於記錄數較少的文件。

     堆排序是就地排序,輔助空間爲O(1).

     它是不穩定的排序方法。(排序的穩定性是指若是在排序的序列中,存在先後相同的兩個元素的話,排序前 和排序後他們的相對位置不發生變化)

12,地精排序 -- Gnome Sort

號稱最簡單的排序算法,只有一層循環,默認狀況下前進冒泡,一旦遇到冒泡的狀況發生就往回冒,直到把這個數字放好爲止

實現:

 1 void gnome_sort(int unsorted[],int len)
 2 {
 3     int i = 0;
 4     while( i < len )
 5     {
 6         if( i == 0 || unsortded[i-1] <= unsortded[i] )
 7         {
 8             i ++;
 9         }
10         else
11         {
12             int tmp = unsortded[i];
13             unsortded[i] = unsortded[i-1];
14             unsortded[i-1] = tmp;
15             i -- ;
16         }
17     }
18 }

模板實現:

 

 1 template<class T>
 2 void gnome_sort_1(T data[],int n,bool comparator(T,T))
 3 {
 4     int i = 1;
 5     while( i < n )
 6     {
 7         if( i > 0 && comparator(data[i],data[i-1])
 8         {
 9             swap(data[i],data[i-1]);
10             i --;
11         }
12         else
13         {
14             i ++;
15         }
16     }
17 }

 

 

 

優化1:

     精在回頭檢查花盤順序前記下當前位置,整理完就瞬間轉移到以前位置的下一個位置。瞬間轉移,很強大,哈哈。

實現:

 1 template<class T>
 2 void gnome_sort_2(T data[],int n,bool comparator(T,T))
 3 {
 4     int i = 1,previous_position = -1;
 5     while( i < n )
 6     {
 7         if( i > 0 && comparator(data[i],data[i-1])
 8         {
 9             if( previous_position == -1 )
10             {
11                 previous_position = i;
12             }
13             swap(data[i],data[i-1]);
14             i --;
15         }
16         else
17         {
18             if( previous_position == -1 )
19             {
20                 i ++;
21             }
22             else
23             {
24                 i = previous_position + 1;
25                 previous_position = -1;
26             }
27         }
28     }
29 }

 

不斷把列表後面待排序的元素插入到列表前面已排序的部分中,但相似於冒泡排序那樣比較並交換相鄰元素。所以其算法複雜度與插入排序至關,但比插入排序須要更多的複製操做(用於交換相鄰元素)。

優化2:

     搬了無數花盤後,地精想到一個新方法。若是交換相鄰兩個花盤,每次要搬花盤三次(複製三次),若是先拿起來當前位置的花盤(temp=data[i]),就有空位給其餘花盤移動,每次只搬一次花盤(複製一次,data[i]=data[i-1])。此次地精先拿一個花盤在手(temp),若是手中花盤(temp)應放到上一個花盤(i-1)前面,就用腳(地精通常不高,腳夠長嗎?^_^)將上一個花盤(i-1)踢到當前位置(i),而後拿着花盤(temp)走到上一個位置(i=i-1),重複此過程。

實現:(原文沒有給temp初始化,也許我理解錯了)

 1 template<class T>
 2 void gnome_sort_3(T data[],int n,bool comparator(T,T))
 3 {
 4     int i = 1,previous_position = -1;
 5     T temp = data[i];
 6     while( i < n )
 7     {
 8         if( i > 0 && comparator(temp,data[i-1])
 9         {
10             if( previous_position == -1 )
11             {
12                 previous_position = i;
13             }
14             data[i] = data[i-1];
15             i --;
16         }
17         else
18         {
19             if( previous_position == -1 )
20             {
21                 i ++;
22             }
23             else
24             {
25                 data[i] = temp;
26                 i = previous_position + 1;
27                 previous_position = -1;
28             }
29             temp = data[i];
30         }
31     }
32 }

 

已經和插入排序同樣了,呵呵
 
13,奇偶排序 -- Odd-even  Sort

 又一個比較性質的排序,基本思路是奇數列排一趟序,偶數列排一趟序,再奇數排,再偶數排,直到所有有序

舉例吧

 

待排數組[6 2 4 1 5 9]

第一次比較奇數列,奇數列與它的鄰居偶數列比較,如6和2比,4和1比,5和9比

[6 2 4 1 5 9]

交換後變成

[2 6 1 4 5 9]

 

第二次比較偶數列,即6和1比,5和5比

[2 6 1 4 5 9]

交換後變成

[2 1 6 4 5 9]

 

第三趟又是奇數列,選擇的是2,6,5分別與它們的鄰居列比較

[2 1 6 4 5 9]

交換後

[1 2 4 6 5 9]

 

第四趟偶數列

[1 2 4 6 5 9]

一次交換

[1 2 4 5 6 9]

實現:

 1 void parallel_bubblesort(void **ppData,int nDataLen,COMPAREFUNC func)
 2 {
 3     int i,j;
 4     for(i = 0; i < nDataLen; i ++ )
 5     {
 6         if( (i & 0x1) == 0 )    //i爲偶數
 7         {
 8             for( j = 0; j < nDataLen; j += 2 )
 9             {
10                 if( (*func)(ppData[j],ppData[j+1])>0 )
11                 {
12                     void *pData = ppData[j];
13                     ppData[j] =  ppData[j+1];
14                     ppData[j+1] = pData;
15                 }
16             }
17         }
18         else
19         {
20             for(j = 1; j < nDataLen - 1; j += 2)
21             {
22                 if( (*func)(ppData[j],ppData[j+1])>0)
23                 {
24                     void *pData = ppData[j];
25                     ppData[j] = ppData[j+1];
26                     ppData[j+1] = pData;
27                 }
28             }
29         }
30     }
31     return ;
32 }

14,梳排序 -- Comb Sort

梳排序仍是基於冒泡排序,與冒泡不一樣的是,梳排序比較的是固定距離處的數的比較和交換,相似希爾那樣

這個固定距離是待排數組長度除以1.3獲得近似值,下次則以上次獲得的近似值再除以1.3,直到距離小至3時,以1遞減

不太好描述,仍是看例子吧

假設待數組[8 4 3 7 6 5 2 1]

待排數組長度爲8,而8÷1.3=6,則比較8和2,4和1,並作交換

[8 4 3 7 6 5 2 1]

[8 4 3 7 6 5 2 1]

交換後的結果爲

[2 1 3 7 6 5 8 4]

 

第二次循環,更新間距爲6÷1.3=4,比較2和6,1和5,3和8,7和4

[2 1 3 7 6 5 8 4]

[2 1 3 7 6 5 8 4]

[2 1 3 7 6 5 8 4]

[2 1 3 7 6 5 8 4]

只有7和4須要交換,交換後的結果爲

[2 1 3 4 6 5 8 7]

 

第三次循環,更新距離爲3,沒有交換

第四次循環,更新距離爲2,沒有交換

第五次循環,更新距離爲1,三處交換

[2 1 3 4 6 5 8 7]

[2 1 3 4 6 5 8 7]

[2 1 3 4 6 5 8 7]

三處交換後的結果爲[1 2 3 4 5 6 7 8]

交換後排序結束,順序輸出便可獲得[1 2 3 4 5 6 7 8]

實現:

 1 void comb_sort(int *data,int len)
 2 {
 3     const double shrink = 1.25;
 4     int i,delta = len,noswap = 0;
 5     while( !noswap )
 6     {
 7         for(noswap = 1,i = 0; i +delta < len; i ++)
 8             if(data[i] > data[i+delta])
 9             {
10                 data[i] ^= data[i+delta];
11                 data[i+delta] ^= data[i];
12                 data[i] ^= data[i+delta];
13                 noswap = 0;
14             }
15             if(delta>1)
16             {
17                 delta /= shrink;
18                 noswap = 0;
19             }
20     }
21 }

15,耐心排序 -- Patience Sort

這個排序的關鍵在建桶和入桶規則上

建桶規則:若是沒有桶,新建一個桶;若是不符合入桶規則那麼新建一個桶

入桶規則:只要比桶裏最上邊的數字小便可入桶,若是有多個桶可入,那麼按照從左到右的順序入桶便可

舉個例子,待排數組[6 4 5 1 8 7 2 3]

第一步,取數字6出來,此時一個桶沒有,根據建桶規則1新建桶,將把本身放進去,爲了表述方便該桶命名爲桶1或者1號桶

第二步,取數字4出來,因爲4符合桶1的入桶規則,因此入桶1,並放置在6上邊,以下圖2所示

第三步,取數字5出來,因爲5不符合桶1的入桶規則,比桶1裏最上邊的數字大,此時又沒有其它桶,那麼根據建桶規則新建桶2,放入住該桶

第四步,取數字1出來,1即符合入1號桶的規則,比4小嘛,也符合入2號桶的規則,比5也小,兩個均可以入,根據入桶規則1入住1號桶(實際入住2號桶也不要緊)

第五步,取數字8出來,8比1號桶的掌門1大,比2號桶的掌門5也大,並且就這倆桶,因此8決定自立門派,創建了3號桶,併入住該桶成爲首位掌門

第六步,取數字7出來,1號桶,2號桶的掌門都不行,最後被3號桶收服,投奔了3號桶的門下

第七步,取數字2出來,被2號桶掌門收了

第八步,取數字3出來,被3號桶的現任掌門7收了

所有入桶完畢....

而後從第一個桶順序取出數字1 4 6,2 5,3 7 8

剩下的使用插入排序結束戰鬥

僞代碼:

 1 BOOL PatienceSort(datatype *array, int size)
 2 {
 3     int i, j;
 4     Node **bucket;
 5     Node *p;
 6 
 7     if(array == NULL) {
 8         return FALSE;
 9     }
10 
11     bucket = (Node **)calloc(size, sizeof(Node *));
12     if(bucket == NULL) {
13         return FALSE;
14     }
15 
16     for(i = 0; i < size; i++) {
17         j = 0;
18         //找到第一個最上面的數據比關鍵字大的桶,若是沒找到則指向一個空位置
19         while(bucket[j] != NULL && (bucket[j])->data < array[i])
20             j++;
21 
22         p = (Node *)malloc(sizeof(Node));
23         if(p == NULL) {
24             return FALSE;
25         }
26         p->data = array[i];
27         //將關鍵字入桶
28         if(bucket[j] != NULL) {
29             p->next = bucket[j];
30         } else {
31             p->next = NULL;
32         }
33 
34         bucket[j] = p;
35     }
36     i = j = 0;
37     //順序的從第一個桶到最後一個桶中取出數據
38     while(bucket[j] != NULL) {
39         p = bucket[j];
40         while(p != NULL) {
41             array[i++] = p->data;
42             p = p->next;
43         }
44         j++;
45     }
46     //進行一次插入排序
47     InsertionSort(array, size);
48 
49     return TRUE;
50 }

16,珠排序 -- Bead Sort

珠排序很是另類[地精也很另類],看完你就知道了,先介紹思路,再分解過程

英文論文地址:http://www.cs.auckland.ac.nz/~jaru003/research/publications/journals/beadsort.pdf

先了解一個概念,否則不容易理解,一個數字3用3個1來表示

一個數字9用9個1來表示,珠排序中的珠指的是每個1,它把每個1想像成一個珠子,這些珠子被串在一塊兒,想像下算盤和糖葫蘆

上圖中的三個珠就表示數字3,兩個珠表示數字2,這個OK了繼續,這裏的3和2都叫bead

上圖(a)中有兩個數字,4和3,分別串在四條線上,因而數字4的最後一個珠子下落,由於它下邊是空的,自由下落後變成圖(b)

圖(c)中隨機給了四個數字,分別是3,2,4,2,這些珠子自由下落,就變成了(d)中,落完就有序了,2,2,3,4

以上就是珠排序的精華

上圖中的n表示待排序數組的長度,有多少數字就有多少層,橫向表示一層

m表示有多少個珠子,就是多少個1,這取決於最大數是幾

好比待排數組[6 2 4 1 5 9]

讓珠子所有作自由落體運動

9沒有什麼好落的,它在最底層

5也沒有什麼好落的,所有有支撐點

1一樣不須要滑落

4除了第一個珠子不動外,其它三顆所有下落,落到1的位置變成下邊這樣

過程的細節不畫了,原則就是你下邊有支點,你就不用再滑落了,最後變成下邊這樣,排序完畢

從上到下順序輸出便可獲得結果:[ 1 2 4 5 6 9]

實現樣例:

 1 #include <iostream>
 2 #include <malloc.h>
 3 
 4 using namespace std;
 5 
 6 int GetMax(int *array,int size,int &len)
 7 {
 8   len = -1;
 9   for( int i = 0; i < size; i ++ ) if( array[i] > len ) len = array[i];
10 }
11 
12 void BeadSort(int *array, int size)
13 {
14   char **bead;
15   int i,j,k,n,len;
16   if( !array )      return ;
17   
18   //肯定每行珠子的最大個數 
19   GetMax(array,size,len);
20   cout << "Max Len of This Array is : " << len << endl;
21   //初始化
22   bead = (char **)calloc(size,sizeof(char *));
23   if( !bead )      return ;
24   for( i = 0; i < size; i ++ )
25   {
26     bead[i] = (char*)calloc(len,sizeof(char));
27     if(!bead[i]) return ;
28   }
29   for( i = 0 ;i < size; i ++ ) for(j = 0; j < array[i]; j ++) bead[i][j] = 1;
30     //初始化完畢,將全部的數按順序用珠子表示。
31   
32   //讓珠子自由下落
33   for(j = 0; j < len; j ++ )
34   {
35     i = k = size - 1;
36     while(i >= 0 ) if( bead[i--][j] == 1 ) bead[k--][j] = 1;
37     while( k >= 0 ) bead[k--][j] = 0;
38   }
39   //自由下落完畢
40   
41   //收集珠子,統計每一行有多少個珠子
42   for( i = 0; i < size; i ++ )
43   {
44     j = n = 0;
45     while( i < len ) 
46     {
47       if( !bead[i][j++]) break;
48       n ++;
49     }
50     array[i] = n;
51   }
52 }
53 
54 int main()
55 {
56   int array[] = {9,11,15,20,19,0,5,1,10,17,13,15,11,19,11,7,20,6,10};
57   int i=0,len = sizeof(array)/sizeof(int);
58   cout << "Before Sort: " ;
59   for( i = 0 ; i < len ; i ++ )
60      cout << array[i] << " ";
61   cout << endl;
62   BeadSort(array,sizeof(array)/sizeof(int));
63   
64   cout << "After Sort: " ;
65   for( i = 0 ; i < len ; i ++ )
66      cout << array[i] << " ";
67   cout << endl;
68 
69   return 0;
70 }

結果:

複雜度:

    珠排序能夠是如下複雜度級別:

    O(1):即全部珠子都同時移動,但這種算法只是概念上的,沒法在計算機中實現

    O(√n):在真實的物理世界中用引力實現,所需時間正比於珠子最大高度的平方根,而最大高度正比於n

    O(n):一次移動一列珠子,能夠用模擬和數字的硬件實現。

    O(S),S是全部輸入數據的和:一次移動一個珠子,能在軟件中實現

   

17,計數排序 -- Counting sort

計數排序的過程相似小學選班幹部的過程 ,如某某人10票,某人9票,那某某人是班長,某人是副班長

大致上分兩個部分: 第一部分是拉選票和投票 , 第二部分是根據票數入桶

看具體過程,一共須要三個數據組,分別是: 待排序數組,票箱數組 和 桶數組

int * unsorted = new int[N]; //{ 6,2,4,1,5,9}; //待排數組

int * ballot  = new int[N];  //票箱數組

int * bucket  = new int[N];  //桶數組

最後再看桶數組,先看待排數組和票箱數組

 

初始狀態,迭代變量 i = 0 時,待排序數組[i] = 6,票箱數組[i] = 0, 這樣經過迭代變量創建了數字與其桶號(即票數)的聯繫

待排數組[ 6 2 4 1 5 9 ] i = 0時,能夠從待排數組中取出6

票箱數組[ 0 0 0 0 0 0 ] 同時能夠從票箱數組裏取出6的票數0,即桶號

 

拉選票的過程:

首先6出列開始拉選票,6的票箱是0號,6對其它全部數字說,誰比我小或與我相等,就給我投票,否則揍你

因而,2 4 1 5 分別給6投票,放入0號票箱,6得四票

票箱數組[ 4 0 0 0 0 0 ]

 

接下來2開始拉選票,對其它人說,誰比我小,誰投我票,否則弄你!因而1投了一票,其餘人比2大不搭理,心想你可真二。因而2從1那獲得一票

待排數組[ 6 2 4 1 5 9 ]

票箱數組[ 4 1 0 0 0 0 ]

 

再而後是,4獲得2和1的投票,共計兩票。

1獲得0票,沒人投他

5獲得2,4,1投的三張票

9是最大,獲得全部人(本身除外)的投票,共計5票(數組長度-1票)

 

投票完畢時的狀態是這樣

待排數組[ 6 2 4 1 5 9 ]

票箱數組[ 4 1 2 0 3 5 ]

 

入桶的過程:

投票過程結束,每人都擁有本身的票數,桶數組說,看好你本身的票數,進入與你票數相等的桶,GO

6共計4票,進入4號桶

2得1票,進入1號桶,有幾票就進幾號桶

4兩票,進2號桶,5三票進3號桶,9有5票,進5號桶

待排數組[ 6 2 4 1 5 9 ]

票箱數組[ 4 1 2 0 3 5 ]

-----------------------

入桶前 [ 0 1 2 3 4 5 ] //裏邊的數字表示桶編號

入桶後 [ 1 2 4 5 6 9 ] //1有0票,進的0號桶

排序完畢,順序輸出便可[ 1 2 4 5 6 9]

 

能夠看到,數字越大票數越多,9獲得除本身外的全部人的票,5票,票數最多因此9最大,

每一個人最多擁有[數組長度減去本身]張票

1票數最少,因此1是最小的數,

計數排序同時兼有桶排的高效和快排的霸道

樣例實現:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #define random(x) rand()%(x)
 4 #define NUM 100//產生100個隨機數
 5 #define MAXNUM 200//待排序的數字範圍是0-200
 6 void countingSort(int A[], int n, int k){
 7     int *ptemp, *pout;
 8     int i;
 9     ptemp = (int *)malloc(sizeof(int)*k);
10     pout = (int *)malloc(sizeof(int)*n);
11     for (i = 0; i < k; i++)
12         ptemp[i] = 0;
13     for (i = 0; i < n; i++)
14         ptemp[A[i]] += 1;
15     for (i = 1; i < k; i++)
16         ptemp[i] = ptemp[i - 1] + ptemp[i];
17     for (i = n - 1; i >= 0; i--)
18     {
19         pout[ptemp[A[i]] - 1] = A[i];
20         ptemp[A[i]] -= 1;
21     }
22     for (i = 0; i < n; i++)
23         A[i] = pout[i];
24     free(ptemp);
25     free(pout);
26 }
27 void printArray(int A[], int n){
28     int i = 0;
29     for (i = 0; i < n; i++){
30         printf("%4d", A[i]);
31     }
32     printf("\n");
33 }
34  
35 void main()
36 {
37     int A[NUM];
38     int i;
39     for (i = 0; i < NUM; i++)
40         A[i] = random(MAXNUM);
41     printf("before sorting:\n");
42     printArray(A, NUM);
43     countingSort(A, NUM, MAXNUM);
44     printf("after sorting:\n");
45     printArray(A, NUM);
46 }
47  
計數排序對輸入的數據有附加的限制條件:
一、輸入的線性表的元素屬於有限偏序集S;
二、設輸入的線性表的長度爲n,|S|=k(表示集合S中元素的總數目爲k),則k=O(n)。
在這兩個條件下,計數排序的複雜性爲O(n)。
 
總結:
    咱們看到,計數 排序算法沒有用到元素間的比較,它利用元素的實際值來肯定它們在輸出 數組中的位置。所以,計數排序算法不是一個基於比較的排序算法,從而它的計算時間下界再也不是Ω(nlogn)。另外一方面,計數排序算法之因此能取得線性計算時間的上界是由於對元素的取值範圍做了必定限制,即k=O(n)。若是k=n2,n3,..,就得不到線性時間的上界。此外,咱們還看到,因爲算法第4行使用了downto語句,經計數排序,輸出序列中值相同的元素之間的相對次序與他們在輸入序列中的相對次序相同,換句話說,計數排序算法是一個穩定的排序算法。
 
應用 - 優化前向星:
     前向星不須要像鄰接表那樣用指針指向下一條邊,仍是挺方便的。可是,因爲前向星初始化須要快排一遍,相對 鄰接表要慢許多。考慮到通常圖論題點數都不會很大,因此能夠改成採用計數排序的思想對前向星進行排序。
     一開始讀入時,先算出每一個點出去的邊有多少條,而後計算出排序後每一個點出去的第一條邊位置應在哪裏,最後把所有邊掃一遍放到排序後應在的位置就行了。
     這樣排序的話初始化的時間複雜度就降到了O(m),整體時間並不會遜色於鄰接表。
應用 - 優化後綴數組的倍增算法:
     若是用快速排序,該算法的複雜度爲O(nlog^2n)。改用計數排序後,複雜度降爲O(nlogn)。
 
18, Proxmap Sort
這個排序是桶排序和基數排序的改進,理解了前二者,這個排序很容易理解

先回憶下桶排序是怎麼回事,它與桶的區別在於入桶規則,桶排序裏是1入1號桶,2入2號桶

這個排序把數字分區了,而後給出一個所謂的鍵,例如它規定0-9都入0號桶

10-19都入1號桶,這樣桶覆蓋的範圍將增大10倍,這在某種狀況下是頗有用的

有了桶排的基礎後,再看下邊兩張圖就什麼都明白了,再也不分解過程了

如下參考:http://dsqiu.iteye.com/blog/1707383

Proxmap Sort是桶排序和基數排序的改進。ProxMap的排序採用不一樣的方法來排序,這在概念上是相似哈希。該算法使用桶技術對哈希方法進行改變,桶的大小不相同。

                                                            Array  table

A1 6.7 5.9 8.4 1.2 7.3 3.7 11.5 1.1 4.8 0.4 10.5 6.1 1.8
H 1 3 0 1 1 1 2 1 1 0 1 1
P 0 1 -9 4 5 6 7 9 10 -9 11 12
L 7 6 10 1 9 4 12 1 5 0 11 7 1
A2 0.4 1.1 1.2 1.8 3.7 4.8 5.9 6.1 6.7 7.3 8.4 10.5 11.5

Technique:

1,  Create 4 arrays and initialize   

  1. int HitList[ARRAYSIZE] -- Keeps a count of the number of hits at each index in the sorted array. HitList[x] holds a count of the number of items whose keys hashed to x. Initialize to all 0.
  2. int Location[ARRAYSIZE] -- Indices in the sorted array calculated using the hash function. Item x in the unsorted array has its hash index stored in Location[x]. Does not need to be initialized.
  3. int ProxMap[ARRAYSIZE] -- Starting index in the sorted array for each bucket. If HitList[x] is not 0 then ProxMap[x] contains the starting index for the bucket of keys hashing to x. Initialize to all keys to -1 (unused).
  4. StructType DataArray2[ARRAYSIZE] -- Array to hold the sorted array. Initialize to all -1 (unused).

2,Use the keys of the unsorted array and a carefully chosen hash function to generate the indices into the sorted array and save these. The hash function must compute indices always in ascending order. Store each hash index in the Location[] array. Location[i] will hold the calculated hash index for the ith structure in the unsorted array. 
HIdx = Hash(DataArray[i]); 
Location[i] = HIdx; 

Care must be taken in selecting the hash function so that the keys are mapped to the entire range of indexes in the array. A good approach is to convert the keys to integer values if they are strings, then map all keys to floats in the range 0<= Key < 1. Finally, map these floats to the array indices using the following formulas:

    /* Map all integer keys to floats in range 0<= Key < 1 */
    KeyFloat = KeyInt / (1 + MAXKEYINTVALUE);

    /* Map all float keys to indices in range 0<= Index < ARRAYSIZE */
    Index = floor(ARRAYSIZE * KeyFloat);


This will then produce indices insuring that all the keys are kept in ascending order (hashs computed using a mod operator will not.)

3,Keep a count of the number of hits at each hash index. HitList[Hidx]++

4,Create the ProxMap (short for proximity map) from the hit list giving the starting index in the sorted array for each bucket.

    RunningTotal = 0;        /* Init counter */
    for(i=0; i 0)    /* There were hits at this address */
            {
            ProxMap[i] = RunningTotal;    /* Set start index for this set */
            RunningTotal += HitList[i];
            }
        }

5,Move keys from the unsorted array to the sorted array using an insertion sort technique for each bucket.


In this diagram 5 sets of structures are sorted when delta = 5

Analysis: ProxMap sorting runs in a surprisingly fast O(n) time.

C代碼:
  1 /******************************************************/
  2 /* ProxmapSort.c                                      */
  3 /*                                                    */
  4 /* Proxmap sort demonstration.                        */
  5 /* Author: Rick Coleman                               */
  6 /* Date: April 1998                                   */
  7 /******************************************************/
  8 #include <stdio.h>
  9 #include <conio.h>
 10 #include "sort.h"
 11 #include <math.h>
 12 
 13 #define ARRAYSIZE    32
 14 
 15 /* Prototype sort function */
 16 void ProxmapSort(StructType DataArray[], StructType DataArray2[],int count);
 17 int Hash(int key, int KeyMax, int KeyMin, int count);
 18 void ProxMapInsertionSort(StructType DataArray[], StructType *theStruct, 
 19                           int startIdx, int listLen);
 20 
 21 int main(void)
 22 {
 23     StructType  DataArray[32], DataArray2[32];
 24     int         count;
 25 
 26     count = ReadRecords(DataArray, 32);
 27     printf("Unsorted list of records.\n\n");
 28     PrintList(DataArray, count);
 29 
 30     ProxmapSort(DataArray, DataArray2, count);
 31 
 32     printf("Sorted list of records.\n\n");
 33     PrintList(DataArray2, count);
 34     printf("Sorting done...\n");
 35     getch();
 36     return(0);
 37 }
 38 
 39 
 40 /***************************************/
 41 /* ProxmapSort()                       */
 42 /*                                     */
 43 /* Sort records on integer key using   */
 44 /*  a proxmap sort.                    */
 45 /***************************************/
 46 void ProxmapSort(StructType DataArray[], StructType DataArray2[],int count)
 47 {
 48     int i;
 49     int HitList[ARRAYSIZE];
 50     int Hidx;                  /* Hashed index */
 51     int ProxMap[ARRAYSIZE];
 52     int RunningTotal;          /* Number of hits */
 53     int Location[ARRAYSIZE];
 54     int KeyMax, KeyMin;        /* Used in Hash() */
 55 
 56     /* Initialize hit list and proxmap */
 57     for(i=0; i<count; i++)
 58     {
 59         HitList[i] = 0;           /* Init to all 0 hits */
 60         ProxMap[i] = -1;          /* Init to all unused */
 61         DataArray2[i].key = -1;   /* Init to all empty */
 62     }
 63 
 64     /* Find the largest key for use in computing the hash */
 65     KeyMax = 0;        /* Guaranteed to be less than the smallest key */
 66     KeyMin = 32767;    /* Guaranteed to be more than the largest key */
 67     for(i=0; i<count; i++)
 68     {
 69         if(DataArray[i].key > KeyMax) KeyMax = DataArray[i].key;
 70         if(DataArray[i].key < KeyMin) KeyMin = DataArray[i].key;
 71     }
 72 
 73     /* Compute the hit count list (note this is not a collision count, but
 74         a collision count+1 */
 75     for(i=0; i<count; i++)
 76     {
 77         Hidx = Hash(DataArray[i].key, KeyMax, KeyMin, count);    /* Calculate hash index */
 78         Location[i] = Hidx;                                      /* Save this for later. (Step 1) */
 79         HitList[Hidx]++;                                         /* Update the hit count (Step 2) */
 80     }
 81 
 82     /* Create the proxmap from the hit list. (Step 3) */
 83     RunningTotal = 0;        /* Init counter */
 84     for(i=0; i<count; i++)
 85     {
 86         if(HitList[i] > 0)    /* There were hits at this address */
 87         {
 88             ProxMap[i] = RunningTotal;    /* Set start index for this set */
 89             RunningTotal += HitList[i];
 90         }
 91     }
 92 
 93     // NOTE: UNCOMMENT THE FOLLOWING SECTION TO SEE WHAT IS IN THE ARRAYS, BUT
 94     //       COMMENT IT OUT WHEN DOING A TEST RUN AS PRINTING IS VERY SLOW AND
 95     //       WILL RESULT IN AN INACCURATE TIME FOR PROXMAP SORT.
 96 /* ----------------------------------------------------
 97     // Print HitList[] to see what it looks like
 98     printf("HitList:\n");
 99     for(i=0; i<count; i++)
100         printf("%d ", HitList[i]);
101     printf("\n\n");
102     getch();
103 
104     // Print ProxMap[] to see what it looks like
105     printf("ProxMap:\n");
106     for(i=0; i<count; i++)
107         printf("%d ", ProxMap[i]);
108     printf("\n\n");
109     getch();
110 
111     // Print Location[] to see what it looks like
112     printf("Location:\n");
113     for(i=0; i<count; i++)
114         printf("%d ", Location[i]);
115     printf("\n\n");
116     getch();
117     ---------------------------------------------  */
118 
119     /* Move the keys from A1 to A2 */
120     /* Assumes A2 has been initialized to all empty slots (key = -1)*/
121     for(i=0; i<count; i++)
122     {
123         if((DataArray2[ProxMap[Location[i]]].key == -1))  /* If the location in A2 is empty...*/
124         {
125             /* Move the structure into the sorted array */
126             DataArray2[ProxMap[Location[i]]] = DataArray[i];
127         }
128         else    /* Insert the structure using an insertion sort */
129         {
130             ProxMapInsertionSort(DataArray2, &DataArray[i], ProxMap[Location[i]], HitList[Location[i]]);
131         }
132     }
133 
134 }
135 
136 /***************************************/
137 /* Hash()                               */
138 /*                                     */
139 /* Calculate a hash index.             */
140 /***************************************/
141 int Hash(int key, int KeyMax, int KeyMin, int count)
142 {
143     float    keyFloat;
144 
145     /* Map integer key to float in the range 0 <= key < 1 */
146     keyFloat = (float)(key - KeyMin) / (float)(1 + KeyMax - KeyMin);
147 
148     /* Map float key to indices in range 0 <= index < count */
149     return((int)floor(count * keyFloat));
150 }
151 
152 /***************************************/
153 /* ProxMapInsertionSort()              */
154 /*                                     */
155 /* Use insertion sort to insert a      */
156 /*   struct into a subarray.           */
157 /***************************************/
158 void ProxMapInsertionSort(StructType DataArray[], StructType *theStruct, 
159                           int startIdx, int listLen)
160 {
161     /* Args:    DataArray - Partly sorted array
162                 *theStruct - Structure to insert
163                 startIdx - Index of start of subarray
164                 listLen - Number of items in the subarray */
165     int i;
166 
167     /* Find the end of the subarray */
168     i = startIdx + listLen - 1;
169     while(DataArray[i-1].key == -1) i--;
170     
171     /* Find the location to insert the key */ 
172     while((DataArray[i-1].key > theStruct->key) && (i > startIdx))
173     {
174         DataArray[i] = DataArray[i-1];
175         i--;
176     }
177 
178     /* Insert the key */
179     DataArray[i] = *theStruct;
180 }

算法wiki: https://en.wikipedia.org/wiki/Proxmap_sort

 

19,Flash Sort

FlashSort依然相似桶排,主要改進了對要使用的桶的預測,或者說,減小了無用桶的數量從而節省了空間,例如

待排數字[ 6 2 4 1 5 9 100 ]桶排須要100個桶,而flash sort則因爲能夠預測桶則只須要7個桶

即待排數組長度個桶,如何預測將要使用的桶有這麼一個公式

 

 
 

該排序有前置條件,須要知道待排數組的區間和待排數組的長度,

例如已知待排數組[ 6 2 4 1 5 9 ]的長度爲6,最大值9,最小值1,這三個是已知條件,若是沒法知道這三個則沒法應用該排序

 

預測的思想:

若是有這樣一個待排數組,其最大值是100,最小值是1,數組長度爲100,那麼50在排完序後極有可能出如今正中間,flash sort就是基於這個思路

 

預測桶號細節:

待排數組[ 6 2 4 1 5 9 ]

具體看6可能出現的桶號

Ai - Amin 是 6 - 1 = 5

Amax - Amin 是9 - 1 = 8

m - 1 是數組長度6 - 1 = 5

則(m - 1) * (Ai - Amin) / (Amax - Amin) = 5 * 5 / 8 =25/8 = 3.125

最後加上1等於 4.125

6預測的桶號爲4.125

2預測的桶號爲1.625

4預測的桶號爲2.875

1預測的桶號爲1

5預測的桶號爲3.5

9預測的桶號爲5

去掉小數位後,每一個數字都擁有本身預測的桶號,對應以下所示

待排數組[ 6 2 4 1 5 9 ]

預測桶號[ 4 1 2 1 3 5 ]

 

入桶規則:

1號桶 2,1

2號桶 4

3號桶 5

4號桶 6

5號桶 9

1號桶內兩個數字使用任意排序算法使之有序,其它桶若是此種狀況一樣須要在桶內排序,使用什麼排序算法不重要,重要的是排成從小到大便可

最後順序從桶裏取出來便可

[1 2 4 5 6 9]

 

wiki 地址: https://en.wikipedia.org/wiki/Flashsort

 

20,Strand Sort

 

Strand sort是思路是這樣的,它首先須要一個空的數組用來存放最終的輸出結果,給它取個名字叫"有序數組"

而後每次遍歷待排數組,獲得一個"子有序數組",而後將"子有序數組"與"有序數組"合併排序

重複上述操做直到待排數組爲空結束

 

例子:

待排數組[ 6 2 4 1 5 9 ]

第一趟遍歷獲得"子有序數組"[ 6 9],並將其歸併排序到有序數組裏

待排數組[ 2 4 1 5]

有序數組[ 6 9 ]

 

第二趟遍歷再次獲得"子有序數組"[2 4 5],將其歸併排序到有序數組裏

待排數組[ 1 ]

有序數組[ 2 4 5 6 9 ]

 

第三趟遍歷再次獲得"子有序數組"[ 1 ],將其歸併排序到有序數組裏

待排數組[ ... ]

有序數組[ 1 2 4 5 6 9 ]

 

待排數組爲空,排序結束

代碼參考地址:http://blog.csdn.net/tiantangrenjian/article/details/7172942

#include <iostream>
using namespace std;

void merge(int res[],int resLen,int sublist[],int last)
{
    int *temp = (int *)malloc(sizeof(int)*(resLen+last));
    int beginRes=0;
    int beginSublist=0;
    int k;
    for(k=0;beginRes<resLen && beginSublist<last;k++)
    {
        if(res[beginRes]<sublist[beginSublist])
            temp[k]=res[beginRes++];
        else temp[k]=sublist[beginSublist++];
        //cout<<"k:"<<k<<"  temp[k]:"<<temp[k]<<endl;
    }
    if(beginRes<resLen)
        memcpy(temp+k,res+beginRes,(resLen-beginRes)*sizeof(int));
    else if(beginSublist<last)
        memcpy(temp+k,sublist+beginSublist,(last-beginSublist)*sizeof(int));
    memcpy(res,temp,(resLen+last)*sizeof(int));
    free(temp);
}

void strandSort(int array[],int length)
{
    int *sublist=(int *)malloc(sizeof(int)*length);
    int *res=(int *)malloc(sizeof(int)*length);          //sizeof(array)=4
    int i;
    int resLen=0;
    res[0]=array[0];
    array[0]=0;
    for(i=1;i<length;i++)
    {
        if(array[i]>res[resLen])
        {
            resLen++;
            res[resLen]=array[i];
            array[i]=0;
        }
    }
    resLen++;

    int last;
    int times=1;
    bool finished;
    while (true)
    {
        finished = true;
        last = -1;
        for(i=times;i<length;i++)
        {
            //cout<<"This time array[i]: "<<array[i]<<endl;
            if(array[i]!=0)
            {
                //cout<<"This time array[i]: "<<array[i]<<endl;
                if (last==-1)
                {
                    sublist[0]=array[i];
                    array[i]=0;
                    last=0;
                    finished = false;
                }
                else if(array[i]>sublist[last])
                {    
                    last++;            
                    sublist[last]=array[i];
                    array[i]=0;    
                }
            }
            
        }
        if(finished) break;
        last++;

        merge(res,resLen,sublist,last);
        resLen=resLen+last;
        times++;
    }
        memcpy(array,res,length*sizeof(int));

}

int main()
{
    //int array[]={15,9,8,1,4,11,7,2,13,16,5,3,6,2,10,14};
    int array[]={13,14,94,33,82,25,59,94,65,23,45,27,73,25,39,10,35,54,90,58};
    int i;
    int length=sizeof(array)/sizeof(int);     //在這裏 sizeof(array)=80 
    strandSort(array,length);
    //int *arr = array;
    //cout<<arr[2]<<endl;

    for(i=0;i<length;i++)
    {
        cout<<array[i]<<"  ";
    }
    cout<<endl;
    return 0;
}

 

21,圈排序 -- Cycle Sort

Cycle sort的思想與計數排序太像了,理解了基數排序再看這個會有很大的幫助,

圈排序與計數排序的區別在於圈排序只給那些須要計數的數字計數,先看完文章吧,看完再回來理解這一句話

所謂的圈的定義,我只能想到用例子來講明,實在很差描述

待排數組[ 6 2 4 1 5 9 ]

排完序後[ 1 2 4 5 6 9 ]

數組索引[ 0 1 2 3 4 5 ]

 

第一部分

     第一步,咱們如今來觀察待排數組和排完後的結果,以及待排數組的索引,能夠發現

排完序後的6應該出如今索引4的位置上,而它如今卻在位置0上,

記住這個位置啊,一直找到某個數應該待在位置0上咱們的任務就完成了

待排數組[ 6 2 4 1 5 9 ]

排完序後[ 1 2 4 5 6 9 ]

數組索引[ 0 1 2 3 4 5 ]

     第二步,而待排數組索引4位置上的5應該出如今索引3的位置上

待排數組[ 6 2 4 1 5 9 ]

排完序後[ 1 2 4 5 6 9 ]

數組索引[ 0 1 2 3 4 5 ]

     第三步,一樣的,待排數組索引3的位置是1,1應該出如今位置0上,注意注意,找到這麼一個數了:1,它應該待在位置0上

待排數組[ 6 2 4 1 5 9 ]

排完序後[ 1 2 4 5 6 9 ]

數組索引[ 0 1 2 3 4 5 ]

     第四步,而索引0處卻放着6,而6應該出如今索引4的位置,至此能夠發現,回到原點了,問題回到第一步了,

因此這裏並不存在所謂的第四步,前三步就已經轉完一圈了

待排數組[ 6 2 4 1 5 9 ]

排完序後[ 1 2 4 5 6 9 ]

數組索引[ 0 1 2 3 4 5 ]

這就是所謂的一圈!真很差描述,不知道您看明白沒...汗.

前三步轉完一圈,獲得的數據分別是[ 6 5 1 ]

 

第二部分

     第一步,圈排序並非一圈排序,而一圈或多圈排序,因此,還得繼續找,這一步從第二個數字2處開始轉圈

待排中的2位於索引1處,排序完畢仍然處於位置1位置,因此這一圈完畢,獲得圈數據[ 2 ]

待排數組[ 6 2 4 1 5 9 ]

排完序後[ 1 2 4 5 6 9 ]

數組索引[ 0 1 2 3 4 5 ]

 

第三部分

     第一步,同上,4也出現了它應該待的位置,結束這一圈,獲得第三個圈:[ 4 ]

待排數組[ 6 2 4 1 5 9 ]

排完序後[ 1 2 4 5 6 9 ]

數組索引[ 0 1 2 3 4 5 ]

 

第四部分

     第一步,因爲1和5出如今第一圈裏,這是什麼意思呢,說明這兩個數已經有本身的圈子了,不用再找了,

便是找,最後仍是獲得第一圈的數據[ 6 5 1 ],因此,1和5跳過,這一部分實際應該找的是9,來看看9的圈子

9應該出如今索引5的位置,實際上它就在索引5的位置,與第二部分的第一步的狀況同樣,因此這一圈的數據也出來了:[ 9 ]

待排數組[ 6 2 4 1 5 9 ]

排完序後[ 1 2 4 5 6 9 ]

數組索引[ 0 1 2 3 4 5 ]

一共找到四個圈子,分別是

[ 6 5 1 ] , [ 2 ] ,[ 4 ] , [ 9 ]

 

若是一個圈只有一個數字,那麼它是不須要轉圈的,即不須要排序,那麼只有第一個圈排序便可

你可能要問了,前邊的那些圈子都是基於已知排序結果才能獲得,我都已知結果還排個毛啊

以上內容都是爲了說明什麼是圈,知道什麼是圈後才能很好的理解圈排序

如今來分解排序的細節

第一步,將6取出來,計算出有4個數字比6小,將6放入索引4,同時原索引4位置的數字5出列

排序以前[ 0 2 4 1 5 9 ] 6

排序以後[ 0 2 4 1 6 9 ] 5

索引位置[ 0 1 2 3 4 5 ]

 

第二步,當前數字5,計算出有3個數字比5小,將5放入索引3,同時原索引3位置的數字

排序以前[ 0 2 4 1 6 9 ] 5

排序以後[ 0 2 4 5 6 9 ] 1

索引位置[ 0 1 2 3 4 5 ]

 

第三步,當前數字1,計算出有0個數字比1小,將1放入索引0,索引0處爲空,這圈完畢

排序以前[ 0 2 4 5 6 9 ] 1

排序以後[ 1 2 4 5 6 9 ]

索引位置[ 0 1 2 3 4 5 ]

第一個圈[ 6 5 1 ]完畢

 

第四步,取出下一個數字2,計算出有1個數字比2小,將2放入索引1處,發現它原本就在索引1處

第五步,取出下一個數字4,計算出有2個數字比4小,將4放入索引2處,發現它原本就在索引2處

第六步,取出下一個數字5,5在第一個圈內,沒必要排序

第七步,取出下一個數字6,6在第一個圈內,沒必要排序

第八步,取出下一個數字9,計算出有5個數字比9小,將9放入索引5處,發現它原本就在索引5處

所有排序完畢

 

wiki 地址: https://en.wikipedia.org/wiki/Cycle_sort

 

22, 圖書館排序 -- Library sort

思路簡介,大概意思是說,排列圖書時,若是在每本書之間留必定的空隙,那麼在進行插入時就有可能會少移動一些書,說白了就是在插入排序的基礎上,給書與書之間留必定的空隙,這個空隙越大,須要移動的書就越少,這是它的思路,用空間換時間

看紅線標的那句話知道,這個空隙留多大,你本身定

圖書館排序的關鍵是分配空間,分配完空間後直接使用插入排序便可

 

 

進行空間分配的過程

這個我實在是找不到相關的資料,沒準就是平均分配嘞

進行插入排序的過程

舉例待排數組[ 0 0 6 0 0 2 0 0 4 0 0 1 0 0 5 0 0 9 ],直接對它進行插入排序

第一次移動,直接把2放6前邊

[ 0 2 6 0 0 0 0 0 4 0 0 1 0 0 5 0 0 9 ]

第二次移動,先把6日後挪,而後把4放在剛纔6的位置,移動了一個位置

[ 0 2 4 6 0 0 0 0 0 0 0 1 0 0 5 0 0 9 ]

第三次移動,直接把1放2前邊

[ 1 2 4 6 0 0 0 0 0 0 0 0 0 0 5 0 0 9 ]

第四次移動,再把6日後挪一位,把5放在剛纔6的位置

[ 1 2 4 5 6 0 0 0 0 0 0 0 0 0 0 0 0 9 ]

第五次移動後,把9放6後邊,排序結束

[ 1 2 4 5 6 9 0 0 0 0 0 0 0 0 0 0 0 0 ]

 

 

以上算法總結自各個網站,出處都給出地址!轉載請註明原出處

wiki 地址: https://en.wikipedia.org/wiki/Library_sort

相關文章
相關標籤/搜索