常見排序算法及實現

1、冒泡排序

     冒泡排序(Bubble Sort)是先從數組第一個元素開始,依次比較相鄰兩個數,若前者比後者ios

大,就將二者交換位置,而後處理下一對,依此類推,不斷掃描數組,直到完成排序。git

     這個算法的名字由來是由於越大的元素會經由交換慢慢「浮」到數列的頂端,故名。算法

一、算法原理

     冒泡排序算法的運做以下:(從後往前)數組

    1)比較相鄰的元素。若是第一個比第二個大,就交換它們兩個。數據結構

    2)對每一對相鄰元素做一樣的工做,從開始第一對到結尾的最後一對。在這一點,最後的元ide

          素應該會是最大的數。函數

    3)針對全部的元素重複以上的步驟,除了最後一個。測試

    4)持續每次對愈來愈少的元素重複上面的步驟,知道沒有任何一對數字須要比較。ui

二、算法分析

    時間複雜度:

    若文件的初始狀態是正序的,一趟掃描便可完成排序。所需的的關鍵字比較次數爲最小n-1和spa

記錄移動次數均爲最小0。因此最好的時間複雜度爲O(n)。  

    若初始文件是反序的,須要進行趟排序。每趟排序要進行次關鍵字的比較(1≤i≤n-

1),且每次比較都必須移動記錄三次來達到交換記錄位置。在這種狀況下,比較和移動次數均

達到最大值。因此最壞時間複雜度爲O(n^2)。

     所以,冒泡排序總的平均時間複雜度爲O(n^2)。

    算法穩定性:

  (穩定性的解釋:假定在待排序的記錄序列中,存在多個具備相同的關鍵字的記錄,若通過排

序,這些記錄的相對次序保持不變,即在原序列中,ri=rj,且ri在rj以前,而在排序後的序列

中,ri仍在rj以前,則稱這種排序算法是穩定的;不然稱爲不穩定的。)

    冒泡排序就是把小的元素往前調或者把大的元素日後調。比較是相鄰的兩個元素比較,交換

也發生在這兩個元素之間。因此,若是兩個元素相等,則沒必要交換;若是兩個相等的元素沒有相

鄰,那麼即便經過前面的兩兩交換把兩個相鄰起來,這時候也不會交換,因此相同元素的先後

元素的先後順序並無改變,因此冒泡排序是一種穩定排序算法。

三、算法實現

/*
  冒泡排序
*/

#include <iostream>
#include <stdio.h>

void bubble_sort( int *array, int length )
{
  if( array == NULL || length <= 0 )
  {
    printf( "invalid input.\n" );
    return;
  }

  int temp;
  
  for( int i = 0; i < length - 1; i++ )
    for( int j = 0; j < length - 1 - i; j++ )
    {
      if( array[ j ] > array[ j + 1 ] )
      {
        temp = array[ j ];
        array[ j ] = array[ j + 1 ];
        array[ j+ 1 ] = temp;
      }
    }
}

void Test( const char* testName, int *array, int length )
{
  if( testName == NULL )
  {
    printf( "test invaild input.\n" );
    return;
  }

  printf( "%s begins: \n", testName );

  bubble_sort( array, length );

  printf( "after bubble sort, the result is: \n" );
  for( int i = 0; i < length; i++ )
    printf( "%d ", array[ i ] );
  printf( "\n" );
}

// 無重複數字
void Test1()
{
  int length = 6;
  int array[ ] = { 4, 2, 6, 7, 1, 5 };
  Test( "Test1", array, length ); 
}

// 有重複數字
void Test2()
{
  int length = 6;
  int array[ ] = { 4, 2, 6, 2, 1, 5 };
  Test( "Test2", array, length ); 
}

// 輸入數組爲空
void Test3()
{
  Test( "Test3", NULL, -1 ); 
}

int main()
{
  Test1();
  Test2();
  Test3();
 
  return 0;
}

 

2、選擇排序

    選擇排序(Selection Sort)簡單而低效。它線性逐一掃描數組元素,從中挑出最小的元素,

將它移動到最前面(也就是與最前面的元素交換)。而後,再次線性掃描數組,找到第二小的

