1. 程式人生 > >C語言八大排序演算法(包括穩定性、時間複雜度等)(收藏)

C語言八大排序演算法(包括穩定性、時間複雜度等)(收藏)

C語言八大排序演算法

概述
排序有內部排序和外部排序,內部排序是資料記錄在記憶體中進行排序,而外部排序是因排序的資料很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存。我們這裡說說八大排序就是內部排序。
在這裡插入圖片描述
當n較大,則應採用時間複雜度為O(nlog2n)的排序方法:快速排序、堆排序或歸併排序序。 快速排序:是目前基於比較的內部排序中被認為是最好的方法,當待排序的關鍵字是隨機分佈時,快速排序的平均時間最短;

1、插入排序—直接插入排序(Straight Insertion Sort)

基本思想:
將一個記錄插入到已排序好的有序表中,從而得到一個新,記錄數增1的有序表。即:先將序列的第1個記錄看成是一個有序的子序列,然後從第2個記錄逐個進行插入,直至整個序列有序為止。

要點:
設立哨兵,作為臨時儲存和判斷陣列邊界之用。

直接插入排序示例:在這裡插入圖片描述
如果碰見一個和插入元素相等的,那麼插入元素把想插入的元素放在相等元素的後面。所以,相等元素的前後順序沒有改變,從原無序序列出去的順序就是排好序後的順序,所以插入排序是穩定的。

演算法的實現:

