1. 程式人生 > >【最全】經典排序算法(C語言)

【最全】經典排序算法(C語言)

排好序 而不是 lock wap 循環 而且 -s 關鍵字 void

本文章包括所有基本排序算法(和其中一些算法的改進算法):
直接插入排序、希爾排序、直接選擇排序、堆排序、冒泡排序、快速排序、歸並排序、基數排序。

算法復雜度比較:

技術分享圖片

算法分類

技術分享圖片

一、直接插入排序

一個插入排序是另一種簡單排序,它的思路是:每次從未排好的序列中選出第一個元素插入到已排好的序列中。
它的算法步驟可以大致歸納如下:

  1. 從未排好的序列中拿出首元素,並把它賦值給temp變量;
  2. 從排好的序列中,依次與temp進行比較,如果元素比temp大,則將元素後移(實際上放置temp的元素位置已經空出)
  3. 直到找到一個元素比temp小, 將temp放入該位置;

因此,從上面的描述中我們可以發現,直接插入排序可以用兩個循環完成:
第一層循環:遍歷待比較的所有數組元素
第二層循環:將本輪選擇的元素(selected)與已經排好序的元素(ordered)相比較。
如果:selected > ordered,那麽將二者交換

#include <stdio.h>
void InsertSort(int *a, int len)
{
    int i, j, tmp;
    for(i=1; i<len; i++){         //i指向無序區第一個元素
        tmp = a[i];
        j = i - 1;                //j指向有序去第一個元素
        // j往前遍歷,找到比a[i]小的,插入到此處。比a[i]大的後移
        while(j>=0 && tmp<a[j]){   //小於號換成大於號則是從大到小排序
            a[j+1] = a[j];
            j--;
        }
        a[j+1] = tmp;  //插入到空出來的位置
    }
    
    return;
}
int main()
{
    int a[] = {1,3,63,5,78,9,12,52,8};
    int n = sizeof(a)/sizeof(int),i;
    InsertionSort(a, n);
    for(i=0; i<n; i++)
        printf("%d ", a[i]);
    return 0;
}

二、折半插入排序

與直接插入算法的區別在於:在有序表中尋找待排序數據的正確位置時,使用了折半查找/二分查找。
減少了比較的次數,但沒有減少插入的次數。時間復雜度仍為O(n^2),但比直接插入排序稍微快一點。

void BinaryInsertSort(int *a, int len)
{
    int i, j, low, high, tmp;
    for(i=1; i<len; i++){
        tmp = a[i];
        j = i - 1;
        low = 0;
        high = i - 1;
        while(low<=high){
            int mid = (low + high) / 2;
            if(tmp>a[mid])
                low = mid + 1;
            else
                high = mid - 1 ;
        }
        while(j>=low && tmp<a[j]){   //小於號換成大於號則是從大到小排序
            a[j+1] = a[j];
            j--;
        }
        a[j+1] = tmp;
    }
    
    return;
}

三、希爾排序法

希爾排序又叫縮小增量排序

基於直接插入排序,基本思想是:先將整個待排序的記錄序列分割成為若幹子序列分別進行直接插入排序,待整個序列中的記錄“基本有序”時,再對全體記錄進行依次直接插入排序。

希爾的思想也很簡單就是一個h-sort的插入算法——每相鄰h個元素進行插入排序
如果h比較大,那麽子數組會很小,用插入效率高
如果h很小,這時候數組基本有序,插入效率也很高

void ShellSort(int arr[], int len)
{
    int step;
    int i, j;
    int tmp;
    for(step = len/2; step > 0; step = step/2)   // 比直接插入排序多了一層循環
    {
        for(i = step; i < len; i++)              // 直接插入排序可以看成step為1的希爾排序。把這裏的Step都替換成1,就是直接插入排序
        {
            j = i - step;
            tmp = arr[i];
            while(j>=0 && tmp<arr[j])
            {
                arr[j+step] = arr[j];
                j = j - step;
            }
            arr[j+step] = tmp;
        }
    }
    
    return;
}

四、簡單選擇排序

