【算法】常見排序算法(插入排序、選擇排序、交換排序和歸并排序)

文章目錄

  • 前言
  • 一、排序概念及常見排序算法框圖
    • 1.排序概念
    • 2.常見排序算法框圖
  • 二、實現比較排序算法
    • 1.插入排序
      • 1.1 直接插入排序
      • 1.2 希爾排序
    • 2.選擇排序
      • 2.1 直接選擇排序
      • 2.2 堆排序
    • 3.交換排序
      • 3.1 冒泡排序
      • 3.2 快速排序
        • 3.2.1 hoare版本
        • 3.2.2 挖坑法
        • 3.2.3 lomuto前后指針
      • 3.3 快速排序的復雜度討論及其優化
        • 3.3.1 快速排序的復雜度
        • 3.3.2 隨機選取基準值 和 三數取中選取基準值
        • 3.3.3 三路劃分
    • 4.歸并排序
      • 4.1 歸并排序的思想及實現
  • 三、比較排序算法總結
    • 1.復雜度及穩定性分析
    • 2.測試代碼:比較排序性能對比
  • 四、實現非比較排序算法
    • 1.計數排序


前言

1.插入排序(直接插入排序 和 希爾排序)、選擇排序(直接選擇排序 和 堆排序)、交換排序(冒泡排序 和 快速排序)和歸并排序
2.重點內容:快速排序(三個版本的算法思路及代碼實現 以及 快速排序的復雜度討論及其優化方法:隨機選取基準值 、三數取中選取基準值 和 三路劃分)


一、排序概念及常見排序算法框圖

1.排序概念

所謂排序,就是使?串記錄,按照其中的某個或某些關鍵數據的大小,遞增或遞減的排列起來的操作。

2.常見排序算法框圖

在這里插入圖片描述
非比較排序中暫只介紹計數排序

二、實現比較排序算法

1.插入排序

1.1 直接插入排序

直接插入排序是?種簡單的插入排序法,其基本思想是:把待排序的記錄按其關鍵碼值的大小逐個插入到?個已經排好序的有序序列中,直到所有的記錄插入完為止,得到?個新的有序序列 。
在這里插入圖片描述
在這里插入圖片描述

void direct_insert_sort(int* arr, int n)
{for (int i = 0; i < n - 1; i++){int end = i;int tmp = arr[end + 1];while (end >= 0){if (tmp < arr[end]){arr[end + 1] = arr[end];end--;}elsebreak;}arr[end + 1] = tmp;}
}

在這里插入圖片描述
直接插入排序的特性總結:

  1. 元素集合越接近有序,直接插入排序算法的時間效率越高
  2. 時間復雜度(按最壞情況):O(N^2)
  3. 空間復雜度:O(1)

1.2 希爾排序

希爾排序法又稱縮小增量法。希爾排序法的基本思想是:先選定一個整數gap(通常 gap = n/2 或 n/3+1,n為數組元素個數),把待排序數據分成gap組,所有的距離相等的數據分在同?組內,并對每?組內的數據進行插入排序,然后 gap = n/2(或 n/3+1)得到下一個整數,再將數組分成gap組,對每?組內的數據排序,當gap=1時,就相當于直接插入排序。
直接插入排序算法的缺陷是它的時間復雜度在最優情況和最差情況下相差極大。希爾排序在直接插入排序算法的基礎上進行改進,通過幾輪分組排序(輪次越后面的排序,數組會越有序,效率會更高)使數組在最后一輪直接插入排序時接近有序,最后一輪直接插入排序效率會接近O(N)。
綜合來說,希爾排序的效率肯定是要高于直接插入排序算法的。

在這里插入圖片描述
在這里插入圖片描述

void shell_sort(int* arr, int n)
{int gap = n;while(gap>1){ gap = gap / 2;for (int i = 0; i < n - gap; i++){int end = i;int tmp = arr[end + gap];while (end >= 0){if (tmp < arr[end]){arr[end + gap] = arr[end];end -= gap;}elsebreak;}arr[end + gap] = tmp;}}
}

希爾排序的時間復雜度取決于增量序列的選擇(gap劃分方法的選擇),不同增量序列會導致不同的性能表現:

(1)原始希爾序列 n / 2 , n / 4 , … , 1 n/2, n/4, \dots, 1 n/2,n/4,,1

  • 最壞情況 O ( n 2 ) O(n^2) O(n2)(與直接插入排序相同)
  • 平均情況:實際應用中約為 O ( n 1.5 ) O(n^{1.5}) O(n1.5),但缺乏嚴格證明。