元素,並移到前面,如此反覆,直到所有元素各歸其位。

   一、算法原理

    n個記錄的文件的直接選擇排序可通過n-1趟直接選擇排序獲得有序結果:

   1)初始狀態:無序區爲R[1...n],有序區爲空。

   2)第一趟排序

         在無序區R[1...n]中選出關鍵字最小的記錄R[k],將它與無序區的第一個記錄R[1]交換,使

R[1...1]和R[2...n]分別變爲記錄個數增長1個的新有序區和記錄個數較少1個的新無序區。

   ......

  3)第i趟排序

        第i趟排序開始時,當前有序區和無序區分別爲R[1...i-1]和R[i...n]。該趟排序從當前無序區中

選出關鍵字最小的記錄R[k],將它與無序區第一個記錄R交換,使R[1...i]和R分別標爲記錄個數

增長1個的新有序區和記錄個數較少1個的新無序區。

   二、算法分析

    時間複雜度:

    選擇排序的交換操做介於 0 和 (n - 1) 次之間。選擇排序的比較操做爲 n (n - 1) / 2 次之間。

選擇排序的賦值操做介於 0 和 3 (n - 1) 次之間。比較次數O(n^2),比較次數與關鍵字的初始

狀態無關,總的比較次數N=(n-1)+(n-2)+...+1=n*(n-1)/2。因此複雜度爲O(n^2)。

   穩定性:

   選擇排序是給每一個位置選擇當前元素最小的,好比給第一個位置選擇最小的,在剩餘元素裏面

給第二個元素選擇第二小的,依次類推,直到第n-1個元素,第n個 元素不用選擇了,由於只剩

下它一個最大的元素了。那麼,在一趟選擇,若是一個元素比當前元素小,而該小的元素又出

如今一個和當前元素相等的元素後面,那麼 交換後穩定性就被破壞了。舉個例子,序列5 8 5 2

9,咱們知道第一遍選擇第1個元素5會和2交換,那麼原序列中兩個5的相對先後順序就被破壞

了,因此選擇排序是一個不穩定的排序算法。

三、算法實現

/*
  選擇排序。
*/

#include <iostream>
#include <stdio.h>

void SelectSort( int *array, int length )
{
  if( array == NULL || length <= 0 )
  {
    printf( "invaild input.\n" );
    return;
  }

  int index = 0;

  // 每次循環只進行一次交換,最多進行len - 1次循環,比冒泡進行交換的次數少。
  for( int i = 0; i < length - 1; i++ )
  {
    // 第一次排序時,已經進行一次大循環,所以已經排好了1個元素
    // 已排好序的元素0,...,i-2,i-1
    // 待排元素爲i,i+1,...,length-1

    index = i;
    for( int j = i + 1; j < length; j++ )
    {
      if( array[ j ] < array[ index ] )
      {
        index = j;
      }
    }

    // 交換
    if( index != i )
    {
      int temp = array[ i ];
      array[ i ] = array[ index ];
      array[ index ] = temp;
    }
  }
}

void Test( const char *testName, int *array, int length )
{
  if( testName == NULL )
  {
    printf( "test invaild input.\n" );
    return;
  }
  
  printf( "%s begins: \n", testName );

  SelectSort( array, length );

  printf( "after select sort, the result is: \n" );
  for( int i = 0; i < length; i++ )
    printf( "%d ", array[ i ] );
  printf( "\n" );
}

// 無重複數字
void Test1()
{
  int length = 6;
  int array[ ] = { 4, 2, 6, 7, 1, 5 };
  Test( "Test1", array, length ); 
}

// 有重複數字
void Test2()
{
  int length = 6;
  int array[ ] = { 4, 2, 6, 2, 1, 5 };
  Test( "Test2", array, length ); 
}

// 輸入數組爲空
void Test3()
{
  int array[ ] = {};
  Test( "Test3", array, 0 ); 
}

// 輸入數組爲null,且長度異常
void Test4()
{
  Test( "Test4", NULL, -1 ); 
}

int main()
{
  Test1();
  Test2();
  Test3();
  Test4();

  return 0;
}

3、歸併排序     

      歸併排序(Merge-Sort)是創建在歸併操做上的一種有效的排序算法,該算法是採用分治法

(Divide and Conquer)的一個很是典型的應用。將數組分紅兩半,這兩半分別排序後,再歸