排序思路是:每次從未排序的序列中選出一個最小值,並把它放在已排好序的序列的序尾。這樣就形成了一個有序序列(從小到大)。
簡單選擇排序的基本思想:找到最小值 + 交換。

第一趟,從n 個記錄中找出關鍵碼最小的記錄與第一個記錄交換;
第二趟,從第二個記錄開始的 n-1 個記錄中再選出關鍵碼最小的記錄與第二個記錄交換;
以此類推.....
第 i 趟,則從第 i 個記錄開始的 n-i+1 個記錄中選出關鍵碼最小的記錄與第 i 個記錄交換,
直到整個序列按關鍵碼有序。

void SelectSort(int *a, int len)
{
    int i, j;
    int key, tmp;
    
    for(i=0; i<len; i++)
    {
        key = i;          // 記錄最小值的下標,初始值為i,即有序序列的最後一個元素    
        for(j=i+1; j<len; j++)  // j從i的下一個元素開始遍歷,即無序序列的第一個元素
        {
            if(a[j]<a[key])  // 如果找到比當前key上的值小的值,則替換key值
                key = j;
        }
        
        if(key!=i)          // key == i說明 無序序列中沒有比a[i]更小的數了,不必替換
        {
            tmp = a[i];      // 替換a[i]和無序序列中最小的元素a[key]
            a[i] = a[key];
            a[key] = tmp;
        }
    }
    return;
}

五、堆排序

堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。實際上也是一種選擇排序,只不過采用了堆這種數據結構,利用這種數據結構,使得在每次查找最大元素時,直接選取堆頂元素,從而時間大大縮短,相對簡單選擇排序算法來說,比較高效。

將初始待排序關鍵字序列(A0, A1, A2 .... An-1)構建成大頂堆(從最後一個非葉子結點 i = len/2 - 1 自下而上),此堆為初始的無序區(構建堆)
將堆頂元素A[0]與最後一個元素A[n-1]交換,此時得到新的無序區(A0, A1, A2,......An-2)和新的有序區(An-1) (交換首尾元素)
由於交換後新的堆頂A[0]可能違反堆的性質,因此需要對當前無序區(A0,A1,......An-2)重新調整為新的大頂堆(從根節點 i = 0,自上而下) (調整堆)
然後再次將A[0]與無序區最後一個元素交換,得到新的無序區(A0,A1....Rn-3)和新的有序區(An-2,An-1)。(不斷循環)
不斷重復此過程直到有序區的元素個數為n-1,則整個排序過程完成
// 下標從0開始,左孩子是2start+1; 如果從1開始,是2start

void HeapAdjust(int *A, int start, int end)
{
    int k;
    int tmp = A[start];
    for(k=2*start+1; k<=end; k=k*2+1)           // k=k*2+1 是取左孩子
    {
        if(k<end && A[k]<A[k+1])    // 比較左右孩子,選較大者
            k++;
        if(tmp<A[k])         // 比較父節點和左右孩子中較大者,如果孩子較大,將孩子節點值賦值給父節點,start指向孩子節點的位置
        {
            A[start] = A[k];
            start = k;
        }
        else
            break;
    }
    A[start] = tmp;      // 將一開始的start的值賦給此時start指向的節點,即將原來不和諧的元素放到合適的位置上
}
void HeapSort(int *A, int len)
{
    int i, j;
    for(i=len/2-1; i>=0; i--)    //從最後一個非葉子結點i(len/2-1)從下至上,從右至左調整結構,構建初始堆
    {
        HeapAdjust(A, i, len);
    }
    for(j=len-1; j>=0; j--)       // 將堆頂元素和末尾元素互換,再將剩下的j-1個元素調整為大頂堆
    {
        int tmp = A[j];
        A[j] = A[0];
        A[0] = tmp;
        HeapAdjust(A, 0, j-1);    // 重建堆是從頂至下,與初始堆相反
    }
}

https://www.cnblogs.com/0zcl/p/6737944.html 堆排序詳解
http://www.cnblogs.com/mengdd/archive/2012/11/30/2796845.html 堆排序 Heap Sort

六、冒泡排序