(2)Hibbard 序列 1 , 3 , 7 , 15 , … , 2 k ? 1 1, 3, 7, 15, \dots, 2^k-1 1,3,7,15,,2k?1

  • 最壞情況 O ( n 3 / 2 ) O(n^{3/2}) O(n3/2)
  • 平均情況:接近 O ( n 5 / 4 ) ) O(n^{5/4})) O(n5/4))

(3)Sedgewick 序列 1 , 5 , 19 , 41 , … 1, 5, 19, 41, \dots 1,5,19,41,

  • 最壞情況 O ( n 4 / 3 ) O(n^{4/3}) O(n4/3)
  • 平均情況:接近 O ( n log ? 2 n ) O(n \log^2 n) O(nlog2n),是已知最優增量序列之一。
增量序列最壞時間復雜度平均時間復雜度空間復雜度
原始序列 O ( n 2 ) O(n^2) O(n2) O ( n 1.5 ) O(n^{1.5}) O(n1.5)(經驗) O ( 1 ) O(1) O(1)
Hibbard 序列 O ( n 3 / 2 ) O(n^{3/2}) O(n3/2) O ( n 5 / 4 ) O(n^{5/4}) O(n5/4) O ( 1 ) O(1) O(1)
Sedgewick 序列 O ( n 4 / 3 ) O(n^{4/3}) O(n4/3) O ( n log ? 2 n ) O(n \log^2 n) O(nlog2n) O ( 1 ) O(1) O(1)

實際應用中,希爾排序通常比 O ( n 2 ) O(n^2) O(n2) 的算法(如冒泡排序)快得多,但不如 O ( n log ? n ) O(n \log n) O(nlogn) 的算法(如快速排序、歸并排序)。

2.選擇排序

2.1 直接選擇排序

每?次從待排序的數據元素中選出最小(或最大)的?個元素,存放在序列的起始位置,直到全部待排序的數據元素排完 。
在這里插入圖片描述
優化版本:每?次從待排序的數據元素中選出一組最小值和最大值,分別存放在序列的起始位置和末尾位置,一次可以確定兩個元素的位置,直到全部待排序的數據元素排完 。
在這里插入圖片描述
在這里插入圖片描述

void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void direct_select_sort(int* arr, int n)
{int left = 0;int right = n - 1;while (left < right){int min = left;int max = left;int cur = left + 1;while (cur <= right){if (arr[cur] > arr[max])max = cur;if (arr[cur] < arr[min])min = cur;cur++;}swap(&arr[left], &arr[min]);if (left == max)max = min;swap(&arr[right], &arr[max]);left++;right--;}
}

時間復雜度計算:
第一次遍歷n個元素的數組,確認一組最大最小值;
第二次遍歷n-2個元素的數組,確認一組最大最小值;
……
最后一次遍歷2(或3)個元素的數組。
所以計算式子為:2+4+……+(n-2)+ n = n ^ 2 + n
最終得到時間復雜度為:O(N^2)

直接選擇排序的特性總結:

  1. 直接選擇排序思考非常好理解,但是效率不是很好。實際中很少使用
  2. 時間復雜度: O(N^2)
  3. 空間復雜度: O(1)

2.2 堆排序

堆是一棵完全二叉樹,使用順序結構的數組來存儲數據。堆中某個結點的值總是不大于(或不小于)其父結點的值,將根結點最大的堆叫做大堆或大根堆,根結點最小的堆叫做小堆或小根堆。
在這里插入圖片描述
在這里插入圖片描述

核心思想總結
利用堆的性質高效地找到最大值(或最小值)并逐步完成排序。 其核心在于:

  1. 構建堆:將無序數組轉化為堆結構。
  2. 反復提取極值:每次提取堆頂元素后調整堆,最終得到有序序列。
#include <queue>
#include <vector>
using namespace std;void heap_sort(int* arr, int n)
{priority_queue<int,vector<int>,greater<int>> hp; // priority_queue是C++的STL庫中自帶的堆結構,直接調用priority_queue生成一個小堆。// C語言要使用堆排序還需要自己先實現一個堆結構for (int i = 0; i < n; i++){hp.push(arr[i]); // 先把數組中所有元素插入小堆中(將無序數組轉化為堆結構)} int i = 0;while (!hp.empty()){arr[i++] = hp.top(); // 每次提取堆頂元素后調整堆,最終得到有序序列hp.pop();} 
}

堆排序的特性總結:

  1. 時間復雜度: O ( n log ? n ) O(n \log n) O(nlogn)
  2. 空間復雜度: O ( 1 ) O(1) O(1)

3.交換排序

3.1 冒泡排序