void print(int a[], int n ,int i)
{  
	cout<<i <<":";
  	for(int j= 0; j<8; j++)
  	{  
  		cout<<a[j] <<" ";
    }  
    cout<<
endl; } void InsertSort(int a[], int n) { for(int i= 1; i<n; i++) { if(a[i] < a[i-1]) { //若第i個元素大於i-1元素,直接插入。小於的話,移動有序表後插入 int j= i-1; int x = a[i]; //複製為哨兵,即儲存待排序元素 a[i] = a[i-1]; //先後移一個元素 while(
x < a[j]) { //查詢在有序表的插入位置 a[j+1] = a[j]; j--; //元素後移 } a[j+1] = x; //插入到正確位置 } print(a,n,i); //列印每趟排序的結果 } } int main() { int a[8] = {3,1,5,7,2,4,9,6}; InsertSort(a,8); print(a,8,8); }

效率:時間複雜度:O(n^2).

其他的插入排序有二分插入排序,2-路插入排序。

2. 插入排序—希爾排序(Shell`s Sort)
希爾排序是1959 年由D.L.Shell 提出來的,相對直接排序有較大的改進。希爾排序又叫縮小增量排序

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

操作方法:
選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;按增量序列個數k,對序列進行k 趟排序;每趟排序,根據對應的增量ti,將待排序列分割成若干長度為m 的子序列,分別對各子表進行直接插入排序。僅增量因子為1 時,整個序列作為一個表來處理,表長度即為整個序列的長度。希爾排序的示例:
在這裡插入圖片描述

演算法實現:
我們簡單處理增量序列:增量序列d = {n/2 ,n/4, n/8 …1} n為要排序數的個數即:先將要排序的一組記錄按某個增量d(n/2,n為要排序數的個數)分成若干組子序列,每組中記錄的下標相差d.對每組中全部元素進行直接插入排序,然後再用一個較小的增量(d/2)對它進行分組,在每組中再進行直接插入排序。繼續不斷縮小增量直至為1,最後使用直接插入排序完成排序。

void print(int a[], int n ,int i){
  cout<<i <<":";  
  for(int j= 0; j<8; j++){
    cout<<a[j] <<" ";  
    }  
    cout<<endl; 
} /**  * 直接插入排序的一般形式  *  * @param int dk 縮小增量,如果是直接插入排序,dk=1  *  */
void ShellInsertSort(int a[], int n, int dk) {
  for(int i= dk; i<n; ++i){
    if(a[i] < a[i-dk]){
     //若第i個元素大於i-1元素,直接插入。小於的話,移動有序表後插入  
     int j = i-dk;  int x = a[i]; //複製為哨兵,即儲存待排序元素  
     a[i] = a[i-dk]; //首先後移一個元素  
     while(x < a[j]){
      //查詢在有序表的插入位置  
      a[j+dk] = a[j];  j -= dk; //元素後移       
      }  
      a[j+dk] = x; //插入到正確位置  
    }  
      print(a, n,i );  
 }
} 
/**  * 先按增量d(n/2,n為要排序數的個數進行希爾排序  *  */
void shellSort(int a[], int n){
  int dk = n/2;  
  while( dk >= 1 ){
    ShellInsertSort(a, n, dk);  dk = dk/2;  
    }
} 
int main(){
  int a[8] = {3,1,5,7,2,4,9,6};  //ShellInsertSort(a,8,1);
   //直接插入排序  shellSort(a,8);
    //希爾插入排序  print(a,8,8
}

希爾排序時效分析很難,關鍵碼的比較次數與記錄移動次數依賴於增量因子序列d的選取,特定情況下可以準確估算出關鍵碼的比較次數和記錄的移動次數。目前還沒有人給出選取最好的增量因子序列的方法。

增量因子序列可以有各種取法,有取奇數的,也有取質數的,但需要注意:增量因子中除1 外沒有公因子,且最後一個增量因子必須為1。希爾排序方法是一個不穩定的排序方法。

3. 選擇排序—簡單選擇排序(Simple Selection Sort)

基本思想:
在要排序的一組數中,選出最小(或者最大)的一個數與第1個位置的數交換;然後在剩下的數當中再找最小(或者最大)的與第2個位置的數交換,依次類推,直到第n-1個元素(倒數第二個數)和第n個元素(最後一個數)比較為止。

簡單選擇排序的示例
在這裡插入圖片描述
操作方法
第一趟,從n 個記錄中找出關鍵碼最小的記錄與第一個記錄交換;第二趟,從第二個記錄開始的n-1 個記錄中再選出關鍵碼最小的記錄與第二個記錄交換;以此類推…第i 趟,則從第i 個記錄開始的n-i+1 個記錄中選出關鍵碼最小的記錄與第i 個記錄交換,直到整個序列按關鍵碼有序。

演算法實現:

void print(int a[], int n ,int i){
  cout<<"第"<<i+1 <<"趟 : ";
    for(int j= 0; j<8; j++){
      cout<<a[j] <<" ";  
      }  
  cout<<endl
}
/**  * 陣列的最小值  *
  * @return int 陣列的鍵值  */
  * int SelectMinKey(int a[], int n, int i) {
  *   int k = i;
  *   for(int j=i+1 ;j< n; ++j) {
  *   if(a[k] > a[j]) k = j;
  *   }  
  * return k; 
  * }   
  * /**  * 選擇排序  *  */
  * void selectSort(int a[], int n){
  *   int key, tmp;
  *   for(int i = 0; i< n; ++i) {
  *   key = SelectMinKey(a, n,i); //選擇最小的元素  
  * if(key != i){
  *   tmp = a[i]; a[i] = a[key]; a[key] = tmp; //最小元素與第i位置元素互換  
  *      }  
  * print(a, n , i);  
  *    } 
  * } 
  * int main(){
  *   int a[8] = {3,1,5,7,2,4,9,6};
  *   cout<<"初始值:";  
  * for(int j= 0; j<8; j++){
  *   cout<<a[j] <<" ";  
  *    }  
  * cout<<endl<<endl;  
  * selectSort(a, 8);  
  * print(a,8,8); 
  * }
  * 

簡單選擇排序的改進——二元選擇排序簡單選擇排序,每趟迴圈只能確定一個元素排序後的定位。我們可以考慮改進為每趟迴圈確定兩個元素(當前趟最大和最小記錄)的位置,從而減少排序所需的迴圈次數。改進後對n個數據進行排序,最多隻需進行[n/2]趟迴圈即可。

具體實現如下:

void SelectSort(int r[],int n) {
  int i ,j , min ,max, tmp;
    for (i=1 ;i <= n/2;i++) {
      // 做不超過n/2趟選擇排序  
      min = i; max = i ; //分別記錄最大和最小關鍵字記錄位置  
      for (j= i+1; j<= n-i; j++) { 
       if (r[j] > r[max]) {
         max = j ; continue ;  
         }  
         if (r[j]< r[min]) {
           min = j ;  
           }  
       }  
       //該交換操作還可分情況討論以提高效率  
       tmp = r[i-1];
       r[i-1] = r[min];
       r[min] = tmp;
       tmp = r[n-i];
       r[n-i] = r[max]; 
       r[max] = tmp;  
    } 
}

4. 選擇排序—堆排序(Heap Sort)
堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。

基本思想:
堆的定義如下:具有n個元素的序列(k1,k2,…,kn),當且僅當滿足在這裡插入圖片描述
時稱之為堆。由堆的定義可以看出,堆頂元素(即第一個元素)必為最小項(小頂堆)。
若以一維陣列儲存一個堆,則堆對應一棵完全二叉樹,且所有非葉結點的值均不大於(或不小於)其子女的值,根結點(堆頂元素)的值是最小(或最大)的。
如:(a)大頂堆序列:(96, 83,27,38,11,09)
(b) 小頂堆序列:(12,36,24,85,47,30,53,91)
在這裡插入圖片描述
初始時把要排序的n個數的序列看作是一棵順序儲存的二叉樹(一維陣列儲存二叉樹),調整它們的儲存序,使之成為一個堆,將堆頂元素輸出,得到n 個元素中最小(或最大)的元素,這時堆的根節點的數最小(或者最大)。然後對前面(n-1)個元素重新調整使之成為堆,輸出堆頂元素,得到n 個元素中次小(或次大)的元素。依此類推,直到只有兩個節點的堆,並對它們作交換,最後得到有n個節點的有序序列。稱這個過程為堆排序。因此,實現堆排序需解決兩個問題

  1. 如何將n 個待排序的數建成堆;
  2. 輸出堆頂元素後,怎樣調整剩餘n-1 個元素,使其成為一個新堆。
    首先討論第二個問題:輸出堆頂元素後,對剩餘n-1元素重新建成堆的調整過程。
    調整小頂堆的方法:1)設有m 個元素的堆,輸出堆頂元素後,剩下m-1 個元素。將堆底元素送入堆頂((最後一個元素與堆頂進行交換),堆被破壞,其原因僅是根結點不滿足堆的性質。
    2)將根結點與左、右子樹中較小元素的進行交換。
    3)若與左子樹交換:如果左子樹堆被破壞,即左子樹的根結點不滿足堆的性質,則重複方法 (2)
    .4)若與右子樹交換,如果右子樹堆被破壞,即右子樹的根結點不滿足堆的性質。則重複方法 (2)
    .5)繼續對不滿足堆性質的子樹進行上述交換操作,直到葉子結點,堆被建成。稱這個自根結點到葉子結點的調整過程為篩選。如圖:
    在這裡插入圖片描述
    再討論對n 個元素初始建堆的過程。

**建堆方法:**對初始序列建堆的過程,就是一個反覆進行篩選的過程。
1)n 個結點的完全二叉樹,則最後一個結點是第個結點的子樹。
2)篩選從第個結點為根的子樹開始,該子樹成為堆。
3)之後向前依次對各結點為根的子樹進行篩選,使之成為堆,直到根結點。

如圖建堆初始過程:無序序列:(49,38,65,97,76,13,27,49)
在這裡插入圖片描述
在這裡插入圖片描述

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

void print(int a[], int n){
  for(int j= 0; j<n; j++){
    cout<<a[j] <<" "; 
     }  
   cout<<endl;
} 
/**  * 已知H[s…m]除了H[s] 外均滿足堆的定義 
 * 調整H[s],使其成為大頂堆.即將對第s個結點為根的子樹篩選,  *
  * @param H是待調整的堆陣列  * @param s是待調整的陣列元素的位置  * @param length是陣列的長度  *  */
 void HeapAdjust(int H[],int s, int length) {
   int tmp = H[s];  
   int child = 2*s+1; //左孩子結點的位置。(i+1 為當前調整結點的右孩子結點的位置)  
   while (child < length) {
     if(child+1 <length && H[child]<H[child+1]) {
      // 如果右孩子大於左孩子(找到比當前待調整結點大的孩子結點)  
      ++child ;  
      }  
     if(H[s]<H[child]) {
      // 如果較大的子結點大於父結點 
       H[s] = H[child]; // 那麼把較大的子結點往上移動,替換它的父結點  
       s = child; // 重新設定s ,即待調整的下一個結點的位置  
       child = 2*s+1; 
        } 
     else {
      // 如果當前待調整結點大於它的左右孩子,則不需要調整,直接退出  
      break;  
      }  
      H[s] = tmp; // 當前待調整的結點放到比其大的孩子結點位置上  
    }  
      print(H,length); 
} /**  * 初始堆進行調整  * 將H[0..length-1]建成堆  * 調整完之後第一個元素是序列的最小的元素  */
void BuildingHeap(int H[], int length) {
  //最後一個有孩子的節點的位置 
  i= (length -1) / 2  
  for (int i = (length -1) / 2 ; i >= 0; --i)  
     HeapAdjust(H,i,length);
 } 
 /**  * 堆排序演算法  */
 void HeapSort(int H[],int length) {
   //初始堆  
   BuildingHeap(H, length);  
   //從最後一個元素開始對序列進行調整  
   for (int i = length - 1; i > 0; --i)  {
     //交換堆頂元素H[0]和堆中最後一個元素  
		int temp = H[i]; H[i] = H[0]; H[0] = temp;  
		//每次交換堆頂元素和堆中最後一個元素之後,都要對堆進行調整  
		HeapAdjust(H,0,i);  
		} 
} 
int main(){
  int H[10] = {3,1,5,7,2,4,9,6,10,8};
    cout<<"初始值:";
    print(H,10);
    HeapSort(H,10);
      //selectSort(a, 8);
    cout<<"結果:";
    print(H,10);
}

設樹深度為k, 在這裡插入圖片描述。從根到葉的篩選,元素比較次數至多2(k-1)次,交換記錄至多k 次。所以,在建好堆後,排序過程中的篩選次數不超過下式:
在這裡插入圖片描述
而建堆時的比較次數不超過4n 次,因此堆排序最壞情況下,時間複雜度也為:O(nlogn )

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

氣泡排序的示例:

在這裡插入圖片描述
演算法的實現:

void bubbleSort(int a[], int n){ 
 for(int i =0 ; i< n-1; ++i) { 
 for(int j = 0; j < n-i-1; ++j) { 
 if(a[j] > a[j+1]) 
 { 
 int tmp = a[j] ; a[j] = a[j+1] ; a[j+1] = tmp; 
 } 
 } 
 } 
}

氣泡排序演算法的改進對氣泡排序常見的改進方法是加入一標誌性變數exchange,用於標誌某一趟排序過程中是否有資料交換,如果進行某一趟排序時並沒有進行資料交換,則說明資料已經按要求排列好,可立即結束排序,避免不必要的比較過程。
本文再提供以下兩種改進演算法:
1.設定一標誌性變數pos,用於記錄每趟排序中最後一次進行交換的位置。由於pos位置之後的記錄均已交換到位,故在進行下一趟排序時只要掃描到pos位置即可。

改進後演算法如下:

void Bubble_1 ( int r[], int n) {
  int i= n -1; //初始時,最後位置保持不變  
  while ( i> 0) {
    int pos= 0; //每趟開始時,無記錄交換  
    for (int j= 0; j< i; j++)  
    if (r[j]> r[j+1]) {  
       pos= j; //記錄交換的位置  
       int tmp = r[j]; r[j]=r[j+1];r[j+1]=tmp;  
    }  i= pos; //為下一趟排序作準備  
  } 
}

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

改進後的演算法實現為:

void Bubble_2 ( int r[], int n){
  int low = 0;
  int high= n -1;
   //設定變數的初始值  int tmp,j;
   while (low < high) {
     for (j= low; j< high; ++j)
      //正向冒泡,找到最大者
        if (r[j]> r[j+1]) {
          tmp = r[j]; r[j]=r[j+1];r[j+1]=tmp;  
          }  
        --high;  //修改high值, 前移一位  
      for ( j=high; j>low; --j)
         //反向冒泡,找到最小者  
        if (r[j
            
           

相關推薦

C語言八大排序演算法(包括穩定性時間複雜收藏)

C語言八大排序演算法 概述 排序有內部排序和外部排序,內部排序是資料記錄在記憶體中進行排序,而外部排序是因排序的資料很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存。我們這裡說說八大排序就是內部排序。 當n較大,則應採用時間複雜度為O(nlog2n)的排序方法:快速排序、堆排

有1,2...一直到n的無序陣列,求排序演算法,並且要求時間複雜為On,時間複雜為O1

提示:用陣列值作為下標 分析:        對於一般陣列的排序顯然 O(n) 是無法完成的。       既然題目這樣要求,肯定原先的陣列有一定的規律,讓人們去尋找一種機會。 例如:原始陣列:           a = [ 10, 6,9, 5,2

常用排序演算法穩定性時間複雜分析

1、  選擇排序、快速排序、希爾排序、堆排序不是穩定的排序演算法,        氣泡排序、插入排序、歸併排序和基數排序是穩定的排序演算法。 2、研究排序演算法的穩定性有何意義?   首先,排序演算法的穩定性大家應該都知道,通俗地講就是能保證排序前兩個相等的資

常見排序演算法及對應的時間複雜和空間複雜

轉載請註明出處: 排序演算法經過了很長時間的演變,產生了很多種不同的方法。對於初學者來說,對它們進行整理便於理解記憶顯得很重要。每種演算法都有它特定的使用場合,很難通用。因此,我們很有必要對所有常見的排序演算法進行歸納。 排序大的分類可以分為兩種:內

(C語言)八大排序之:堆排序快速排序

堆排序( heap sort )  reference:http://mp.weixin.qq.com/s/mY_bVJPWhzZWL5ZdooA6tw 1 /* FILE: heapSort.c 2 * DATE: 20180116 3 * ------

[原始碼和文件分享]基於C++的八大排序演算法的實現與比較

1 概述 排序有內部排序和外部排序,內部排序是資料記錄在記憶體中進行排序,而外部排序是因排序的資料很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存。我們這裡說說八大排序就是內部排序。 當 n 較大,則應採用時間複雜度為 O(nlog2n) 的排序方法:快速排序、堆排序或歸併排

C語言實現排序演算法---希爾排序

今天又重新研究了一遍諸多排序演算法,現在簡單分享一下里面的希爾排序(Shell Sort)的心得。 希爾排序(Shell Sort)是插入排序的一種。也稱縮小增量排序,是直接插入排序演算法的一種

氣泡排序演算法時間複雜穩定性

氣泡排序 氣泡排序一般是我們學習排序演算法時第一個接觸的演算法,下面來介紹一下氣泡排序。 演算法原理 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。 對每一對相鄰元素做同樣的工作,從開始第一對到結尾的最後一對。在這一步,最後的元素應該會是最大

深入學習排序演算法穩定性比較次數交換次數探討

        在學習排序演算法時,出於效率考慮,經常容易看到演算法的穩定性、比較次數及交換次數研究。特別是考試或者公司筆試題,經常出現這樣的題目。由於排序演算法有很多種,平時提出大家才能說出個大概,

插入排序演算法時間複雜穩定性

插入排序 演算法原理 將資料分為有序部分和無序部分。 在無序部分選擇一個元素,按照順序插入到有序部分,使之有序。 直到無序部分都插入到有序部分結束。 演算法分析 排序的思想就是維護一個有序的部分,將無序部分的資料按照順序插入到有序部分。 通

C#之八大排序演算法

1、直接插入排序(direct Insert Sort),基本思想是:順序地將待排序的記錄按其關鍵碼的大小插入到已排序的記錄子序列的適當位置。子序列的記錄個數從1 開始逐漸增大,當子序列的記錄個數與順序表中的記錄個數相同時排序完畢。  public void InsertS

演算法 c語言 折半排序演算法

#include<stdio.h> #define N 8 void binsearch(int a[]); void show(int a[]); int main() {              int a[N] = {50,36,66,76,95,12,

演算法穩定排序和非穩定排序排序和外排序時間複雜和空間複雜

轉自:點選開啟連結 1、穩定排序和非穩定排序 簡單地說就是所有相等的數經過某種排序方法後,仍能保持它們在排序之前的相對次序,我們就說這種排序方法是穩定的。反之,就是非穩定的。 比如:一組數排序前是a1,a2,a3,a4,a5,其中a2=a4,經過某種排序後為a1,a2,a4

排序演算法 穩定性時間複雜分析

1、  選擇排序、快速排序、希爾排序、堆排序不是穩定的排序演算法,        氣泡排序、插入排序、歸併排序和基數排序是穩定的排序演算法。 2、研究排序演算法的穩定性有何意義?   首先,排序演算法的穩定性大家應該都知道,通俗地講就是能保證排序前兩個相等的資

已知長度為n的線性表A採用順序儲存結構,請寫一個時間複雜為On空間複雜為O1演算法,該演算法可刪除線性表中所有值為item的資料元素。

語言:C++ #include <iostream> using namespace std; typedef int ElemType; //定義 #define MAXSIZE 100 typedef struct {ElemType *elem; int length;}Sq

演算法初級01——認識時間複雜對數器 master公式計算時間複雜小和問題和逆序對問題

雖然以前學過,再次回顧還是有別樣的收穫~   認識時間複雜度 常數時間的操作:一個操作如果和資料量沒有關係,每次都是固定時間內完成的操作,叫做常數操作。 時間複雜度為一個演算法流程中,常數運算元量的指標。常用O(讀作big O)來表示。具體來說,在常數運算元量的表示式中,

時間複雜為On排序演算法

我們常用的幾種排序演算法,氣泡排序,選擇排序,它們已經是相對比較簡單,穩定的排序演算法了,但是它們時間複雜度為O(n*n),基本都要用到兩層迴圈,今天我就像大家介紹一種簡單,只用一層for迴圈,時間複雜度為O(n)的排序演算法。 樣例輸入:1 4 5 6 3 4 2 8 9 1 樣例輸出

一個時間複雜為On排序演算法,空間複雜為O1

package test; import java.util.HashSet; import java.util.Set; public class Test { public st

各種排序穩定性時間複雜

轉載:http://blog.chinaunix.net/uid-21457204-id-3060260.html 轉載:http://blog.csdn.net/johnny710vip/article/details/6895654 這幾天筆試了好幾次了,連續碰到一個

實現一個棧,要求實現Push出棧Pop入棧Min返回最小值時間複雜為O1

這道題考查棧的知識點,要求實現一個棧,在對這個棧進行入棧和出棧以及返回最小棧元素時要求時間複雜度為O(1)。 方法一: 用兩個棧,一個正常出入棧,另一個存最小棧,入棧的時候第一個站正常入,最小棧如果為空或者要入的data比最小棧的棧頂元素小的時候才給最小棧入棧。