冒泡排序是一種相對簡單的排序,它每次比較相鄰的兩個元素,如果前者大於後者,則交換< swap >這兩個元素(從小到大排序),這樣每一趟比較就把大的元素沈入最後,形象的稱之為“冒泡”,每走一趟,實際上最尾的元素已經排好。
將序列當中的左右元素,依次比較,保證右邊的元素始終大於左邊的元素;( 第一輪結束後,序列最後一個元素一定是當前序列的最大值;)
對序列當中剩下的n-1個元素再次執行步驟1。
對於長度為n的序列,一共需要執行n-1輪比較。(利用while循環可以減少執行次數)

void BubbleSort(int *A, int len)
{
    int i, j;
    int tmp;
    for(i=0; i<len-1; i++)    // 長度為len的數組,最多需要len-1次冒泡,即可有序(剩下一個自動變為最大或最小)
    {
        for(j=0; j<len-i-1; j++)  // 註意:每次循環比較len-1-i次,如果寫成len-1,多了一次數組會越界,產生不可預料的結果
        {
            if(A[j]>A[j+1])
            {
                tmp = A[j];
                A[j] = A[j+1];
                A[j+1] = tmp;
            }
        }
    }
}

冒泡算法改進之一(加入標誌位判斷是否已經有序):

對冒泡排序常見的改進方法是加入一標誌性變量exchange,用於標誌某一趟排序過程中是否有數據交換,如果進行某一趟排序時並沒有進行數據交換,則說明數據已經按要求排列好,可立即結束排序,避免不必要的比較過程。

// 改進一,加入標誌位
void BubbleSort2(int *A, int len)
{
    int i, j;
    int tmp;
    int flag;        // flag=1時表示已經排序完成
    for(i=0; i<len-1; i++)
    {
        flag = 1;          // 每次冒泡前把flag置1
        for(j=0; j<len-i-1; j++)
        {
            if(A[j]>A[j+1])
            {
                tmp = A[j];
                A[j] = A[j+1];
                A[j+1] = tmp;
                flag = 0;  // 如果發生交換,置0,說明還沒有排序完成
            }
        }
        if(flag==1)  // 如果一次冒泡循環中一次交換也沒發生,則flag還是為1,說明數組已經排好序
            break;
    }
}

冒泡算法改進之二(記錄最後一次交換的位置):

在冒泡排序的每趟掃描中,記住最後一次交換發生的位置lastexchange也能有所幫助。因為該位置之前的相鄰記錄已經有序,故下一趟排序開始的時候,0到lastexchange已經是有序的了,lastexchange到n-1是無序區。所以一趟排序可能使當前有序區擴充多個記錄.即較大縮小無序區範圍,而非遞減1,以此減少排序趟數。這種算法如下:

// 改進二,記錄最後一次交換的位置
void BubbleSort3(int *A, int len)
{
    int j;
    int tmp;
    int flag = len - 1;
    int pos;
    while(flag>0)
    {
        pos = 0;        // 不要漏了該條賦值語句。否則會陷入無限循環。
        
        for(j=0; j<flag; j++)
        {
            if(A[j]>A[j+1])   // 如果沒有交換,則不會進入該if分支,pos還是=0,說明已經排好序,while循環才會結束
            {
                tmp = A[j];
                A[j] = A[j+1];
                A[j+1] = tmp;
                pos = j;           // 記錄最後一次交換的位置
            }
        }
        flag = pos;         // 把位置賦給flag,用於判斷無序區是否還有元素
    }
}

冒泡算法改進之三(雙向冒泡):

傳統冒泡排序中每一趟排序操作只能找到一個最大值或最小值,我們考慮利用在每趟排序中進行正向和反向兩遍冒泡的方法一次可以得到兩個最終值(最大者和最小者) , 從而使排序趟數幾乎減少了一半。

// 改進三,雙向冒泡
void BubbleSort4(int *A, int len)
{
    int low=0, high=len-1;
    int i;
    int tmp;
    while(low<high)
    {
        for(i=low; i<high; i++)  // 正向冒泡,找到最大者
        {
            if(A[i]>A[i+1])
            {
                tmp = A[i];
                A[i] = A[i+1];
                A[i+1] = tmp;
            }
        }
        high--;            // 修改high值, 前移一位 
        for(i=high; i>low; i--)     // 反向冒泡,找到最小者 
        {
            if(A[i]<A[i-1])
            {
                tmp = A[i];
                A[i] = A[i-1];
                A[i-1] = tmp;
            }
        }
        low++;            // 修改low值,後移一位
    }
}