並在一塊兒。排序某一半時,繼續沿用一樣的排序算法,最終,將歸併兩個只含一個元素的數

組。

    一、算法原理

    歸併操做的工做原理以下:

   1)申請空間,使其大小爲兩個已經排序序列之和,該空間用來存放合併後的序列。

   2)設定兩個指針,最初位置分別爲兩個已經排序序列的起始位置。

   3)比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位 

         置。

   4)重複步驟3直到某一指針超出序列尾,將另外一序列剩下的全部元素直接複製到合併序列

         尾。

   二、算法分析

    時間複雜度:

    歸併排序的效率是比較高的,設數列長爲N,將數列分開成小數列一共要logN步,每一步都

是一個合併有序數列的過程,時間複雜度能夠記爲O(N),故一共爲O(N*logN)。

   穩定性:

   歸併排序是穩定的排序.即相等的元素的順序不會改變.如輸入記錄 1(1) 3(2) 2(3) 2(4) 5(5) (括

號中是記錄的關鍵字)時輸出的 1(1) 2(3) 2(4) 3(2) 5(5) 中的2 和 2 是按輸入的順序.這對要排序數

據包含多個信息而要按其中的某一個信息排序,要求其它信息儘可能按輸入的順序排列時很重要.這

也是它比快速排序優點的地方。

  三、算法實現

/*
  歸併排序。
*/

#include <iostream>
#include <stdio.h>

void merge( int *array, int low, int mid, int high )
{
  int nLeft = low;  // nLeft是左邊序列的下標
  int nRight = mid + 1;  // nRight是右邊序列的下標
  int nMerge = 0; // nMerge是臨時存放合併的下標

  int *tempArray = ( int* )new int[ high - low + 1 ];  // tempArray是臨時合併序列

  // 掃描左邊序列和右邊序列,直到一邊掃描結束
  while( nLeft <= mid && nRight <= high )
  {
    if( array[ nLeft ] <= array[ nRight ] )
    {
      tempArray[ nMerge ] = array[ nLeft ];
      nLeft++;
      nMerge++;
    }
    else
    {
      tempArray[ nMerge ] = array[ nRight ];
      nRight++;
      nMerge++;
    }
  }

  // 若左邊序列還沒掃描完,則將其所有賦值到臨時合併序列
  while( nLeft <= mid )
  {
    tempArray[ nMerge ] = array[ nLeft ];
    nLeft++;
    nMerge++;
  }

  // 若右邊序列還沒掃描完,則將其所有賦值到臨時合併序列
  while( nRight <= high )
  {
    tempArray[ nMerge ] = array[ nRight ];
    nRight++;
    nMerge++;
  }

  // 將臨時合併序列複製到原始序列中
  for( nMerge = 0, nLeft = low; nLeft <= high; nLeft++, nMerge++ )
  {
    array[ nLeft ] = tempArray[ nMerge ];
  }

  delete [] tempArray;
}

void mergeSort( int *array, int low, int high )
{
  if( array == NULL || low < 0 || high < 0 || low > high )
  {
    printf( "invaild input.\n" );
    return;
  }

  if( low < high )
  {
    int mid = ( low + high ) / 2 ;
    mergeSort( array, low, mid );
    mergeSort( array, mid + 1, high );
    merge( array, low, mid, high );
  }
}

void Test( const char* testName, int *array, int low, int high )
{
  if( testName == NULL )
  {
    printf( "test invaild input.\n" );
    return;
  }

  printf( "%s begins: \n", testName );
  
  mergeSort( array, low, high );

  printf( "after select sort, the result is: \n" );
  for( int i = 0; i < high - low + 1; i++ )
    printf( "%d ", array[ i ] );
  printf( "\n" );
}

// 無重複數字
void Test1()
{
  int length = 6;
  int array[ ] = { 4, 2, 6, 7, 1, 5 };
  Test( "Test1", array, 0, 5 ); 
}

// 有重複數字
void Test2()
{
  int length = 6;
  int array[ ] = { 4, 2, 6, 2, 1, 5 };
  Test( "Test2", array, 0, 5 ); 
}

// 輸入數字只有一個元素
void Test3()
{
  int array[ ] = { 100 };
  Test( "Test3", array, 0, 0 ); 
}

// 輸入數組爲空
void Test4()
{
  int emptyArray[ ] = { };
  Test( "Test4", emptyArray, 0, 0 ); 
}