冒泡排序的核心思想就是:兩兩相鄰的元素進行比較,滿足條件進行交換;一趟冒泡排序確定一個數的位置。
在這里插入圖片描述
在這里插入圖片描述

冒泡排序改良:
(1)設置一個變量,讓它可以判斷一趟冒泡排序中有無進行數組元素交換。
(2)當一趟冒泡排序中無任何一次數組元素交換,說明數組中元素已經有序,這時可提前結束循環。

void bubble_sort(int* arr, int n)
{for (int i = 0; i < n; i++){int exchange = 0; // 設置變量exchange,用來判斷一趟冒泡排序中有無進行數組元素交換for (int j = 0; j < n - i - 1; j++){if (arr[j] > arr[j + 1]){exchange = 1;swap(&arr[j], &arr[j + 1]);}} if(exchange == 0){break;}}
}

時間復雜度計算(按最壞情況):
每趟冒泡排序確定一個元素的位置,所以n個元素要進行n-1次冒泡排序。
第一趟冒泡排序要進行n-1次比較,確認一個元素的位置;
第二趟冒泡排序要進行n-2次比較(比上一趟少進行一次比較,因為確定了位置的元素無需再進行比較),再確認一個元素的位置;
……
依此類推,第n-1趟冒泡排序只需要進行1次比較
所以計算式子為:1+2+……+(n-2)+(n-1)=(n ^ 2)/ 2 - n/2
最終得到時間復雜度為:O(N^2)

冒泡排序的特性總結:

  1. 時間復雜度: O(N^2)
  2. 空間復雜度: O(1)

3.2 快速排序

快速排序是Hoare于1962年提出的?種二叉樹結構的交換排序方法,其基本思想為:任取待排序元素序列中的某元素作為基準值,按照該排序碼將待排序集合分割成兩子序列,左子序列中所有元素均小于基準值,右子序列中所有元素均大于基準值,然后最左右子序列重復該過程,直到所有元素都排列在相應位置上為止。

3.2.1 hoare版本

算法思路 :

  1. 確定基準值(此處直接選擇最左元素為基準值),用key標記其下標;創建left和right標記,left起始指向key+1位置,right起始指向末尾位置
  2. 從右向左找出比基準值小的數據,用下標right標記;從左向右找出比基準值大的數據,用下標left標記;然后right和left指向的數據互換,進入下次循環;期間一旦left>right,循環直接終止
  3. 結束循環后,right所在的位置就是基準值的正確位置,把基準值交換到right位置
  4. 以基準值位置劃分數組,左邊的數組<=基準值,右邊的數組>=基準值
  5. 接著對左邊和右邊數組進行以上同樣的操作,不斷劃分數組,直到無法再進行劃分
    在這里插入圖片描述
void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void hoare_quicksort(int* arr, int begin, int end)
{int key = begin;int left = begin + 1;int right = end;while (left <= right){while (left <= right && arr[right] >= arr[key])right--;while (left <= right && arr[left] <= arr[key])left++;if (left <= right){swap(&arr[left], &arr[right]);right--;left++;}}swap(&arr[key], &arr[right]);key = right; // [begin,key-1] key [key+1,end]if (begin < key - 1)hoare_quicksort(arr, begin, key - 1);if (key + 1 < end)hoare_quicksort(arr, key + 1, end);
}
3.2.2 挖坑法

算法思路 :

  1. 確定基準值(此處直接選擇最左元素為基準值),創建變量key保存基準值;創建left和right標記,left起始指向開頭位置(起始以left指向位置為"坑"位),right起始指向末尾位置
  2. 首先移動right標記從右向左找出比基準值小的數據,找到后立即放入左邊"坑"位中,當前位置變為新的"坑"位,然后移動left從左向右找出比基準值大的數據,找到后立即放入右邊"坑"位中,當前位置變為新的"坑",循環進行此過程,過程中一旦left和right相遇,循環立即終止。
  3. 結束循環后,當前的"坑"位就是基準值的正確位置,將最開始用變量key存儲的基準值放入當前的"坑"位中
  4. 以基準值位置劃分數組,左邊的數組<=基準值,右邊的數組>=基準值
  5. 接著對左邊和右邊數組進行以上同樣的操作,不斷劃分數組,直到無法再進行劃分
    在這里插入圖片描述
    在這里插入圖片描述
void digholes_quicksort(int* arr, int begin, int end)
{int key = arr[begin];int left = begin;int right = end;while (left < right){while (left < right && arr[right] >= key)right--;if (left == right)break;elsearr[left] = arr[right];while (left < right && arr[left] <= key)left++;if (left == right)break;elsearr[right] = arr[left];}arr[right] = key; // [begin,right-1] right [right+1,end]if (begin < right - 1)digholes_quicksort(arr, begin, right - 1);if (right + 1 < end)digholes_quicksort(arr, right + 1, end);
}
3.2.3 lomuto前后指針

算法思路 :

  1. 確定基準值(此處直接選擇最左元素為基準值),用key標記其下標;創建prev和cur標記,prev起始指向開頭位置,cur起始指向prev+1位置
  2. 首先移動cur標記從左向右找出比基準值小的數據,找到之后,prev先后移一位,然后將cur和prev指向的數據交換,緊接著cur后移一位,繼續循環進行此過程,直到cur越界,循環才終止。
  3. 結束循環后,prev所在的位置就是基準值的正確位置,把基準值交換到prev位置
  4. 以基準值位置劃分數組,左邊的數組<=基準值,右邊的數組>=基準值
  5. 接著對左邊和右邊數組進行以上同樣的操作,不斷劃分數組,直到無法再進行劃分
    在這里插入圖片描述
    在這里插入圖片描述
void lomuto_quicksort(int* arr, int begin, int end)
{int key = begin;int prev = begin;int cur = prev + 1;while (cur <= end){while (cur <= end && arr[cur] >= arr[key])cur++;if (cur <= end){prev++;swap(&arr[cur], &arr[prev]);cur++;}}swap(&arr[key], &arr[prev]);key = prev; // [begin,key-1] key [key+1,end]if (begin < key - 1)lomuto_quicksort(arr, begin, key - 1);if (key + 1 < end)lomuto_quicksort(arr, key + 1, end);
}

3.3 快速排序的復雜度討論及其優化

3.3.1 快速排序的復雜度

在這里插入圖片描述
在這里插入圖片描述在這里插入圖片描述

時間復雜度分析:

  1. 最好情況

    • 每次劃分都能將數組均勻分成兩部分(每次選的基準值都接近中位數)。
    • 遞歸深度為 log ? n \log n logn,每層需遍歷 n n n 個元素(實際就第一層需遍歷n個元素,每層遞歸都會確定一些元素的位置,遞歸層數越深,需遍歷的元素越少,但統一按每層遍歷n個元素計算比較方便)。
    • 時間復雜度 O ( n log ? n ) O(n \log n) O(nlogn)
  2. 平均情況

    • 假設基準值通過隨機選擇或三數取中法選出,劃分的均衡性服從概率分布。
    • 數學期望計算后仍為 O ( n log ? n ) O(n \log n) O(nlogn)
    • 時間復雜度 O ( n log ? n ) O(n \log n) O(nlogn)
  3. 最壞情況

    • 每次劃分極不均衡(如數組已有序且固定選第一個元素為基準值)。
    • 遞歸深度為 n n n,總操作次數為 n + ( n ? 1 ) + ? + 1 = n ( n + 1 ) 2 n + (n-1) + \dots + 1 = \frac{n(n+1)}{2} n+(n?1)+?+1=2n(n+1)?
    • 時間復雜度 O ( n 2 ) O(n^2) O(n2)

空間復雜度分析:

  1. 最好情況

    • 遞歸深度為 log ? n \log n logn,棧空間占用與遞歸深度成正比。
    • 空間復雜度 O ( log ? n ) O(\log n) O(logn)
  2. 平均情況

    • 遞歸深度仍為 log ? n \log n logn(通過隨機化選擇基準值減少最壞概率)。
    • 空間復雜度 O ( log ? n ) O(\log n) O(logn)
  3. 最壞情況

    • 遞歸深度為 n n n(如每次僅處理一個元素)。
    • 空間復雜度 O ( n ) O(n) O(n)
  • 優化策略隨機選擇或三數取中法選擇基準值,可有效降低與最壞情況1類似場景(數組有序或部分有序)的時間和空間復雜度 ;三路劃分法 可有效降低與最壞情況2類似場景(數組存在大量重復數據)的時間和空間復雜度。
  • 空間占用雖為原地排序,但需注意遞歸棧的隱式空間消耗。
3.3.2 隨機選取基準值 和 三數取中選取基準值

決定快排性能的關鍵點是每次單趟排序后,基準值對數組的分割,如果每次選的基準值都接近數組中的中位數,那么快排的遞歸樹就是顆均勻的滿二叉樹,性能最佳。但是實踐中雖然不可能每次都是二分居中,但是性能也還是可控的。但是如果出現每次選到最小值/最大值,劃分為0個和N-1的子問題時,時間復雜度為O(N^2),數組有序并且每次固定選第一個元素為基準值前不進行任何干預,就會出現這樣的問題。
我們仍然可以采用第一個元素作為基準值的方法,不過在那之前進行隨機選取 或 三數取中法選出基準值,再把選出的基準值換到第一個元素,這樣可以有效減少因數組有序或部分有序而降低快排效率的情況。

  1. 隨機選取基準值
    每次排序時,從當前數組中隨機選擇一個元素作為基準值,再通過交換操作將其移動到數組的起始位置,最后進行常規的劃分操作。
#include <time.h>
#include <stdlib.h>
void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void random_select(int* arr, int begin, int end)
{srand((unsigned)time(NULL));// 隨機選擇基準值的位置,選擇區間在begin和end之間int key = rand() % (end - begin + 1) + begin;//把基準值位置的元素與begin位置元素互換,后續依然可以選第一個元素為基準值劃分函數  swap(&arr[key], &arr[begin]);
}
  1. 三數取中選取基準值
    從當前子數組的首、中、尾三個位置取元素,選擇三者的中位數作為基準值,通過交換操作將其移動到數組的起始位置,最后進行常規的劃分操作。
void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void median_select(int* arr, int begin, int end)
{int mid = begin + ((end - begin) >> 1);//計算數組中間的元素的下標  // 使用三數取中法選擇三者的中位數作為基準值并將其換到begin位置 if (arr[mid] < arr[end])//目標: arr[mid] >= arr[end]  {swap(&arr[mid], &arr[end]);}if (arr[begin] > arr[mid])//目標: arr[begin] <= arr[mid]  {swap(&arr[begin], &arr[mid]);}if (arr[begin] < arr[end]) //目標: arr[begin] >= arr[end]  {swap(&arr[begin], &arr[end]);}//此時,arr[mid] >= arr[begin] >= arr[end]  //begin位置上保存這三個位置中間的值,后續依然可以選第一個元素為基準值劃分函數  
}
3.3.3 三路劃分

快速排序的三路劃分是一種優化策略,用于處理數組中存在大量重復元素的情況。 它將數組劃分為三個區域:

  • 小于基準值的部分(左段)
  • 等于基準值的部分(中段)
  • 大于基準值的部分(右段)

通過將重復元素集中到中間段,后續遞歸時只需處理左右兩段,避免對重復元素重復操作,從而提升效率。

算法思路(類似hoare的左右指針和lomuto的前后指針的結合) :

  1. 確定基準值(直接選擇最左元素為基準值),創建變量key保存基準值;創建 left、right 和 cur 標記,left起始指向開頭位置,right起始指向末尾位置,cur起始指向left+1位置
  2. cur標記從左向右移動會遇到三種不同的情形,每種情形都會有對應的處理方式:
  • 情形一:cur遇到比基準值小的值后跟left位置交換,換到左邊,left++,cur++;
  • 情形二:cur遇到比基準值大的值后跟right位置交換,換到右邊,right–;
  • 情形三:cur遇到跟基準值相等的值后,cur++。
  1. 直到cur > right才結束以上過程
  2. 以 left 和 right位置劃分數組,左邊的數組<基準值,右邊的數組>基準值
  3. 接著對左邊和右邊數組進行以上同樣的操作,不斷劃分數組,直到無法再進行劃分
    在這里插入圖片描述

4.歸并排序

4.1 歸并排序的思想及實現

歸并排序算法思想:
歸并排序(MERGE-SORT)是建立在歸并操作上的?種有效的排序算法,該算法是采用分治法的?個非常典型的應用。將已有序的子序列合并,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合并成?個有序表,稱為二路歸并。

在這里插入圖片描述

void merge_sort(int* arr, int begin, int end, int* tmp)
{int mid = begin + (end - begin) / 2;if (begin < mid)merge_sort(arr, begin, mid, tmp);if (mid + 1 < end)merge_sort(arr, mid + 1, end, tmp);int left1 = begin;int right1 = mid;int left2 = mid + 1;int right2 = end;int n = begin;while (left1 <= right1 && left2 <= right2){if (arr[left1] <= arr[left2])tmp[n++] = arr[left1++];elsetmp[n++] = arr[left2++];}while (left1 <= right1){tmp[n++] = arr[left1++];}while (left2 <= right2){tmp[n++] = arr[left2++];}while (begin <= end){arr[begin] = tmp[begin];begin++;}
}

三、比較排序算法總結

1.復雜度及穩定性分析

算法 最好 最壞 平均時間復雜度 空間 穩定 冒泡排序 O ( n ) O ( n 2 ) O ( n 2 ) O ( 1 ) ? 直接插入排序 O ( n ) O ( n 2 ) O ( n 2 ) O ( 1 ) ? 歸并排序 O ( n log ? n ) O ( n log ? n ) O ( n log ? n ) O ( n ) ? 直接選擇排序 O ( n 2 ) O ( n 2 ) O ( n 2 ) O ( 1 ) ? 堆排序 O ( n log ? n ) O ( n log ? n ) O ( n log ? n ) O ( 1 ) ? 希爾排序 O ( n log ? n ) O ( n 2 ) O ( n 1.3 ) O ( 1 ) ? 快速排序 O ( n log ? n ) O ( n 2 ) O ( n log ? n ) O ( log ? n ) ? \begin{array}{|l|c|c|c|c|c|} \hline \text{算法} & \text{最好} & \text{最壞} & \text{平均時間復雜度} & \text{空間} & \text{穩定} \\ \hline 冒泡排序 & O(n) & O(n^2) & O(n^2) & O(1) & ? \\ 直接插入排序 & O(n) & O(n^2) & O(n^2) & O(1) & ? \\ 歸并排序 & O(n \log n) & O(n \log n) & O(n \log n) & O(n) & ? \\ 直接選擇排序 & O(n^2) & O(n^2) & O(n^2) & O(1) & ? \\ 堆排序 & O(n \log n) & O(n \log n) & O(n \log n) & O(1) & ? \\ 希爾排序 & O(n \log n) & O(n^2) & O(n^{1.3}) & O(1) & ? \\ 快速排序 & O(n \log n) & O(n^2) & O(n \log n) & O(\log n) & ? \\ \hline \end{array} 算法冒泡排序直接插入排序歸并排序直接選擇排序堆排序希爾排序快速排序?最好O(n)O(n)O(nlogn)O(n2)O(nlogn)O(nlogn)O(nlogn)?最壞O(n2)O(n2)O(nlogn)O(n2)O(nlogn)O(n2)O(n2)?平均時間復雜度O(n2)O(n2)O(nlogn)O(n2)O(nlogn)O(n1.3)O(nlogn)?空間O(1)O(1)O(n)O(1)O(1)O(1)O(logn)?穩定?????????

2.測試代碼:比較排序性能對比

用以上各種比較排序算法排序同一組隨機生成的數據,測試時間(單位:毫秒)消耗,要在Release版本運行這段代碼(含遞歸的算法會在Debug版本生成更多調試數據,影響算法效率):

#include "sort.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>void TestOP()
{srand(time(0));const int N = 50000;int* a1 = (int*)malloc(sizeof(int) * N);int* a2 = (int*)malloc(sizeof(int) * N);int* a3 = (int*)malloc(sizeof(int) * N);int* a4 = (int*)malloc(sizeof(int) * N);int* a5 = (int*)malloc(sizeof(int) * N);int* a6 = (int*)malloc(sizeof(int) * N);int* a7 = (int*)malloc(sizeof(int) * N);int* tmp = (int*)malloc(sizeof(int) * N);for (int i = 0; i < N; ++i){a1[i] = rand();a2[i] = a1[i];a3[i] = a1[i];a4[i] = a1[i];a5[i] = a1[i];a6[i] = a1[i];a7[i] = a1[i];} int begin1 = clock();direct_insert_sort(a1, N);int end1 = clock();int begin2 = clock();shell_sort(a2, N);int end2 = clock();int begin3 = clock();direct_select_sort(a3, N);int end3 = clock();int begin4 = clock();heap_sort(a4, N);int end4 = clock();int begin5 = clock();hoare_quicksort(a5, 0, N - 1);int end5 = clock();int begin6 = clock();merge_sort(a6, 0, N - 1, tmp);int end6 = clock();int begin7 = clock();bubble_sort(a7, N);int end7 = clock();printf("direct_insert_sort:%d\n", end1 - begin1);printf("shell_sort:%d\n", end2 - begin2);printf("direct_select_sort:%d\n", end3 - begin3);printf("heap_sort:%d\n", end4 - begin4);printf("hoare_quicksort:%d\n", end5 - begin5);printf("merge_sort:%d\n", end6 - begin6);printf("bubble_sort:%d\n", end7 - begin7);free(a1);free(a2);free(a3);free(a4);free(a5);free(a6);free(a7);free(tmp);
}int main()
{TestOP();return 0;
}

在這里插入圖片描述

四、實現非比較排序算法

1.計數排序

計數排序是對哈希直接定址法的變形應用。 操作步驟:
1)統計相同元素出現次數
2)根據統計的結果將序列回收到原來的序列中