七、快速排序

快速排序(Quicksort)是對冒泡排序的一種改進。
關於快速排序,它的基本思想就是選取一個基準,一趟排序確定兩個區間,一個區間全部比基準值小,另一個區間全部比基準值大,接著再選取一個基準值來進行排序,以此類推,最後得到一個有序的數列。
1.選取基準值,通過不同的方式挑選出基準值。
2.用分治的思想進行分割,通過該基準值在序列中的位置,將序列分成兩個區間,在準值左邊的區間裏的數都比基準值小(默認以升序排序),在基準值右邊的區間裏的數都比基準值大。
3.遞歸調用快速排序的函數對兩個區間再進行上兩步操作,直到調用的區間為空或是只有一個數。

// 基本雙向快速排序
void QuickSort(int *A, int start, int end)
{
    if(start<end){                // 調試時少了這一步,一直報錯
        int i=start, j=end;
        int pivot = A[i];    // 第0個元素作為基準數
        while(i<j)
        {
            while(i<j && A[j]>pivot) j--;
            A[i] = A[j];
            while(i<j && A[i]<pivot) i++;
            A[j] = A[i];
        }
        A[i] = pivot;          // 基準數歸位,i左邊為較小數,右邊為較大數
        QuickSort(A, start, i-1);  // 遞歸調用,將剩下兩部分繼續進行快排
        QuickSort(A, i+1, end);
    }
}

快速排序改進一(基準數隨機選取)

上面版本的快排在選取主元的時候,每次都選取第一個元素。當序列為有序時,會發現劃分出來的兩個子序列一個裏面沒有元素,而另一個則只比原來少一個元素。為了避免這種情況,引入一個隨機化量來破壞這種有序狀態。

// 隨機選取基準數,如果序列基本有序,可以避免分治之後一個區間元素過少,一個區間元素過多的情況
void QuickSort2(int *A, int start, int end)
{
    if(start<end){
        int i=start, j=end;
        int pivot_pos = rand() % (end - start) + start;      // 從start~end間隨機選取一個元素作為基準數
        int pivot = A[pivot_pos];              // 開辟一塊內存存儲pivot值
        SWAP(&A[pivot_pos], &A[start]);        // 交換pivot_pos和start位置上的值,不會影響到上面pivot的值
        while(i<j)
        {
            while(i<j && A[j]>pivot) j--;
            A[i] = A[j];
            while(i<j && A[i]<pivot) i++;
            A[j] = A[i];
        }
        A[i] = pivot;          // 基準數歸位,i左邊為較小數,右邊為較大數
        QuickSort2(A, start, i-1);  // 遞歸調用,將剩下兩部分繼續進行快排
        QuickSort2(A, i+1, end);
    }
}

快速排序改進二(三數取中)

但是隨機函數本身也要消耗一定的時間,而且隨機選取也有可能出現不好分割的幾率,所以又提出了三數取中法,即取左端、右端和中間三個元素排序後取中間的數作為關鍵元素。

// 三數取中選取基準數,選取序列頭尾還有中間的三個數,取三個中值在中間的元素作為基準數
void QuickSort3(int *A, int start, int end)
{
    if(start<end){
        int mid = (start + end) / 2;
        if(A[mid]>A[end]) SWAP(&A[mid], &A[end]);
        if(A[start]>A[end]) SWAP(&A[start], &A[end]);
        if(A[start]<A[mid]) SWAP(&A[start], &A[mid]);        //通過以上三步可以找到中間值,存放在A[start]中
        int i=start, j=end;
        int pivot = A[start];
        while(i<j)
        {
            while(i<j && A[j]>pivot) j--;
            A[i] = A[j];
            while(i<j && A[i]<pivot) i++;
            A[j] = A[i];
        }
        A[i] = pivot;          // 基準數歸位,i左邊為較小數,右邊為較大數
        QuickSort3(A, start, i-1);  // 遞歸調用,將剩下兩部分繼續進行快排
        QuickSort3(A, i+1, end);
        return;
    }
    else
        return;
}