// 輸入數組爲null,且長度異常
void Test5()
{
  Test( "Test5", NULL, -1, -1 ); 
}

int main()
{
  Test1();
  Test2();
  Test3();
  Test4();
  Test5();

  return 0;
}

   (補充:一、當數組爲空,low=0,high=0時,數組本應沒有元素,但gcc編譯後最終會打印出

一個元素,這個元素經屢次測試是上次Test結果的最後一個元素。若是上次沒有執行Test則會出

現如:-1220152147 之類的未初始化結果;  二、固然對於數組指針爲NULL的狀況,打印就會出

現段錯誤)。

   (繼續補充:好吧,沒按捺住,又去找了爲啥是上次Test結果的最後一個元素,能夠從上圖看

出,緣由是在初始化emptyArray[]數組時(也就是:int emptyArray[ ] = { };這句代碼上),gcc每

次都分配0xbffff0cc地址給emptyArray[],而是這個地址正是上一次Test結果中的最後一個元素地

址,並且每次array[]無論有多少元素的最後一個元素地址都是0xbffff0cc,至於緣由應該跟具體

gcc數組內存分配策略有關,固然這裏就再也不跟下去了,要否則就沒飯吃了╮(╯▽╰)╭ )。

4、快速排序

    快速排序(Quicksort)是對冒泡跑序的一種改進。它的基本思想是:隨機選擇一個元素,對

數組進行分割,將全部比它小的元素排在前面,比它大的元素則排後面。這裏的分割經由一系

列元素交換的動做完成。而後再按此方法對這兩部分數據分別進行快速排序,整個排序過程可

以遞歸進行,以此達到整個數據變成有序序列。

    一、算法原理

    基本思想:

    1)先從數列中取出一個數做爲基準數;

    2)分區過程,將比這個數大的數全放到它的右邊,小於或等於它的數全放到它的左邊;

    3)再對左右區間重複第二步,直到各區間只有一個數。

    一趟快速排序的算法是:

   1)設置兩個變量i、j,排序開始的時侯:i=0,j=N-1; 

   2)以第一個數組元素做爲關鍵數據,賦值給key,即key=A[0];

   3)從j開始向前搜索,即由後開始向前搜索(j--),找到第一個小於key的值A[j],將A[j]和A[i]互

         換;

   4)從i開始向後搜索,即由前開始向後搜索(i++),找到第一個大於key的A[i],將A[i]和A[j]互換;

   5)重複第三、4步,直到i=j。 (3,4步中,沒找到符合條件的值,即3中A[j]不小於key,4中A[i]

        不大於key的時候改變j、i的值,使得j=j-1,i=i+1,直至找到爲止。找到符合條件的值,進

        行交換的時候i, j指針位置不變。另外,i==j這一過程必定正好是i+或j-完成的時候,此時令

        循環結束)。

   快速排序還有不少改進版本,如隨機選擇基準數,區間內數據較少時直接用另外的方法排序以

小遞歸深度。

   二、算法分析

    時間複雜度:

    快速排序之所比較快,由於相比冒泡排序,每次交換是跳躍式的。每次排序的時候設置一個

基準點,將小於等於基準點的數所有放到基準點的左邊,將大於等於基準 點的數所有放到基準

點的右邊。這樣在每次交換的時候就不會像冒泡排序同樣每次只能在相鄰的數之間進行交換,

交換的距離就大的多了。所以總的比較和交換次數 就少了,速度天然就提升了。固然在最壞的

狀況下,仍多是相鄰的兩個數進行了交換。所以快速排序的最差時間複雜度和冒泡排序是一

樣的都是O(N2),它的平均時間複雜度爲O(NlogN)。

   穩定性:

   快速排序有兩個方向,左邊的i下標一直往右走,當a[i] <= a[center_index],其中center_index

是中樞元素的數組下標,通常取爲數組第0個元素。而右邊的j下標一直往左走,當a[j] >

a[center_index]。若是i和j都走不動了,i <= j, 交換a[i]和a[j],重複上面的過程,直到i>j。 交換a[j]

和a[center_index],完成一趟快速排序。在中樞元素和a[j]交換的時候,頗有可能把前面的元素

