1. 程式人生 > >C 各種排序(選擇/冒泡/快速/插入/希爾/歸併/堆)

C 各種排序(選擇/冒泡/快速/插入/希爾/歸併/堆)

1.穩定性比較

插入排序、氣泡排序、二叉樹排序、二路歸併排序及其他線形排序是穩定的

選擇排序、希爾排序、快速排序、堆排序是不穩定的

2.時間複雜性比較

插入排序、氣泡排序、選擇排序的時間複雜性為O(n2)

其它非線形排序的時間複雜性為O(nlog2n)

線形排序的時間複雜性為O(n);

3.輔助空間的比較

線形排序、二路歸併排序的輔助空間為O(n),其它排序的輔助空間為O(1);

4.其它比較

插入、氣泡排序的速度較慢,但參加排序的序列區域性或整體有序時,這種排序能達到較快的速度。

反而在這種情況下,快速排序反而慢了。

當n較小時,對穩定性不作要求時宜用選擇排序,對穩定性有要求時宜用插入或氣泡排序。

若待排序的記錄的關鍵字在一個明顯有限範圍內時,且空間允許是用桶排序。

當n較大時,關鍵字元素比較隨機,對穩定性沒要求宜用快速排序。

當n較大時,關鍵字元素可能出現本身是有序的,對穩定性有要求時,空間允許的情況下。

宜用歸併排序。

當n較大時,關鍵字元素可能出現本身是有序的,對穩定性沒有要求時宜用堆排序。

*************************************************************************************

重溫經典排序思想--C語言常用排序全解 

/*
=============================================================================
相關知識介紹(所有定義只為幫助讀者理解相關概念,並非嚴格定義):
1、穩定排序和非穩定排序

簡單地說就是所有相等的數經過某種排序方法後,仍能保持它們在排序之前的相對次序,我們就說這種排序方法是穩定的。反之,就是非穩定的。
比如:一組數排序前是a1,a2,a3,a4,a5,其中a2=a4,經過某種排序後為a1,a2,a4,a3,a5,
則我們說這種排序是穩定的,因為a2排序前在a4的前面,排序後它還是在a4的前面。假如變成a1,a4,
a2,a3,a5就不是穩定的了。

2、內排序和外排序

在排序過程中,所有需要排序的數都在記憶體,並在記憶體中調整它們的儲存順序,稱為內排序;
在排序過程中,只有部分數被調入記憶體,並藉助記憶體調整數在外存中的存放順序排序方法稱為外排序。

3、演算法的時間複雜度和空間複雜度

所謂演算法的時間複雜度,是指執行演算法所需要的計算工作量。
一個演算法的空間複雜度,一般是指執行這個演算法所需要的記憶體空間。
================================================================================
*/
/*
================================================
功能:選擇排序
輸入:陣列名稱(也就是陣列首地址)、陣列中元素個數
================================================
*/
/*
====================================================
演算法思想簡單描述:

在要排序的一組數中,選出最小的一個數與第一個位置的數交換;然後在剩下的數當中再找最小的與第二個位置的數交換,如此迴圈到倒數第二個數和最後一個數比較為止。

選擇排序是不穩定的。演算法複雜度O(n2)--[n的平方]
=====================================================
*/
void select_sort(int *x, int n)
{
int i, j, min, t;

for (i=0; i<n-1; i++) /*要選擇的次數:0~n-2共n-1次*/
{
   min = i; /*假設當前下標為i的數最小,比較後再調整*/
   for (j=i+1; j<n; j++)/*迴圈找出最小的數的下標是哪個*/
   {
    if (*(x+j) < *(x+min))
    {   
     min = j; /*如果後面的數比前面的小,則記下它的下標*/
    }
   }  
  
   if (min != i) /*如果min在迴圈中改變了,就需要交換資料*/
   {
    t = *(x+i);
    *(x+i) = *(x+min);
    *(x+min) = t;
   }
}
}

/*
================================================
功能:直接插入排序
輸入:陣列名稱(也就是陣列首地址)、陣列中元素個數
================================================
*/
/*
====================================================
演算法思想簡單描述:

在要排序的一組數中,假設前面(n-1) [n>=2] 個數已經是排好順序的,現在要把第n個數插到前面的有序數中,使得這n個數也是排好順序的。也就是說,你要進行比較a[p],找到剛好比第i位數大,又比第i+1位數小的那個位置,把要插入的數放進去,怎麼放進去的?就是把比a[p]大的數往後移動一位,如此反覆迴圈,直到全部排好順序。


直接插入排序是穩定的。演算法時間複雜度O(n2)--[n的平方]
=====================================================
*/
void insert_sort(int *x, int n)
{
int i, j, t;

for (i=1; i<n; i++) /*要選擇的次數:1~n-1共n-1次*/
{
   /*
    暫存下標為i的數。注意:下標從1開始,原因就是開始時
    第一個數即下標為0的數,前面沒有任何數,單單一個,認為
    它是排好順序的。
   */
   t=*(x+i);
   for (j=i-1; j>=0 && t<*(x+j); j--) /*注意:j=i-1,j--,這裡就是下標為i的數,在它前面有序列中找插入位置。*/
   {
    *(x+j+1) = *(x+j); /*如果滿足條件就往後挪。最壞的情況就是t比下標為0的數都小,它要放在最前面,j==-1,退出迴圈*/
   }

   *(x+j+1) = t; /*找到下標為i的數的放置位置*/
}
}