快速排序改進三(當待排序序列的長度分割到一定大小後,使用插入排序)

原因:對於很小和部分有序的數組,快排不如插排好。當待排序序列的長度分割到一定大小後,繼續分割的效率比插入排序要差,此時可以使用插排而不是快排
截止範圍:待排序序列長度N = 10,雖然在5~20之間任一截止範圍都有可能產生類似的結果。這種做法也避免了一些有害的退化情形。

if (start - end + 1 < 10)  
{  
    InsertSort(A, start, end);  
    return;  
}
//else時,正常執行快排 

快速排序改進四(尾遞歸,減少一次遞歸)

其實這種優化編譯器會自己優化,相比不使用優化的方法,時間幾乎沒有減少。
QuickSort函數在其尾部有兩次遞歸操作。如果待排序的序列劃分極端不平衡,遞歸的深度將趨近於n,而不是平衡時的logn。

// 尾遞歸,減少遞歸深度
int Partition(int *A, int start, int end)
{
    int pivot = A[start];
    while(start<end)
    {
        while(start<end && A[end]>pivot) end--;
        SWAP(&A[end], &A[start]);
        while(start<end && A[start]<pivot) start++;
        SWAP(&A[start], &A[end]);
    }
    return start;
}
?
void QuickSortTail(int *A, int start, int end)
{
    /* 普通遞歸方式,start和end這兩個局部變量在下一次函數調用中還需要使用,所以需要繼續堆棧
    if(start<end)
    {
        int pivot_pos = Partition(A, start, end);
        QuickSort(A, start, pivot_pos-1);
        QuickSort(A, pivot_pos+1, end);
    }
    */
    while(start<end)
    {
        int pivot_pos = Partition(A, start, end);
?
        if(pivot_pos-start < end-pivot_pos)            // 短的部分采用遞歸,可以有效減少遞歸深度
        {
            QuickSortTail(A, start, pivot_pos-1);        // 左半部分繼續遞歸
            start = pivot_pos + 1;
        }
        else
        {
            QuickSortTail(A, pivot_pos+1, end);        // 右半部分繼續遞歸
            end = pivot_pos - 1;
        }
?
    }
}

八、歸並排序

歸並排序是建立在歸並操作上的一種有效的排序算法。該算法是采用分治法(Divide and Conquer)的一個非常典型的應用。
技術分享圖片

(1)遞歸方式實現歸並排序

分為兩個函數實現:
1、分:可以看到這種結構很像一棵完全二叉樹,本文的歸並排序我們采用遞歸去實現(也可采用叠代的方式去實現)。分階段可以理解為就是遞歸拆分子序列的過程,遞歸深度為log2n。
2、並:然後考慮下如何將將二個有序數列合並。這個非常簡單,只要從比較二個數列的第一個數,誰小就先取誰,取了後就在對應數列中刪除這個數。然後再進行比較,如果有數列為空,那直接將另一個數列的數據依次取出即可。

// 把兩個有序數組合並成一個有序數組
void MergeArray(int *A, int len_A, int *B, int len_B)
{
    int i, j, k;
    i = j = k = 0;
    int temp[len_A + len_B];
    while(i<len_A && j<len_B)          //取兩個數組第一個元素比較,小的存入temp中
    {
        if(A[i]<B[j])
            temp[k++] = A[i++];
        else
            temp[k++] = B[j++];
    }
    while(i<len_A)                    //兩個數組中剩余的元素一定是偏大的,直接存入temp中
    {
        temp[k++] = A[i++];
    }
    while(j<len_B)
    {
        temp[k++] = B[j++];            
    }
    for(i=0; i<(len_A+len_B); i++)
        A[i] = temp[i];                //temp再轉存入A數組中
}
//遞歸實現歸並排序
void MergeSort1(int *A, int len)
{
    if(len>1)
    {
        // 數組分成兩半
        int *list1 = A;
        int list1_len = len/2;
        int *list2 = A + len/2; 
        int list2_len = len - list1_len;
        MergeSort1(list1, list1_len);  //左邊部分遞歸
        MergeSort1(list2, list2_len);  //右邊部分遞歸
        MergeArray(list1, list1_len, list2, list2_len);  //合並兩個有序數組
    }
}