的穩定性打亂,好比序列爲 5 3 3 4 3 8 9 10 11, 如今中樞元素5和3(第5個元素,下標從1開始

計)交換就會把元素3的穩定性打亂,因此快速排序是一個不穩定的排序算法,不穩定發生在中

樞元素和a[j] 交換的時刻。

  三、算法實現

  第一種實現爲選取第一個元素爲樞紐:

/*
  快速排序。(以第一個元素做爲基準)
*/

#include <iostream>
#include <stdio.h>

void QuickSort( int *array, int low, int high )
{
  if( array == NULL || low < 0 )
  {
    printf( "invalid input.\n" );
    return;
  }
  if( high < 0 || low > high ) // 必須加這句,由於遞歸的時候high可能爲-1
  {
    return;
  }

  int first = low;
  int last = high;
  int key = array[ first ]; // 用第一個元素做爲做爲樞紐
  
  while( first < last )
  {
    while( first < last && array[ last ] >= key )
    {
      --last;
    }

    array[ first ] = array[ last ];  // 將比樞紐元素小的移到低端
    
    while( first < last && array[ first ] <= key )
    {
      ++first;
    }

    array[ last ] = array[ first ];  // 將比樞紐元素大的移到高端
  }
  
  array[ first ] = key; // 樞紐到位記錄

  QuickSort( array, low, first - 1 );
  QuickSort( array, first + 1, high );

}

void Test( const char* testName, int* array, int low, int high )
{
  if( testName == NULL )
  {
    printf( "test invaild input.\n" );
    return;
  }

  printf( "%s begins: \n", testName );
  
  QuickSort( array, low, high );
  
  printf( "after quick sort, the result is: \n" );
  for( int i = 0; i < high - low + 1; i++ )
    printf( "%d ", array[ i ] );
  printf( "\n" );
}

// 無重複數字
void Test1()
{
  int array[ ] = { 4, 2, 6, 7, 1, 5 };
  Test( "Test1", array, 0, 5 ); 
}

// 有重複數字
void Test2()
{
  int array[ ] = { 4, 2, 6, 2, 1, 5 };
  Test( "Test2", array, 0, 5 ); 
}

// 輸入數字只有一個元素
void Test3()
{
  int array[ ] = { 100 };
  Test( "Test3", array, 0, 0 ); 
}

// 輸入數組爲空
void Test4()
{
  int emptyArray[ ] = { };
  Test( "Test4", emptyArray, 0, 0 ); 
}

// 輸入數組爲null,且長度異常
void Test5()
{
  Test( "Test5", NULL, -1, -1 ); 
}

int main()
{
  Test1();
  Test2();
  Test3();
  Test4();
  Test5();

  return 0;
}

 

第二種實現爲隨機選取元素爲樞紐:   

/*
  快速排序。(以隨機元素做爲基準)
*/

#include <stdlib.h>
#include <iostream>
#include <time.h>
#include <stdio.h>

void Swap( int *first, int *second )
{
  int temp = *first;
  *first = *second;
  *second = temp;
}  

int Partition( int *array, int low, int high )
{
  if( array == NULL || low < 0 )
  {
    printf( "invalid input.\n" );
    return -1;
  }

  srand( ( unsigned )time( NULL ) );     // 利用時間設置隨機數種子
  int index = low + rand()%( high - low + 1 ); // 產生low到high的隨機數

  Swap( &array[ index ], &array[ low ] );
  
  int first = low;
  int last = high;
  int key = array[ first ]; // 用第一個元素做爲做爲樞紐
  
  while( first < last )
  {
    while( first < last && array[ last ] >= key )
    {
      --last;
    }

    array[ first ] = array[ last ];  // 將比樞紐元素小的移到低端
    
    while( first < last && array[ first ] <= key )
    {
      ++first;
    }

    array[ last ] = array[ first ];  // 將比樞紐元素大的移到高端
  }
  
  array[ first ] = key; // 樞紐到位記錄

  return first;
}


/*
int Partition( int *array, int low, int high )
{
  if( array == NULL || low < 0 )
  {
    printf( "invalid input.\n" );
    return -1;
  }

  srand( ( unsigned )time( NULL ) );     // 利用時間設置隨機數種子
  int index = low + rand()%( high - low + 1 ); // 產生low到high的隨機數

  Swap( &array[ index ], &array[ low ] );
  
  int small = low - 1;
  for( index = low; index < high; ++index )
  {
    if( array[ index ] < array[ high ] )
    {
      ++small;
      if( small != index )
        Swap( &array[ index ], &array[ small ] );
    }
  }

  ++small;
  Swap( &array[ small ], &array[ high ] );  

  return small;
}
*/