/*
================================================
功能:氣泡排序
輸入:陣列名稱(也就是陣列首地址)、陣列中元素個數
================================================
*/
/*
====================================================
演算法思想簡單描述:

在要排序的一組數中,對當前還未排好序的範圍內的全部數,自上而下對相鄰的兩個數依次進行比較和調整,讓較大的數往下沉,較小的往上冒。即:每當兩相鄰的數比較後發現它們的排序與排序要求相反時,就將它們互換。

下面是一種改進的冒泡演算法,它記錄了每一遍掃描後最後下沉數的
位置k,這樣可以減少外層迴圈掃描的次數。

氣泡排序是穩定的。演算法時間複雜度O(n2)--[n的平方]
=====================================================
*/

void bubble_sort(int *x, int n)
{
int j, k, h, t;
  
for (h=n-1; h>0; h=k) /*迴圈到沒有比較範圍*/
{
   for (j=0, k=0; j<h; j++) /*每次預置k=0,迴圈掃描後更新k*/
   {
    if (*(x+j) > *(x+j+1)) /*大的放在後面,小的放到前面*/
    {
     t = *(x+j);
     *(x+j) = *(x+j+1);
     *(x+j+1) = t; /*完成交換*/
     k = j; /*儲存最後下沉的位置。這樣k後面的都是排序排好了的。如果沒有這一步,就是按照傳統的冒泡的話,k後面排序好的,也要在進行 一次,因為傳統冒泡,的迴圈,每次值比上一次少比較一次*/
    }
   }
}
}

/*
================================================
功能:希爾排序
輸入:陣列名稱(也就是陣列首地址)、陣列中元素個數
================================================
*/
/*
====================================================
演算法思想簡單描述:
該方法的基本思想是:先將整個待排元素序列分割成若干個子序列(由相隔某個“增量”的元素組成的)分別進行直接插入排序,然後依次縮減增量再進行排序,待整個序列中的元素基本有序(增量足夠小)時,再對全體元素進行一次直接插入排序。因為直接插入排序在元素基本有序的情況下(接近最好情況),效率是很高的,因此希爾排序在時間效率上比前兩種方法有較大提高。

以n=10的一個數組49, 38, 65, 97, 26, 13, 27, 49, 55, 4為例

第一次 gap = 10 / 2 = 5

49   38   65   97   26   13   27   49   55   4

1A                                        1B

        2A                                         2B

                 3A                                         3B

                         4A                                          4B

                                  5A                                         5B

1A,1B,2A,2B等為分組標記,數字相同的表示在同一組,大寫字母表示是該組的第幾個元素, 每次對同一組的資料進行直接插入排序。即分成了五組(49, 13) (38, 27) (65, 49)  (97, 55)  (26, 4)這樣每組排序後就變成了(13, 49)  (27, 38)  (49, 65)  (55, 97)  (4, 26),下同。

第二次 gap = 5 / 2 = 2

排序後

13   27   49   55   4    49   38   65   97   26

1A             1B             1C              1D            1E

        2A               2B             2C             2D              2E

第三次 gap = 2 / 2 = 1

4   26   13   27   38    49   49   55   97   65

1A   1B     1C    1D    1E      1F     1G    1H     1I     1J

第四次 gap = 1 / 2 = 0 排序完成得到陣列:

4   13   26   27   38    49   49   55   65   97


希爾排序是不穩定的。
=====================================================
*/
void shell_sort(int *x, int n)
{
int h, j, k, t;

for (h=n/2; h>0; h=h/2) /*控制增量*/
{
   for (j=h; j<n; j++) /*這個實際上就是上面的直接插入排序*/
   {
    t = *(x+j);
    for (k=j-h; (k>=0 && t<*(x+k)); k-=h)
    {
     *(x+k+h) = *(x+k);
    }
    *(x+k+h) = t;
   }
}
}