(2)叠代方式實現歸並排序

非遞歸的方法,避免了遞歸時深度為log2N的棧空間,空間只是用到歸並臨時申請的跟原來數組一樣大小的空間,並且在時間性能上也有一定的提升,因此,使用歸並排序是,盡量考慮用非遞歸的方法。
無論是基於遞歸還是循環的歸並排序, 它們調用的核心方法都是相同的:完成一趟合並的算法,即兩個已經有序的數組序列合並成一個更大的有序數組序列 (前提是兩個原序列都是有序的)
技術分享圖片

//叠代實現歸並排序
void MergeSort2(int *A, int len)
{
    int left_min, left_max, right_min, right_max;
    int i, next;
    int *temp = (int *)malloc(len*sizeof(int));
    if(temp == -1) return;
    for(i=1; i<len; i*=2)      //步長從1開始,以2的倍數遞增
    {
        for(left_min=0; left_min<len-i; left_min=right_max)  // 每次循環結束後,把letf_min指向right_max;right_max指向right_min+i;
        {
            right_min = left_max = left_min + i;
            right_max = right_min + i;
            if(right_max>len)        // right_max 最大為len,如果超出,則等於len
            {
                right_max = len;
            }
            next = 0;
            while(left_min<left_max && right_min<right_max)  // 合並[left_min, left_max]和[right_min, right_max]兩個區間的數組
            {
                if(A[left_min]<A[right_min])
                {
                    temp[next++] = A[left_min++];
                }
                else
                {
                    temp[next++] = A[right_min++];
                }
            }
            while(left_min<left_max)      // 如果是左邊的元素還有剩余,則把它們移動到right數組的最右邊;如果是右邊的數組元素還有剩余,則不用動
            {
                A[--right_min] = A[--left_max];
            }
            while(next>0)                // 再把temp數組填充到left和right數組中
            {
                A[--right_min] = temp[--next];
            }
        }
    }
}

九、基數排序

(一)首先談談計數排序

前面所有的排序算法都存在比較,都可以稱為”比較排序“。比較排序的下界為o(nlogn)。那麽有沒有時間復雜度為o(n)的線性時間排序算法呢?計數排序便是很基礎的一種線性時間排序,它是基數排序的基礎。基本思想是:對每一個元素x,確定小於x的元素個數,就可以把x直接放到它在有序序列中的位置上。過程描述:假設待排序序列a中值的範圍[0,k],其中k表示待排序序列中的最大值。首先用一個輔助數組count記錄各個值在a中出現的次數,比如count[i]表示i在a中的個數。然後依次改變count中元素值,使count[i]表示a中不大於i的元素個數。然後從後往前掃描a數組,a中的元素根據count中的信息直接放到輔助數組b中。最後把有序序列b復制到a。

計數排序是穩定的排序算法;平均時間復雜度、最優時間復雜度和最差時間復雜度都為O(n+k),空間復雜度為O(n+k),其中,n為待排元素個數,k為待排元素的範圍(0~k)。

// 計數排序
void CountSort(int *A, int len)
{
    int min = A[0];
    int max = A[0];
    int i = 0;
    //找出數組中的最大值和最小值,確定哈希表的大小
    for(i=0; i<len; i++)
    {
        if(A[i]<min) min = A[i];
        if(A[i]>max) max = A[i];
    }
    int count_size = max - min + 1;
    int *count_arr = (int *)malloc(sizeof(int) * count_size);   // 創建一個用於計數的數組
    int *temp = (int *)malloc(sizeof(int) * len);
    for(i=0; i<count_size; i++) 
        count_arr[i] = 0;   // 計數數組全部初始化為0
    for(i=0; i<len; i++) 
        count_arr[A[i]-min]++;    // 統計數組A中各元素出現的次數,並放在對應的位置上
    for(i=1; i<count_size; i++) 
        count_arr[i] += count_arr[i-1];  // 儲存自己數組下標數值在目標數組對應的位置,保證穩定性 
    for(i=0; i<count_size; i++){
        //printf("%d, ", count_arr[i]);
    }
    //printf("\n");
    for(i=len-1; i>=0; i--)
    {
        temp[count_arr[A[i]-min]-1] = A[i];  //將原數組按大小順序儲存到另一個數組
        count_arr[A[i]-min]--;
        //temp[--count_arr[A[i] - min]] = A[i];
    }
    for(i=0; i<len; i++) 
        A[i] = temp[i];
    
    free(count_arr);
    free(temp);
}