在這里插入圖片描述
"找出最大值k,創建長度為k+1的計數數組"的方式有些時候會造成很大空間浪費,可以找出待排序數組中的最大最小值,按范圍開空間:
在這里插入圖片描述

void count_sort(int* arr, int n)
{int min = arr[0], max = arr[0];for (int i = 1; i < n; i++){if (arr[i] > max)max = arr[i];if (arr[i] < min)min = arr[i];} int range = max - min + 1;int* count = (int*)calloc(range, sizeof(int));if (count == NULL){perror("calloc fail");return;}// 統計次數for (int i = 0; i < n; i++){count[arr[i] - min]++; } // 排序int j = 0;for (int i = 0; i < range; i++){while (count[i]--){arr[j++] = i + min;}}free(count);
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/74297.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/74297.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/74297.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Go語言分布式鎖實戰:dlock助力構建高并發穩定系統

在構建分布式系統時&#xff0c;一個常見且棘手的問題便是資源競爭和數據一致性問題。分布式鎖作為一種常用的解決方案&#xff0c;在多個進程或節點之間協調訪問共享資源時顯得尤為重要。今天&#xff0c;我們將介紹一款分布式鎖庫——dlock&#xff0c;并通過詳細的使用示例帶…

算法方法快速回顧

&#xff08;待修改&#xff09; 目錄 1. 雙指針2. 滑動窗口理論基礎 3. 二分查找3. 二分查找理論基礎 4. KMP5. 回溯算法6. 貪心算法7. 動態規劃7.1. 01背包7.2. 完全背包7.3. 多重背包 8. 單調棧9. 并查集10. 圖論10.1. 廣度優先搜索&#xff08;BFS&#xff09;10.2. 深度優…

深度學習:讓機器學會“思考”的魔法

文章目錄 引言&#xff1a;從“鸚鵡學舌”到“舉一反三”一、深度學習是什么&#xff1f;1. 定義&#xff1a;機器的“大腦”2. 核心思想&#xff1a;從數據中“悟”出規律 二、深度學習的“大腦”結構&#xff1a;神經網絡1. 神經元&#xff1a;深度學習的基本單元2. 神經網絡…

電動自行車/電動工具鋰電池PCM方案--SH367003、SH367004、SH79F329

在消費電子系統中&#xff0c;如手機電池包&#xff0c;筆記本電腦電池包等&#xff0c;帶有控制IC、功率MOSFETFE管以及其他電子元件的電路系統稱為電池充放電保護板Protection Circuit Module &#xff08;PCM&#xff09;&#xff0c;而對于動力電池的電池管理系統&#xff…

補碼詳細分析

補碼引入 舉一個生活化的例子 假設由一個掛鐘&#xff0c;它只能順時鐘調時間&#xff0c;那么它調時間就分成了一下兩種情況 正好順時針調就能調好 如&#xff1a;時針從5調到9需要逆時針調才能調好 如&#xff1a;時針從10調到7 在上面的情況中1是不用處理的&#xff0c;2…

計算機網絡入門:物理層與數據鏈路層詳解

&#x1f310; &#xff08;專業解析 中學生也能懂&#xff01;&#xff09; &#x1f4d6; 前言 計算機網絡就像數字世界的“高速公路系統”&#xff0c;而物理層和數據鏈路層是這條公路的基石。本文用 專業視角 和 生活化比喻 &#xff0c;帶你輕松理解這兩層的核心原理&a…

哪些視頻格式在webview2中播放可以設置成透明的?

在WebView2中&#xff0c;能夠播放并設置成透明背景的視頻格式主要取決于其支持的編解碼器以及視頻是否包含alpha通道&#xff08;透明度信息&#xff09;。以下是支持透明背景的視頻格式&#xff1a; 支持透明背景的視頻格式 1. WebM&#xff08;使用VP9編解碼器&#xff09; …

【基于ROS的A*算法實現路徑規劃】A* | ROS | 路徑規劃 | Python

### 記錄一下使用Python實現ROS平臺A*算法路徑規劃 ### 代碼可自取 &#xff1a;Xz/little_projecthttps://gitee.com/Xz_zh/little_project.git 目錄 一、思路分析 二、算法實現 三、路徑規劃實現 一、思路分析 要求使用A*算法實現路徑規劃&#xff0c;可以將該任務分為三…

2025-03-23 吳恩達機器學習3——多維特征

文章目錄 1 多元引入2 矢量化2.1 示例2.2 非矢量化實現2.3 矢量化實現2.4 應用 3 特征縮放3.1 舉例3.2 必要性3.3 方法3.3.1 最大最小值縮放&#xff08;Min-Max Scaling&#xff09;3.3.2 均值歸一化&#xff08;Mean Normalization&#xff09;3.3.3 Z 分數歸一化&#xff08…

正點原子內存管理學習和修改

由于項目需要用到內存管理進行動態申請和釋放&#xff0c;今天又重新學習了一下正點原子的內存管理實驗&#xff0c;溫習了一下內存管理的實質。首先先上正點原子內存管理的源代碼&#xff1a; malloc.c文件&#xff1a; #include "./MALLOC/malloc.h"#if !(__ARMC…

時空觀測者:俯身拾貝

目錄 中華文明時空貝殼集&#xff08;按時間排序&#xff09;1. 良渚玉琮&#xff08;約公元前3300-2300年&#xff09;2. 三星堆青銅神樹&#xff08;公元前1200年&#xff09;3. 殷墟甲骨文&#xff08;約公元前14世紀&#xff09;4. 京杭大運河&#xff08;公元前486年始建&…

護網期間監測工作全解析:內容與應對策略

護網期間監測工作全解析&#xff1a;內容與應對策略 一、引言 在數字化浪潮中&#xff0c;網絡安全的重要性愈發凸顯&#xff0c;護網行動作為保障關鍵信息基礎設施安全的關鍵舉措&#xff0c;備受矚目。護網期間&#xff0c;監測工作是發現潛在威脅、防范攻擊的重要防線。全…

【Centos7搭建Zabbix4.x監控HCL模擬網絡設備:zabbix-server搭建及監控基礎05

蘭生幽谷&#xff0c;不為莫服而不芳&#xff1b; 君子行義&#xff0c;不為莫知而止休。 5.zabbix監控HCL模擬網絡設備 在保證zabbix-server與HCL網絡相通的情況下進行如下操作。 5.1創建主機群 配置-主機群-創建主機群 圖 19 取名&#xff0c;添加。 圖 20 5.2 創建監控…

趣味極簡品牌海報藝術貼紙設計圓潤邊緣無襯線粗體裝飾字體 Chunko Bold - Sans Serif Font

Chunko Bold 是一種功能強大的顯示字體&#xff0c;體現了大膽極簡主義的原則 – 當代設計的主流趨勢。這種自信的字體將粗獷的幾何形狀與現代的趣味性相結合&#xff0c;具有圓潤的邊緣和強烈的存在感&#xff0c;與當今的極簡主義設計方法完美契合。無論是用于鮮明的構圖還是…

Spring Boot(十七):集成和使用Redis

Redis(Remote Dictionary Server,遠程字典服務器)是一個開源的、基于內存的數據結構存儲系統,它可以用作數據庫、緩存和消息中間件。Spring Boot 中集成和使用Redis主要涉及以下幾個步驟: 添加依賴 在項目的pom.xml文件中添加Redis的依賴。Spring Boot提供了對Redis的集…

2025-03-21 Unity 序列化 —— 自定義2進制序列化

文章目錄 前言1 項目結構1.1 整體1.2 代碼 2 實現2.1 Processor2.1.1 BaseType2.1.2 CollectionType2.1.3 CustomType 2.2 ByteFormatter2.3 ByteHelper 3 使用 前言 ? BinaryFormatter 類可以將 C# 類對象快速轉換為字節數組數據。 ? 在網絡開發時&#xff0c;不會使用 Bi…

為WordPress自定義一個留言板

要在WordPress中創建一個留言反饋表單&#xff0c;并實現后臺管理功能&#xff0c;您可以按照以下步驟進行操作&#xff1a; 1. 創建留言反饋表單 首先&#xff0c;您需要使用一個表單插件來創建表單。推薦使用 Contact Form 7 或 WPForms。以下是使用 Contact Form 7 的示例…

嵌入式項目:利用心知天氣獲取天氣數據實驗方案

【實驗目的】 1、利用心知天氣服務器獲取指定位置天氣數據 2、將天氣數據解析并可視化顯示到OLED屏幕 【實驗原理】 【實驗步驟】 官網注冊

go-zero學習筆記

內容不多&#xff0c;只有部分筆記&#xff0c;剩下的沒有繼續學下去&#xff0c;包括路由與處理器、日志中間件、請求上下文 文章目錄 1、go-zero核心庫1.1 路由與處理器1.2 日志中間件1.3 請求上下文 1、go-zero核心庫 1.1 路由與處理器 package mainimport ("github…

【Go】Go語言繼承-多態模擬

繼承&#xff08;結構體嵌入&#xff09;多態&#xff08;接口實現和空接口&#xff09; 1. 繼承&#xff08;結構體嵌入&#xff09; Go 語言沒有傳統的面向對象的繼承機制&#xff0c;但可以通過“結構體嵌入”實現類似繼承的效果。 結構體嵌入&#xff1a;在結構體中嵌入另…