void QuickSort( int *array, int low, int high )
{
  if( low == high )
  {
    return;
  }
  
  int index = Partition( array, low, high );

  if( index < 0 )
    return;
  
  if( index > low )
    QuickSort( array, low, index - 1 );
  if( index < high )
    QuickSort( array, index + 1, high );
}

void Test( const char* testName, int* array, int low, int high )
{
  if( testName == NULL )
  {
    printf( "test invaild input.\n" );
    return;
  }

  printf( "%s begins: \n", testName );
  
  QuickSort( array, low, high );
  
  printf( "after quick sort, the result is: \n" );
  for( int i = 0; i < high - low + 1; i++ )
    printf( "%d ", array[ i ] );
  printf( "\n" );
}

// 無重複數字
void Test1()
{
  int array[ ] = { 4, 2, 6, 7, 1, 5 };
  Test( "Test1", array, 0, 5 ); 
}

// 有重複數字
void Test2()
{
  int array[ ] = { 4, 2, 6, 2, 1, 5 };
  Test( "Test2", array, 0, 5 ); 
}

// 輸入數字只有一個元素
void Test3()
{
  int array[ ] = { 100 };
  Test( "Test3", array, 0, 0 ); 
}

// 輸入數組爲空
void Test4()
{
  int emptyArray[ ] = { };
  Test( "Test4", emptyArray, 0, 0 ); 
}

// 輸入數組爲null,且長度異常
void Test5()
{
  Test( "Test5", NULL, -1, -1 ); 
}

int main()
{
  Test1();
  Test2();
  Test3();
  Test4();
  Test5();

  return 0;
}

   隨機樞紐快速排序的Partition函數有兩種實現。

5、堆排序

    堆排序(Heapsort)是指利用堆積樹(堆)這種數據結構所設計的一種排序算法,它是選擇排序

的一種。能夠利用數組的特色快速定位指定索引的元素。堆分爲大根堆和小根堆,是徹底二叉

。大根堆的要求是每一個節點的值都不大於其父節點的值,即A[PARENT[i]] >= A[i]。在數組的

非降序排序中,須要使用的就是大根堆,由於根據大根堆的要求可知,最大的值必定在堆頂。

    堆其實是一棵徹底二叉樹,其任何一非葉節點知足性質: 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]稱爲小頂堆。由上述性

質可知大頂堆的堆頂的關鍵 字確定是全部關鍵字中最大的,小頂堆的堆頂的關鍵字是全部關鍵

字中最小的。

    一、算法原理

     其基本思想(大頂堆):

    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]同該區間的最後一個記錄交換,而後將新的無序區調整爲新

的堆。

   二、算法分析

    時間複雜度:

    堆排序其實也是一種選擇排序,是一種樹形選擇排序。只不過直接選擇排序中,爲了從

R[1...n]中選擇最大記錄,需比較n-1次,而後 從R[1...n-2]中選擇最大記錄需比較n-2次。事實上

這n-2次比較中有不少已經在前面的n-1次比較中已經作過,而樹形選擇排序剛好利用樹形的 特

點保存了部分前面的比較結果,所以能夠減小比較次數。對於n個關鍵字序列,最壞狀況下每一個

節點需比較log2(n)次,所以其最壞狀況下時間複雜度爲 O(nlogn)。

   穩定性:

   咱們知道堆的結構是節點i的孩子爲2*i和2*i+1節點,大頂堆要求父節點大於等於其2個子節

點,小頂堆要求父節點小於等於其2個子節點。在一個長爲n 的序列,堆排序的過程是從第n/2開

始和其子節點共3個值選擇最大(大頂堆)或者最小(小頂堆),這3個元素之間的選擇固然不會破壞穩

定性。但當爲n /2-1, n/2-2, ...1這些個父節點選擇元素時,就會破壞穩定性。有可能第n/2個父節

點交換把後面一個元素交換過去了,而第n/2-1個父節點把後面一個相同的元素沒 有交換,那麼

