1. 程式人生 > >各種排序演算法比較

各種排序演算法比較

1.穩定性比較

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

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

2.時間複雜性比較

   平均情況  最好情況 最壞情況
歸併排序 O(nlogn)  O(nlogn) O(nlogn)
基數排序 O(n) O(n) O(n)
快速排序 O(nlogn) O(nlogn) O(n2)
希爾排序 O(n1.5) O(n) O(n1.5)
插入排序 O(n2) O(n) O(n2)

選擇排序

O(n2) O(n2) O(n2)

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個數
也是排好順序的。如此反覆迴圈,直到全部排好順序。

直接插入排序是穩定的。演算法時間複雜度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後面的都是排序排好了的。*/
    }
   }
}
}


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

在直接插入排序演算法中,每次插入一個數,使有序序列只增加1個節點,
並且對插入下一個數沒有提供任何幫助。如果比較相隔較遠距離(稱為
增量)的數,使得數移動時能跨過多個元素,則進行一次比較就可能消除
多個元素交換。D.L.shell於1959年在以他名字命名的排序演算法中實現
了這一思想。演算法先將要排序的一組數按某個增量d分成若干組,每組中
記錄的下標相差d.對每組中全部元素進行排序,然後再用一個較小的增量
對它進行,在每組中再進行排序。當增量減到1時,整個要排序的數被分成
一組,排序完成。

下面的函式是一個希爾排序演算法的一個實現,初次取序列的一半為增量,
以後每次減半,直到增量為1。

希爾排序是不穩定的。
=====================================================
*/
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)

=====================================================
*/
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); /*上面的迴圈退出:即出現比基準點小的數,替換基準點的數*/
     i++; /*後移一個位置,並以此為基準點*/
    }

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

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

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


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

堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。
堆的定義如下:具有n個元素的序列(h1,h2,...,hn),當且僅當
滿足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1)(i=1,2,...,n/2)
時稱之為堆。在這裡只討論滿足前者條件的堆。

由堆的定義可以看出,堆頂元素(即第一個元素)必為最大項。完全二叉樹可以
很直觀地表示堆的結構。堆頂為根,其它為左子樹、右子樹。
初始時把要排序的數的序列看作是一棵順序儲存的二叉樹,調整它們的儲存順序,
使之成為一個堆,這時堆的根節點的數最大。然後將根節點與堆的最後一個節點
交換。然後對前面(n-1)個數重新調整使之成為堆。依此類推,直到只有兩個節點
的堆,並對它們作交換,最後得到有n個節點的有序序列。

從演算法描述來看,堆排序需要兩個過程,一是建立堆,二是堆頂與堆的最後一個元素
交換位置。所以堆排序有兩個函式組成。一是建堆的滲透函式,二是反覆呼叫滲透函式
實現排序的函式。

堆排序是不穩定的。演算法時間複雜度O(nlog2n)。

*/
/*
功能:滲透建堆
輸入:陣列名稱(也就是陣列首地址)、參與建堆元素的個數、從第幾個元素開始
*/
void sift(int *x, int n, int s)
{
int t, k, j;

t = *(x+s); /*暫存開始元素*/
k = s;   /*開始元素下標*/
j = 2*k + 1; /*右子樹元素下標*/

while (j<n)
{
   if (j<n-1 && *(x+j) < *(x+j+1))/*判斷是否滿足堆的條件:滿足就繼續下一輪比較,否則調整。*/
   {
    j++;
   }

   if (t<*(x+j)) /*調整*/
   {
    *(x+k) = *(x+j);
    k = j; /*調整後,開始元素也隨之調整*/
    j = 2*k + 1;
   }
   else /*沒有需要調整了,已經是個堆了,退出迴圈。*/
   {
    break;
   }
}

*(x+k) = t; /*開始元素放到它正確位置*/
}


/*
功能:堆排序
輸入:陣列名稱(也就是陣列首地址)、陣列中元素個數
*/
void heap_sort(int *x, int n)
{
int i, k, t;
int *p;

for (i=n/2-1; i>=0; i--)
{
   sift(x,n,i); /*初始建堆*/
}

for (k=n-1; k>=1; k--)
{
   t = *(x+0); /*堆頂放到最後*/
   *(x+0) = *(x+k);
   *(x+k) = t;
   sift(x,k,0); /*剩下的數再建堆*/
}
}


void main()
{
#define MAX 4
int *p, i, a[MAX];

/*錄入測試資料*/
p = a;
printf("Input %d number for sorting :/n",MAX);
for (i=0; i<MAX; i++)
{
   scanf("%d",p++);
}
printf("/n");

/*測試選擇排序*/


p = a;
select_sort(p,MAX);
/**/


/*測試直接插入排序*/

/*
p = a;
insert_sort(p,MAX);
*/


/*測試氣泡排序*/

/*
p = a;
insert_sort(p,MAX);
*/

/*測試快速排序*/

/*
p = a;
quick_sort(p,0,MAX-1);
*/

/*測試堆排序*/

/*
p = a;
heap_sort(p,MAX);
*/

for (p=a, i=0; i<MAX; i++)
{
   printf("%d ",*p++);
}

printf("/n");
system("pause");
}