不過該計數排序不能對負數進行排序,如果需要對負數進行排序,需要進行改進(先一次遍歷待排數組,找出負數中的最大值和最小值,正數中的最大值和最小值,創建兩個計數數組,negativeCountArray用來統計待排數組中各個不同負數的出現個數,positiveCountArray用來統計待排數組中各個正數出現的個數,先統計總的負數的個數,然後再統計各個不同正數的個數,然後在往sortedArray中放元素的時候對正數和負數區別對待。)

(一)基數排序

基數排序的基本思想是:一共有10個"桶",代表各個數位為0~9.在每個桶上,組織一個優先隊列,對於輸入的一系列正整數和0,按照個位的大小關系分別進入10個"桶"中.然後遍歷每個"桶",按照十位的大小關系進行調整,緊接著是百位,千位.......直到到達最大數的最大位數。
技術分享圖片

基數排序只是針對於數字,思想就是將我們需要待排列的元素按照指定的進制將每一位排列,時間復雜度為:P(N+B),註:其中P為待排列數字的最大位數,N為待排序列的長度,B為進制數。

//基數排序
void RadixSort(int *A, int len)
{
    int max_digit = 0;
    int i, j;
    int radix = 10;
    //求數組中的最大位數
    for (i = 0; i < len; ++i)
    {
        while (A[i] > (pow(10, max_digit)))
        {
            max_digit++;
        }
    }
    int flag = 1;
    for(j=1; j<=max_digit; j++)
    {
        //建立數組 統計每個位上不同數字出現的次數
        int digit_count_arr[10] = { 0 };
        for(i=0; i<len; i++)
            digit_count_arr[(A[i] / flag) % radix]++;
        //建立數組 統計A[i]在temp中的起始下標
        int BeginIndex[10] = { 0 };
        for(i=1; i<radix; i++)
            BeginIndex[i] = BeginIndex[i - 1] + digit_count_arr[i - 1];
        //建立臨時數組 用於收集數據
        int *tmp = (int *)malloc(sizeof(int) * len);
        //初始化
        for(i=0; i<len; i++)
            tmp[i] = 0;
        //將數據寫入臨時數組
        for(i=0; i<len; i++)
        {
            int index = (A[i] / flag) % 10;
            tmp[BeginIndex[index]++] = A[i];  // 寫入一個同時要把該位置上的計數加1,因為同一個位置可能有多個元素
        }
        //將數據重新寫回原數組
        for(i=0; i<len; i++)
            A[i] = tmp[i];
        flag = flag * 10;  // 用於下一位的排序
        free(tmp);
    }
}

資料
https://www.cnblogs.com/maluning/p/7944809.html
常用七大經典排序算法總結(C語言描述)

http://blog.csdn.net/gl486546/article/details/53053069
排序算法之希爾排序

https://www.jianshu.com/p/7d037c332a9d
數據結構常見的八大排序算法(詳細整理)

http://blog.csdn.net/zhangjikuan/article/details/49095533
九大排序算法-C語言實現及詳解

https://www.jianshu.com/p/6777a3297e36
快速排序的優化 和關於遞歸的問題,說說我的想法

http://www.cnblogs.com/Anker/archive/2013/03/04/2943498.html
遞歸與尾遞歸總結

http://blog.csdn.net/touch_2011/article/details/6785881
漫談經典排序算法:四、歸並排序(合並排序)(推薦)

【最全】經典排序算法(C語言)