這2個相同的元素之間的穩定性就被破壞了。因此,堆排序不是穩定的排序算法。不適合記錄較

少的排序。   

  三、算法實現

/*
  堆排序。
*/

#include <iostream>
#include <stdio.h>

// array是待調整的堆數組,pos是待調整的數組元素的位置,length是數組的長度
// 本函數功能是:根據數組array構建大根堆
void HeapAdjust( int *array, int pos, int length )
{
  if( array == NULL || pos < 0 || length <= 0 )
  {
    printf( "invalid input.\n" );
    return;
  }

  int child;
  int temp;
  
  for( ; 2 * pos + 1 < length; pos = child )
  {
    child = 2 * pos + 1;  // 子結點的位置
    
    if( child < length - 1 && array[ child + 1 ] > array[ child ] )  // 獲得子結點中較大的結點
      ++child;
    
    if( array[ pos ] < array[ child ] )  // 若是較大的子結點大於父結點那麼把較大的子結點往上移動,替換它的父結點
    {
      temp = array[ pos ];
      array[ pos ] = array[ child ];
      array[ child ] = temp;
    }
    else 
      break;
  }
}

void HeapSort( int *array, int length )
{
  int i;
  
  // 調整序列的前半部分元素,調整完以後第一個元素是序列的最大的元素
  // length/2-1是最後一個非葉節點
  for( i = length / 2 - 1; i >= 0; --i )
    HeapAdjust( array, i, length );

  // 從最後一個元素開始對序列進行調整,不斷的縮小調整的範圍直到第一個元素
  for( i = length - 1; i > 0; --i )
  {
    // 把第一個元素和當前的最後一個元素交換,
    // 保證當前的最後一個位置的元素都是在如今的這個序列之中最大的
    array[ i ] = array[ 0 ] ^ array[ i ];
    array[ 0 ] = array[ 0 ] ^ array[ i ];
    array[ i ] = array[ 0 ] ^ array[ i ];
  
    // 不斷縮小調整heap的範圍,每一次調整完畢保證第一個元素是當前序列的最大值
    HeapAdjust( array, 0, i );
  }
}

void Test( const char* testName, int* array, int length )
{
  if( testName == NULL )
  {
    printf( "test invaild input.\n" );
    return;
  }

  printf( "%s begins: \n", testName );
  
  HeapSort( array, length );
  
  printf( "after heap sort, the result is: \n" );
  for( int i = 0; i < length; i++ )
    printf( "%d ", array[ i ] );
  printf( "\n" );
}

// 無重複數字
void Test1()
{
  int array[ ] = { 4, 2, 6, 7, 1, 5 };
  Test( "Test1", array, 6 ); 
}

// 有重複數字
void Test2()
{
  int array[ ] = { 4, 2, 6, 2, 1, 5 };
  Test( "Test2", array, 6 ); 
}

// 輸入數字只有一個元素
void Test3()
{
  int array[ ] = { 100 };
  Test( "Test3", array, 1 ); 
}

// 輸入數組爲空
void Test4()
{
  int emptyArray[ ] = { };
  Test( "Test4", emptyArray, 0 ); 
}

// 輸入數組爲null,且長度異常
void Test5()
{
  Test( "Test5", NULL, -1 ); 
}

int main()
{
  Test1();
  Test2();
  Test3();
  Test4();
  Test5();

  return 0;
}

6、基數排序

    基數排序(Radix sort)屬於「分配式排序」(distribution sort),又稱「桶子法」(bucket sort)

或bin sort,顧名思義,它是透過鍵值的部分資訊,將要排序的元素分配至某些「桶」中,藉以達

到排序的做用。

   一、算法原理

   1)首先根據個位數的數值,在走訪數值時將它們分配至編號0到9的桶子中,接下來將這些桶

子中的數值從新串接起來;

   2)接着再進行一次分配,此次是根據十位數來分配,接下來將這些桶子中的數值從新串接起

來,成爲如下的數列;

       這時候整個數列已經排序完畢;若是排序的對象有三位數以上,則持續進行以上的動做直至

最高位數爲止。

      最高位優先(Most Significant Digit first)法,簡稱MSD法:先按k1排序分組,同一組中記錄,