/*
================================================
功能:快速排序
輸入:陣列名稱(也就是陣列首地址)、陣列中起止元素的下標
================================================
*/
/*
====================================================
演算法思想簡單描述:

快速排序是對氣泡排序的一種本質改進。它的基本思想是通過一趟掃描後,使得排序序列的長度能大幅度地減少。在氣泡排序中,一次掃描只能確保最大數值的數移到正確位置,而待排序序列的長度可能只減少1。快速排序通過一趟掃描,就能確保某個數(以它為基準點吧)的左邊各數都比它小,右邊各數都比它大。然後又用同樣的方法處理它左右兩邊的數,直到基準點的左右只有一個元素為止。它是由C.A.R.Hoare於1962年提出的。

顯然快速排序可以用遞迴實現,當然也可以用棧化解遞迴實現。下面的函式是用遞迴實現的,有興趣的朋友可以改成非遞迴的。

快速排序是不穩定的。最理想情況演算法時間複雜度O(nlog2n),最壞O(n2)

舉例說明一下吧,這個可能不是太好理解。假設要排序的序列為

2 2 4 9 3 6 7 1 5 首先用2當作基準,使用i j兩個指標分別從兩邊進行掃描,把比2小的元素和比2大的元素分開。首先比較2和5,5比2大,j左移

2 2 4 9 3 6 7 1 5 比較2和1,1小於2,所以把1放在2的位置

2 1 4 9 3 6 7 1 5 比較2和4,4大於2,因此將4移動到後面

2 1 4 9 3 6 7 4 5 比較2和7,2和6,2和3,2和9,全部大於2,滿足條件,因此不變,然後再把4替換為2

經過第一輪的快速排序,元素變為下面的樣子

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

之後,在把2左邊的元素進行快排,由於只有一個元素,因此快排結束。右邊進行快排,遞迴進行,最終生成最後的結果。


=====================================================
*/
void quick_sort(int *x, int low, int high)
{
int i, j, t;

if (low < high) /*要排序的元素起止下標,保證小的放在左邊,大的放在右邊。這裡以下標為low的元素為基準點*/
{
   i = low;
   j = high;
   t = *(x+low); /*暫存基準點的數*/

   while (i<j) /*迴圈掃描*/
   {
    while (i<j && *(x+j)>t) /*在右邊的只要比基準點大仍放在右邊*/
    {
     j--; /*前移一個位置*/
    }

    if (i<j) 
    {
     *(x+i) = *(x+j); /*上面的迴圈退出:即出現比基準點小的數,替換基準點的數*///此時*(x + i)的值儲存在t中
     i++; /*後移一個位置,並以此為基準點*/
    }

    while (i<j && *(x+i)<=t) /*在左邊的只要小於等於基準點仍放在左邊*/
    {
     i++; /*後移一個位置*/
    }

    if (i<j)
    {
     *(x+j) = *(x+i); /*上面的迴圈退出:即出現比基準點大的數,放到右邊 (x + j)值已經在上上一步複製給了*(x + i)*/
     j--; /*前移一個位置*/
    }
   }

   *(x+i) = t; /*一遍掃描完後,放到適當位置*/
   quick_sort(x,low,i-1);   /*對基準點左邊的數再執行快速排序*/
   quick_sort(x,i+1,high);   /*對基準點右邊的數再執行快速排序*/
}
}

//以上 參考 http://blog.csdn.net/TrueLie/article/details/1687466

歸併排序見我以前轉載的文章

堆排序例項(因為堆排序並不能)

   首先,建立初始的堆結構如圖: (主要就是這幾幅圖,很厲害啊)

  然後,交換堆頂的元素和最後一個元素,此時最後一個位置作為有序區(有序區顯示為黃色),然後進行其他無序區的堆調整,重新得到大頂堆後,交換堆頂和倒數第二個元素的位置……

  重複此過程:

  最後,有序區擴充套件完成即排序完成:

  由排序過程可見,若想得到升序,則建立大頂堆,若想得到降序,則建立小頂堆

程式碼

  假設排列的元素為整型,且元素的關鍵字為其本身。

  因為要進行升序排列,所以用大頂堆。

  根結點從0開始,所以i結點的左右孩子結點的下標為2i+1和2i+2。

//堆篩選函式
//已知H[start~end]中除了start之外均滿足堆的定義
//本函式進行調整,使H[start~end]成為一個大頂堆
typedef int ElemType;
void HeapAdjust(ElemType H[], int start, int end)
{

    ElemType temp = H[start];

    for(int i = 2*start + 1; i<=end; i*=2)
    {
        //因為假設根結點的序號為0而不是1,所以i結點左孩子和右孩子分別為2i+1和2i+2
        if(i<end && H[i]<H[i+1])//左右孩子的比較
        {
            ++i;//i為較大的記錄的下標
        }

        if(temp > H[i])//左右孩子中獲勝者與父親的比較
        {
            break;
        }

        //將孩子結點上位,則以孩子結點的位置進行下一輪的篩選
        H[start]= H[i];
        start = i;
        
    }

    H[start]= temp; //插入最開始不和諧的元素
}

void HeapSort(ElemType A[], int n)
{
    //先建立大頂堆
    for(int i=n/2; i>=0; --i)
    {
        HeapAdjust(A,i,n);
    }
    //進行排序
    for(int i=n-1; i>0; --i)
    {
        //最後一個元素和第一元素進行交換
        ElemType temp=A[i];
        A[i] = A[0];
        A[0] = temp;

        //然後將剩下的無序元素繼續調整為大頂堆
        HeapAdjust(A,0,i-1);
    }

}
堆排序 參考http://www.cnblogs.com/mengdd/archive/2012/11/30/2796845.html