關鍵碼k1相等,再對各組按k2排序分紅子組,以後,對後面的關鍵碼繼續這樣的排序分 組,直

到按最次位關鍵碼kd對各子組排序後。再將各組鏈接起來,便獲得一個有序序列。

     最低位優先(Least Significant Digit first)法,簡稱LSD法:先從kd開始排序,再對kd-1進行排

序,依次重複,直到對k1排序後便獲得一個有序序列。

   二、算法分析

    時間複雜度:

    設待排序列爲n個記錄,d個關鍵碼,關鍵碼的取值範圍爲radix,則進行鏈式基數排序的時間

複雜度爲O(d(n+radix)),其中,一趟分配時間複雜度爲O(n),一趟收集時間複雜度爲O(radix),

共進行d趟分配和收集。其時間複雜度爲O (nlog(r)m),其中r爲所採起的基數,而m爲堆數。

   穩定性:

   基數排序是按照低位先排序,而後收集;再按照高位排序,而後再收集;依次類推,直到最高

位。有時候有些屬性是有優先級順序的,先按低優先級排序,再按高優 先級排序,最後的次序

就是高優先級高的在前,高優先級相同的低優先級高的在前。基數排序基於分別排序,分別收

集,因此其是穩定的排序算法。  

    三、算法實現

/*
  基數排序。
*/

#include <iostream>
#include <stdio.h>

// 輔助函數,求數據的最大位數
int MaxBit( int *array, int n )
{
  int maxbit = 1;  // 保存最大的位數
  int div = 10;

  for( int i = 0; i < n; ++i )
  {
    while( array[ i ] >= div )
    {
      div *= 10;
      ++maxbit;
    }
  }
  
  return maxbit;
}

void RadixSort( int *array, int n )
{
  if( array == NULL || n < 0 )
  {
    printf( "invalid input.\n" );
    return;
  }

  int maxbit = MaxBit( array, n );
  int *temp = new int[ n ];
  int *count = new int[ 10 ];  // 計數器
  int radix = 1;
  int i, j, k;

  for( i = 1; i <= maxbit; i++ )  //進行maxbit次排序
  {
    for( j = 0; j < 10; j++ )
      count[ j ] = 0;     // 每次分配前清空計數器
    
    for( j = 0; j < n; j++ )
    {
      k = ( array[ j ] / radix ) % 10;   // 統計每一個桶中的記錄數
      count[ k ]++;
    }
 
    for( j = 1; j < 10; j++ )
      count[ j ] = count[ j - 1 ] + count[ j ];  // 將tmp中的位置依次分配給每一個桶
  
    for( j = n - 1; j >= 0; j-- )    // 將全部桶中記錄依次收集到tmp中
    {
      k = ( array[ j ] / radix ) % 10;
      temp[ count[ k ] - 1 ] = array[ j ];
      count[ k ]--;
    }

    for( j = 0; j < n; j++ )  // 將臨時數組的內容複製到data中
      array[ j ] = temp[ j ];

    radix = radix * 10;
  }

  delete [] temp;
  delete [] count;
}

void Test( const char* testName, int* array, int n )
{
  if( testName == NULL )
  {
    printf( "test invaild input.\n" );
    return;
  }

  printf( "%s begins: \n", testName );
  
  RadixSort( array, n );
  
  printf( "after radix sort, the result is: \n" );
  for( int i = 0; i < n; i++ )
    printf( "%d ", array[ i ] );
  printf( "\n" );
}

// 無重複數字
void Test1()
{
  int array[ ] = { 42, 24, 61, 71, 11, 57 };
  Test( "Test1", array, 6 ); 
}

// 有重複數字
void Test2()
{
  int array[ ] = { 421, 24, 6,421, 121, 54 };
  Test( "Test2", array, 6 ); 
}

// 輸入數字只有一個元素
void Test3()
{
  int array[ ] = { 100 };
  Test( "Test3", array, 1 ); 
}

// 輸入數組爲空
void Test4()
{
  int emptyArray[ ] = { };
  Test( "Test4", emptyArray, 0 ); 
}

// 輸入數組爲null,且長度異常
void Test5()
{
  Test( "Test5", NULL, -1 ); 
}

int main()
{
  Test1();
  Test2();
  Test3();
  Test4();
  Test5();

  return 0;
}

相關文章
相關標籤/搜索