最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C語言實現(xiàn)八大排序算法的代碼詳解

 更新時間:2026年05月14日 09:16:45   作者:代碼地平線  
排序,是計算機程序設計中最為基礎且重要的算法之一,無論是面試題還是實際工程,排序算法總是高頻出現(xiàn),本文從 冒泡排序 到 計數(shù)排序,逐一分析每種排序的核心思路、代碼實現(xiàn)、時間與空間復雜度,需要的朋友可以參考下

引言

排序,是計算機程序設計中最為基礎且重要的算法之一。無論是面試題還是實際工程,排序算法總是高頻出現(xiàn)。本文從 冒泡排序計數(shù)排序,逐一分析每種排序的核心思路、代碼實現(xiàn)、時間與空間復雜度,并給出 10 萬級數(shù)據(jù)的實測對比,幫你建立完整的排序知識體系。

排序,籠統(tǒng)來說就是將一串記錄按照關鍵字的大小,遞增或遞減地排列起來。生活中處處都有排序的影子——購物按價格篩選、成績排行榜、考試成績排名等。

本文基于 C 語言,實現(xiàn)以下八大排序算法(全部以遞增為例):

序號排序名稱類別
1冒泡排序交換排序
2堆排序選擇排序
3直接插入排序插入排序
4希爾排序插入排序
5直接選擇排序選擇排序
6快速排序交換排序
7歸并排序歸并排序
8計數(shù)排序非比較排序

一、冒泡排序

冒泡排序是我們接觸的第一個排序,雖然效率不高,但教學意義重大,是打開排序算法世界大門的第一把鑰匙。

圖解過程

初始數(shù)組:[5, 3, 8, 4, 2]

第一趟(i=0):

[5, 3, 8, 4, 2]
  ↓比較
[3, 5, 8, 4, 2]  交換 5>3
  ↓比較
[3, 5, 8, 4, 2]  不交換 5<8
  ↓比較
[3, 4, 8, 5, 2]  交換 8>4
  ↓比較
[3, 4, 2, 8, 5]  交換 8>2
第一趟結(jié)束,最大值8沉到最右

第二趟(i=1):

[3, 4, 2, 8, 5]
  ↓比較
[3, 4, 2, 8, 5]  不交換 3<4
  ↓比較
[2, 4, 3, 8, 5]  交換 4>2
  ↓比較
[2, 3, 4, 8, 5]  交換 4>3
第二趟結(jié)束,次大值5在8左邊

第三趟(i=2):

[2, 3, 4, 8, 5]
  ↓比較
[2, 3, 4, 8, 5]  不交換 2<3
  ↓比較
[2, 3, 4, 8, 5]  不交換 3<4
第三趟結(jié)束,無需交換,數(shù)組已有序

代碼實現(xiàn)

void Swap(int* a, int* b)
{
    int c = *a;
    *a = *b;
    *b = c;
}

void maopao(int* arr, int r)
{
    for (int i = 0; i < r - 1; i++)           // 趟數(shù)
    {
        int flag = 1;
        for (int j = 0; j < r - 1 - i; j++)   // 兩兩比較
        {
            if (arr[j] > arr[j + 1])
            {
                flag = 0;
                Swap(&arr[j], &arr[j + 1]);
            }
        }
        if (flag == 1) return;               // 提前結(jié)束優(yōu)化
    }
}

過程分析

  • 外層循環(huán)控制總的趟數(shù),對于 n 個元素,只需要 n-1 趟,因為最后一趟只剩一個元素無需比較。
  • 內(nèi)層循環(huán)負責兩兩比較,將大的元素逐步"冒泡"到右側(cè)。
  • 每經(jīng)過一趟排序,未排序部分就會少一個元素,因此內(nèi)層 j 的上限要減去 i。
  • flag 優(yōu)化:當某一趟沒有任何交換時,說明數(shù)組已經(jīng)有序,直接 return。

時空復雜度

  • 空間復雜度:O(1),僅使用了常量級輔助變量
  • 時間復雜度:O(N²),最差情況為逆序

性能驗證

10 萬個隨機數(shù),冒泡排序耗時約 3000+ ms,效率最低,但邏輯最為簡單。

二、堆排序

堆排序利用 這一完全二叉樹結(jié)構(gòu)的特點——堆頂元素要么最大(大堆),要么最小(小堆),不斷交換堆頂與末尾元素并重新調(diào)整堆,最終得到有序序列。

圖解過程

以數(shù)組 [4, 10, 3, 5, 1] 為例,構(gòu)建大堆:

原始完全二叉樹:
        4
      /   \
    10      3
   /  \
  5    1

建堆過程(從最后一個非葉子節(jié)點開始向下調(diào)整):
節(jié)點(10)是最后一個非葉子節(jié)點,比較10與孩子5、1,10最大無需交換
節(jié)點(4)與孩子10、3比較,4<10,交換
        10
      /    \
     4      3
    / \
   5   1

再調(diào)整節(jié)點(4),與孩子5比較,4<5,交換
        10
      /    \
     5      3
    / \
   4   1

代碼實現(xiàn)

void Swap(int* a, int* b)
{
    int c = *a;
    *a = *b;
    *b = c;
}

// 向下調(diào)整算法 —— 構(gòu)建大堆
void AdjustDown(int* arr, int r, int parent)
{
    int child = parent * 2 + 1;
    while (child < r)
    {
        if (child + 1 < r && arr[child + 1] > arr[child])
        {
            child++;
        }
        if (arr[parent] < arr[child])
        {
            Swap(&arr[parent], &arr[child]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}

void Heappai(int* arr, int r)
{
    // 建堆 —— 從第一個非葉子節(jié)點開始向下調(diào)整
    for (int i = (r - 1 - 1) / 2; i >= 0; i--)
    {
        AdjustDown(arr, r, i);
    }

    // 排序:不斷將堆頂(最大值)與末尾交換,再調(diào)整堆
    int end = r;
    while (end)
    {
        Swap(&arr[0], &arr[--end]);
        AdjustDown(arr, end, 0);
    }
}

過程分析

  • 建堆:從最后一個非葉子節(jié)點 (n-1-1)/2 開始往前,對每個節(jié)點執(zhí)行向下調(diào)整,最終得到一個大堆。
  • 排序:將堆頂最大值與數(shù)組末尾交換,此時末尾就是最大元素;然后對剩余的 n-1 個元素重新調(diào)整為堆,循環(huán)直至堆為空。
  • 核心就在于 向下調(diào)整算法 —— 讓父節(jié)點與孩子節(jié)點比較,若孩子比父大(大堆),則交換,并繼續(xù)向下調(diào)整。

時空復雜度

  • 空間復雜度:O(1),原地排序
  • 時間復雜度:O(N log N),建堆 O(N),每次調(diào)整 O(log N),共 N 次

性能驗證

10 萬數(shù)據(jù)僅需 6 ms 左右,遠超冒泡排序。

三、直接插入排序

想象一下打撲克牌時,每摸一張牌都會按順序插入到手牌中——直接插入排序就是這個思路。

圖解過程

數(shù)組 [4, 5, 2, 7, 1],逐步將元素插入已排序部分:

初始:已排序[4],待插入[5, 2, 7, 1]

插入5:tep=5,end=0,4<5 不挪,插入到位置1
已排序[4, 5],待插入[2, 7, 1]

插入2:tep=2,end=1,5>2 挪到位置2,end=0
              4>2 挪到位置1,end=-1
              插入到位置0
已排序[2, 4, 5],待插入[7, 1]

插入7:tep=7,end=2,5<7 不挪,插入到位置3
已排序[2, 4, 5, 7],待插入[1]

插入1:tep=1,end=3,7>1 挪,end=2
              5>1 挪,end=1
              4>1 挪,end=0
              2>1 挪,end=-1
              插入到位置0
最終:[1, 2, 4, 5, 7]

代碼實現(xiàn)

void insertsort(int a[], int n)
{
    for (int i = 0; i < n - 1; i++)
    {
        int end = i;
        int tep = a[end + 1];          // 保存待插入元素
        while (end >= 0)
        {
            if (a[end] > tep)          // 比待插入元素大,往后挪
            {
                a[end + 1] = a[end];
                end--;
            }
            else
            {
                break;
            }
        }
        a[end + 1] = tep;              // 插入到正確位置
    }
}

過程分析

  • 外層循環(huán)控制要插入的元素,用 end 指向已排序部分的最后一個位置,tep 保存待插入元素。
  • 內(nèi)層 while 循環(huán)中,如果已排序元素比 tep 大,就把它往后挪一位;否則找到插入位置。
  • 簡單說:移動排序,像整理撲克牌一樣,比新牌大的牌就往前挪。

時空復雜度

  • 空間復雜度:O(1)
  • 時間復雜度:O(N²),最差情況為逆序;但實際很難遇到最差情況,所以實際效率比冒泡高不少

性能驗證

10 萬數(shù)據(jù)約 幾十毫秒,明顯優(yōu)于冒泡排序。

四、希爾排序

希爾排序是直接插入排序的升級版,核心思想是預排序——先讓數(shù)據(jù)基本有序,最后再做一次直接插入排序。

希爾排序法又稱縮小增量法。先選定一個整數(shù)(通常是 gap = n/3+1),把待排序記錄分成各組,所有距離相等的記錄分在同一組內(nèi),對每一組進行排序,然后 gap = gap/3+1 得到下一個整數(shù),再次分組排序……當 gap=1 時,就相當于直接插入排序。

圖解過程

數(shù)組 [9, 5, 1, 7, 3, 6, 4, 8],gap 從 3 遞減到 1:

gap = 3 時,分組情況:
索引:  0  1  2  3  4  5  6  7
數(shù)據(jù): 9  5  1  7  3  6  4  8
組別:  A  B  A  B  A  B  A  B

A組 [9, 1, 3, 4] 排序后:[1, 3, 4, 9]
B組 [5, 7, 6, 8] 排序后:[5, 6, 7, 8]

gap = 1 時,整體插入排序,此時數(shù)組已接近有序,效率極高

代碼實現(xiàn)

void shellsort(int a[], int n)
{
    int gap = n;
    while (gap > 1)
    {
        gap = gap / 3 + 1;            // 保證最后一次 gap=1
        for (int i = 0; i < n - gap; i++)
        {
            int end = i;
            int tep = a[end + gap];
            while (end >= 0)
            {
                if (a[end] > tep)
                {
                    a[end + gap] = a[end];
                    end -= gap;
                }
                else
                {
                    break;
                }
            }
            a[end + gap] = tep;
        }
    }
}

過程分析

  • gap 就是分組間隔,gap/3+1 使得 gap 逐步縮小,最終必為 1。
  • 每組內(nèi)進行插入排序,當 gap=1 時,整個數(shù)組已經(jīng)基本有序,直接插入排序效率最高。
  • 外層 for 循環(huán)用 i 來控制遍歷,而不是每組單獨排——優(yōu)化點在于 i 到了哪一組就排哪一組。

時空復雜度

  • 空間復雜度:O(1)
  • 時間復雜度:O(N^1.3) 左右,《數(shù)據(jù)結(jié)構(gòu)(C語言版)》— 嚴蔚敏

性能驗證

10 萬數(shù)據(jù)約 6 ms,相比直接插入排序提升顯著。

五、直接選擇排序

直接選擇排序的思路非常直接:每次在未排序部分選出最小值放到開頭,選出最大值放到末尾,依次收縮邊界。

圖解過程

數(shù)組 [3, 5, 1, 4, 2],begin=0,end=4:

初始:[3, 5, 1, 4, 2]
       ↑          ↑
      mini       maxi

第一輪找最小最大:
遍歷 [3,5,1,4,2],發(fā)現(xiàn) mini=2(在索引4),maxi=5(在索引1)
交換 min 到開頭,max 到末尾:
[2, 5, 1, 4, 3] ←→ [2, 3, 1, 4, 5]
  begin=1, end=3

第二輪:
遍歷 [5,1,4,3],發(fā)現(xiàn) mini=1(在索引2),maxi=5(在索引0,但開頭已處理)
因為 maxi==begin,需要將 maxi 修正為 mini(索引2)
交換:
[1, 3, 5, 4, 2]
  begin=2, end=2,結(jié)束

代碼實現(xiàn)

void SelectSort(int* arr, int n)
{
    int begin = 0, end = n - 1;
    while (begin < end)
    {
        int mini = begin, maxi = begin;
        for (int i = begin + 1; i <= end; i++)
        {
            if (arr[i] < arr[mini]) mini = i;
            if (arr[i] > arr[maxi]) maxi = i;
        }
        // 注意:若最大值在開頭,先交換會覆蓋 mini 的位置,需要修正
        if (maxi == begin) maxi = mini;
        Swap(&arr[mini], &arr[begin]);
        Swap(&arr[maxi], &arr[end]);
        begin++;
        end--;
    }
}

過程分析

  • 遍歷未排序區(qū)間 [begin, end],找出最小值下標 mini 和最大值下標 maxi。
  • 交換到兩端后,begin++,end–,縮小區(qū)間。
  • 特別注意:如果最大值恰好在開頭,先交換 mini 和 begin 后,最大值的位置會被覆蓋,此時需要將 maxi 修正為 mini。

時空復雜度

  • 空間復雜度:O(1)
  • 時間復雜度:O(N²)

性能驗證

與冒泡排序大差不差,10 萬數(shù)據(jù)約 2000-3000 ms

六、快速排序

快速排序簡稱"快排",相信即便沒學過也聽過它的大名,是面試和工程中的高頻明星。

快速排序的基本思想:任取待排序元素序列中的某元素作為基準值,按照該排序碼將待排序集合分割成兩子序列,左子序列所有元素均小于基準值,右子序列所有元素均大于基準值,然后遞歸左右子序列,直至所有元素排列在相應位置上。

圖解過程 —— Lomuto 前后指針法

數(shù)組 [4, 2, 7, 1, 5, 3],選基準值 key=4(左端):

初始:
[4, 2, 7, 1, 5, 3]
 ↑key
prev=0, cur=1

cur=1:a[1]=2 < 4,++prev=1,交換(自己和自己,換了等于沒換)
[4, 2, 7, 1, 5, 3]

cur=2:a[2]=7 > 4,cur++,不交換
cur=3:a[3]=1 < 4,++prev=3,交換 a[3]?a[3]
[4, 2, 1, 7, 5, 3]

cur=4:a[4]=5 > 4,cur++,不交換
cur=5:a[5]=3 < 4,++prev=4,交換 a[5]?a[4]
[4, 2, 1, 3, 5, 7]
           ↑
          prev

最后交換 a[prev] 和 a[key]:
[3, 2, 1, 4, 5, 7]
             ↑
           基準值位置(已歸位)

左區(qū)間 [3,2,1],右區(qū)間 [5,7],遞歸繼續(xù)...

1. hoare 版本

int GetMid(int* a, int left, int right)
{
    int mid = (left + right) / 2;
    if (a[left] > a[right])
    {
        if (a[right] > a[mid]) return right;
        else if (a[mid] > a[left]) return left;
        else return mid;
    }
    else
    {
        if (a[mid] < a[left]) return left;
        else if (a[mid] > a[right]) return right;
        else return mid;
    }
}

int Quicksort(int* a, int left, int right)
{
    int mid = GetMid(a, left, right);
    Swap(&a[left], &a[mid]);           // 三數(shù)取中優(yōu)化
    int key = left;
    int begin = left, end = right;
    while (begin < end)
    {
        while (begin < end && a[end] >= a[key]) end--;
        while (begin < end && a[begin] <= a[key]) begin++;
        Swap(&a[begin], &a[end]);
    }
    Swap(&a[key], &a[begin]);
    return begin;
}

void QuickSort(int* a, int left, int right)
{
    if (left >= right) return;
    int key = Quicksort(a, left, right);
    QuickSort(a, left, key - 1);
    QuickSort(a, key + 1, right);
}

2. Lomuto 前后指針法

int partQuickSort(int* a, int left, int right)
{
    int mid = GetMid(a, left, right);
    Swap(&a[left], &a[mid]);
    int key = left;
    int prev = left, cur = left + 1;
    while (cur <= right)
    {
        if (a[cur] < a[key] && ++prev != cur)
            Swap(&a[prev], &a[cur]);
        cur++;
    }
    Swap(&a[prev], &a[key]);
    return prev;
}

3. 小區(qū)間優(yōu)化 + 三數(shù)取中

void Quicksort2(int* a, int left, int right)
{
    if (left >= right) return;
    if (right - left + 1 < 10)         // 小區(qū)間優(yōu)化
    {
        insertsort(a + left, right - left + 1);
        return;
    }
    int key = Quicksort(a, left, right);
    Quicksort2(a, left, key - 1);
    Quicksort2(a, key + 1, right);
}

4. 非遞歸版本(借助棧)

void QuickSortNonR(int* a, int left, int right)
{
    ST st;
    STInit(&st);
    STPush(&st, right);
    STPush(&st, left);
    while (!STEmpty(&st))
    {
        int begin = STTop(&st); STPop(&st);
        int end = STTop(&st);   STPop(&st);
        int key = partQuickSort(a, begin, end);
        if (key + 1 < end) { STPush(&st, end); STPush(&st, key + 1); }
        if (begin < key - 1) { STPush(&st, key - 1); STPush(&st, begin); }
    }
    STDestory(&st);
}

時空復雜度

  • 空間復雜度:O(log N)(遞歸棧幀)
  • 時間復雜度:O(N log N),最差 O(N²)(有序數(shù)組可通過三數(shù)取中避免)

性能驗證

10 萬數(shù)據(jù)約 4 ms,綜合性能最強。

七、歸并排序

歸并排序采用 分治法 的思想,先遞歸拆分數(shù)組至單個元素,再有序合并兩個子數(shù)組,最終得到完全有序的序列。

圖解過程

數(shù)組 [6, 5, 3, 1, 8, 7, 2, 4] 的遞歸拆分與合并:

遞歸拆分:
[6, 5, 3, 1, 8, 7, 2, 4]
[6, 5, 3, 1]    [8, 7, 2, 4]
[6, 5]  [3, 1]  [8, 7]  [2, 4]
[6] [5] [3] [1] [8] [7] [2] [4]   ← 單個元素,遞歸終止

兩兩合并(歸并):
[5, 6]  [1, 3]  [7, 8]  [2, 4]
[1, 3, 5, 6]    [2, 4, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8]   ← 完全有序

遞歸版本

void _MergeSort(int* a, int* tmp, int begin, int end)
{
    if (begin == end) return;
    int mid = (begin + end) / 2;
    _MergeSort(a, tmp, begin, mid);
    _MergeSort(a, tmp, mid + 1, end);

    // 歸并
    int begin1 = begin, end1 = mid;
    int begin2 = mid + 1, end2 = end;
    int i = begin;
    while (begin1 <= end1 && begin2 <= end2)
    {
        if (a[begin1] <= a[begin2]) tmp[i++] = a[begin1++];
        else                        tmp[i++] = a[begin2++];
    }
    while (begin1 <= end1) tmp[i++] = a[begin1++];
    while (begin2 <= end2) tmp[i++] = a[begin2++];
    memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}

void MergeSort(int* a, int n)
{
    int* tmp = (int*)malloc(sizeof(int) * n);
    _MergeSort(a, tmp, 0, n - 1);
    free(tmp);
}

這里時間復雜度未來介紹一下為什么是O(logN)
這里拿N等于8舉例
因為這里用的是遞歸思想第一次是處理一個數(shù)組長度為的8進行歸并排序所以是O(N);
第二個進入遞歸,是處理二個長度為【4】【4】的二個數(shù)組分別進行歸并排序
第三次是【2】【2】【2】【2】進行歸并。
第四次是【1】【1】【1】【1】【1】【1】【1】【1】歸并
我們可以看見每一層都是O(N)
那一共有幾層呢
我們可以算一下

  • 初始數(shù)組長度:n
  • 第1次拆分:得到2個長度為n/2的子數(shù)組
  • 第2次拆分:得到4個長度為n/4的子數(shù)組
  • 第k次拆分:得到2^k個長度為n/2的k次方的子數(shù)組

當子數(shù)組長度為1時,停止拆分:

n/z^k= 1

兩邊取以2為底的對數(shù):

k = log2 n

所以總層數(shù)約為log2 n層(向上取整,因為數(shù)組長度不一定是2的整數(shù)次冪)。
所以他的時間復雜度是O(NlogN)

非遞歸版本(循環(huán)實現(xiàn))

void _MergeSortNonR(int* a, int n)
{
    int* tmp = (int*)malloc(sizeof(int) * n);
    int gap = 1;
    while (gap < n)
    {
        for (int i = 0; i < n; i += 2 * gap)
        {
            int begin1 = i, end1 = i + gap - 1;
            int begin2 = i + gap, end2 = i + 2 * gap - 1;
            if (begin2 >= n) break;
            if (end2 >= n) end2 = n - 1;
            int j = begin1;
            while (begin1 <= end1 && begin2 <= end2)
            {
                if (a[begin1] <= a[begin2]) tmp[j++] = a[begin1++];
                else                        tmp[j++] = a[begin2++];
            }
            while (begin1 <= end1) tmp[j++] = a[begin1++];
            while (begin2 <= end2) tmp[j++] = a[begin2++];
            memcpy(a + i, tmp + i, (end2 - begin1 + 1) * sizeof(int));
        }
        gap *= 2;
    }
    free(tmp);
}

這里我們可以通過圖看見里面的循環(huán)執(zhí)行次數(shù)每一次都是O(N)
外面的次數(shù)是logN
所以他的時間復雜度也是O(NlogN)

時空復雜度

  • 空間復雜度:O(N),需要額外輔助數(shù)組
  • 時間復雜度:O(N log N)

性能驗證

10 萬數(shù)據(jù)約 4 ms,與快排持平,且性能穩(wěn)定。

八、計數(shù)排序

前面七種排序都需要兩兩比較元素大小,計數(shù)排序則另辟蹊徑,通過統(tǒng)計每個元素出現(xiàn)的次數(shù),按下標天然有序的特性來完成排序

圖解過程

數(shù)組 [4, 2, 4, 1, 3]

Step 1:找最小最大值
min=1, max=4,range = 4-1+1 = 4

Step 2:創(chuàng)建計數(shù)數(shù)組(長度4,全0)
count: [0, 0, 0, 0]
        ↓
       index

Step 3:遍歷原數(shù)組,統(tǒng)計并映射
a[0]=4 → count[4-1]=count[3]++
a[1]=2 → count[2-1]=count[1]++
a[2]=4 → count[3]++
a[3]=1 → count[1-1]=count[0]++
a[4]=3 → count[3-1]=count[2]++

count: [1, 1, 1, 2]
         ↑  ↑  ↑  ↑
        1   2   3   4  (+min還原)

Step 4:遍歷計數(shù)數(shù)組,回寫原數(shù)組
count[0]=1 → a[0]=1+1=2
count[1]=1 → a[1]=2+1=3
count[2]=1 → a[2]=3+1=4
count[3]=2 → a[3]=4+1=5, a[4]=5+1=6
最終:[2, 3, 4, 5, 6]

代碼實現(xiàn)

void contsort(int* a, int n)
{
    int min = a[0], max = a[0];
    for (int i = 1; i < n; i++)
    {
        if (a[i] < min) min = a[i];
        if (a[i] > max) max = a[i];
    }
    int range = max - min + 1;
    int* cout = (int*)calloc(range, sizeof(int));

    // 統(tǒng)計次數(shù)
    for (int i = 0; i < n; i++)
    {
        cout[a[i] - min]++;           // 映射到計數(shù)數(shù)組下標
    }

    // 回寫
    int j = 0;
    for (int i = 0; i < range; i++)
    {
        while (cout[i]--)
        {
            a[j++] = i + min;
        }
    }
    free(cout);
}

過程分析

  1. 先遍歷數(shù)組找出最小值和最大值,確定計數(shù)數(shù)組的大小 range = max - min + 1。
  2. 創(chuàng)建計數(shù)數(shù)組 cout,用 calloc 初始化為 0。
  3. 遍歷原數(shù)組,通過 a[i] - min 映射到計數(shù)數(shù)組下標并累加計數(shù)。
  4. 最后遍歷計數(shù)數(shù)組,按下標(加上最小值還原原值)依次填回原數(shù)組。

特性分析

  • 計數(shù)排序不是比較排序,利用下標天然有序的特性完成排序。
  • 適用場景:數(shù)據(jù)范圍集中時效率極高;數(shù)據(jù)分散時空間浪費嚴重。

時空復雜度

  • 空間復雜度:O(range)
  • 時間復雜度:O(N + range)

性能驗證

10 萬數(shù)據(jù) 0 ms,在適用場景下堪稱恐怖,但適用范圍有限。

九、完整測試代碼

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

int main()
{
    srand((unsigned int)time(NULL));
    const int N = 100000;
    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);

    for (int i = 0; i < N; ++i)
    {
        a1[i] = rand() + i;
        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 begin7 = clock(); MergeSort(a7, N);        int end7 = clock();
    int begin6 = clock(); QuickSortNonR(a6, 0, N - 1); int end6 = clock();
    int begin5 = clock(); shellsort(a5, N);         int end5 = clock();
    int begin4 = clock(); Heappai(a4, N);          int end4 = clock();
    int begin3 = clock(); maopao(a3, N);           int end3 = clock();
    int begin2 = clock(); qsort(a2, N, sizeof(int), paixu); int end2 = clock();
    int begin1 = clock(); Quicksort2(a1, 0, N - 1); int end1 = clock();

    printf("排序 %d 個隨機數(shù),各算法用時(毫秒):\n", N);
    printf("MergeSort:       %d\n", end7 - begin7);
    printf("QuickSortNonR:   %d\n", end6 - begin6);
    printf("shellsort:       %d\n", end5 - begin5);
    printf("Heapsort:        %d\n", end4 - begin4);
    printf("bubblesort:      %d\n", end3 - begin3);
    printf("qsort:           %d\n", end2 - begin2);
    printf("Quicksort2:      %d\n", end1 - begin1);

    free(a1); free(a2); free(a3); free(a4); free(a5); free(a6); free(a7);
    return 0;
}

測試結(jié)果

排序 100000 個隨機數(shù),各算法用時(毫秒):
MergeSort:       4
QuickSortNonR:   4
shellsort:       6
Heapsort:        6
bubblesort:      3000+
qsort:           8
Quicksort2:      4

十、排序穩(wěn)定性詳解

什么是穩(wěn)定性?

穩(wěn)定性是指:待排序序列中存在值相等的元素,排序后這些相等元素的相對前后順序保持不變,則稱該排序算法是穩(wěn)定的;否則稱為不穩(wěn)定的。

舉例說明

有一個數(shù)組,每個元素不僅有值,還有原始下標(用于區(qū)分相同值):

初始狀態(tài):  [5a, 3, 5b, 1, 3]
          a在b前,都是5

排升序后:

  • 穩(wěn)定排序結(jié)果: [1, 3a, 3, 5a, 5b] → 兩個 3 的相對順序沒變,兩個 5 的相對順序也沒變
  • 不穩(wěn)定排序結(jié)果: [1, 3, 3, 5b, 5a] → 兩個 5 的相對順序被顛倒

為什么有的穩(wěn)定,有的不穩(wěn)定?

穩(wěn)定排序:只在必須交換時才交換,相同值之間靠比較判斷 (> / <),相等時保持原位置不動。

排序穩(wěn)定原因
冒泡排序if(arr[j] > arr[j+1])> 而非 ,相等時不交換 ?
直接插入排序從后往前找,遇到 a[end] > tep 才挪,等于時 break,相同值保持原有順序 ?
歸并排序合并時 a[begin1] <= a[begin2],用 <= 保證相等時左區(qū)間優(yōu)先 ?
計數(shù)排序用下標映射,下標天然有序,相同值不會被調(diào)換位置 ?

不穩(wěn)定排序:排序過程中,相同值的元素可能被交換到另一個相同值的前面或后面,破壞了原始相對順序。

排序不穩(wěn)定原因
直接選擇排序每次同時交換 min 和 max 到兩端,相同值的兩個元素可能被換位 ?
希爾排序gap > 1 預排序階段,跨組比較時相同值可能產(chǎn)生相對位移 ?
堆排序建堆和調(diào)整過程中,堆頂與末尾交換時,相同值可能被打亂 ?
快速排序hoare/挖坑法中,right 找小 left 找大交換,相同值可能在兩區(qū)間之間被換位 ?

穩(wěn)定性的意義

如果排序?qū)ο笫?strong>多字段結(jié)構(gòu)體(比如先按成績排序,相同成績時保持姓名先后順序),穩(wěn)定性就有重要價值。

簡單說:穩(wěn)定排序保護"相等"元素的相對位置,不穩(wěn)定排序不保證這一點。

十一、排序總結(jié)

排序名稱最好時間平均時間最壞時間空間復雜度穩(wěn)定性
冒泡排序O(N)O(N²)O(N²)O(1)? 穩(wěn)定
堆排序O(N log N)O(N log N)O(N log N)O(1)? 不穩(wěn)定
直接插入排序O(N)O(N²)O(N²)O(1)? 穩(wěn)定
希爾排序O(N log N)O(N^1.3)O(N²)O(1)? 不穩(wěn)定
直接選擇排序O(N²)O(N²)O(N²)O(1)? 不穩(wěn)定
快速排序O(N log N)O(N log N)O(N²)O(log N)? 不穩(wěn)定
歸并排序O(N log N)O(N log N)O(N log N)O(N)? 穩(wěn)定
計數(shù)排序O(N + range)O(N + range)O(N + range)O(range)? 穩(wěn)定

十二、內(nèi)部排序與外部排序

前面講的八大排序算法,全部屬于內(nèi)部排序,因為它們假設數(shù)據(jù)在內(nèi)存中,可以隨機訪問。但實際工程中,數(shù)據(jù)量往往遠大于內(nèi)存容量,這就需要外部排序。

什么是內(nèi)部排序?什么是外部排序?

類型定義適用場景
內(nèi)部排序(內(nèi)排)數(shù)據(jù)全部加載到內(nèi)存中,可以隨機訪問任意元素數(shù)據(jù)量小,能完整裝進內(nèi)存
外部排序(外排)數(shù)據(jù)太大無法一次性裝進內(nèi)存,需要分塊讀寫磁盤/文件超大文件、百萬/千萬級數(shù)據(jù)量

什么時候用外排?

當數(shù)據(jù)量達到內(nèi)存裝不下的程度時,就必須用外排:

  • 排序 10 億個整數(shù)(~40GB),機器只有 16GB 內(nèi)存
  • 排序一個 100GB 的日志文件
  • 數(shù)據(jù)庫對超大型表進行排序輸出

外排的核心思想:分而治之 + 多路歸并

外排分為兩個階段

階段一:分段內(nèi)排

原始大文件(太大,無法一次讀入內(nèi)存)
        ↓
每次讀一塊數(shù)據(jù)進內(nèi)存(比如 1GB)
        ↓
用內(nèi)排算法(快排/歸并排)排好這一塊
        ↓
寫回磁盤,得到若干有序的小文件(稱為"歸并段")

階段二:多路歸并

多個有序小文件
        ↓
每次從各文件讀一個數(shù)(或一小批),選最小/最大的輸出
        ↓
繼續(xù)讀、繼續(xù)選,直到所有文件處理完畢
        ↓
最終得到完全有序的大文件

圖解過程

假設內(nèi)存每次最多容納 3 個整數(shù),待排序數(shù)據(jù)為 [8, 3, 9, 2, 7, 1, 5, 4, 6]

【階段一:分段內(nèi)排】
內(nèi)存每次最多3個數(shù),分3次讀入:

讀入 [8, 3, 9] → 內(nèi)排 → [3, 8, 9] → 寫回 temp1
讀入 [2, 7, 1] → 內(nèi)排 → [1, 2, 7] → 寫回 temp2
讀入 [5, 4, 6] → 內(nèi)排 → [4, 5, 6] → 寫回 temp3

得到三個有序文件:temp1=[3,8,9]  temp2=[1,2,7]  temp3=[4,5,6]

【階段二:多路歸并】
三路歸并:每次從 temp1/temp2/temp3 各讀一個數(shù)出來比較

第一輪:
比較 3(t1)、1(t2)、4(t3) → 選 1 → 輸出 [1],從 temp2 補一個數(shù)
比較 3(t1)、2(t2)、4(t3) → 選 2 → 輸出 [1,2],從 temp2 補一個數(shù)
比較 3(t1)、7(t2)、4(t3) → 選 3 → 輸出 [1,2,3],從 temp1 補一個數(shù)
比較 8(t1)、7(t2)、4(t3) → 選 4 → 輸出 [1,2,3,4],從 temp3 補一個數(shù)
比較 8(t1)、7(t2)、5(t3) → 選 5 → 輸出 [1,2,3,4,5],從 temp3 補一個數(shù)
比較 8(t1)、7(t2)、6(t3) → 選 6 → 輸出 [1,2,3,4,5,6],從 temp3 補一個數(shù)
...繼續(xù),最終輸出 [1,2,3,4,5,6,7,8,9]

關鍵點:多路歸并的效率

多路歸并的復雜度為 O(N logK),其中:

  • N 為總數(shù)據(jù)量
  • K 為歸并路數(shù)(文件數(shù)量)

路數(shù) K 越大,層數(shù)越少,讀寫磁盤次數(shù)越少。理想情況下 K 越大越好,但受限于內(nèi)存中同時打開的文件描述符數(shù)量

實際優(yōu)化手段:

  • 增加歸并路數(shù)(K 從 2 增大到 8、16……)
  • 敗者樹/勝者樹:減少比較次數(shù)
  • 置換-選擇排序:生成長度更大的有序歸并段,減少歸并段數(shù)量

內(nèi)排 vs 外排的選擇

數(shù)據(jù)量內(nèi)存夠用?推薦方案
幾千~幾萬?直接內(nèi)排,快排/歸并排隨便選
幾十萬~幾百萬?內(nèi)排,可用優(yōu)化快排或歸并排
數(shù)千萬~數(shù)億?外排,分塊內(nèi)排 + 多路歸并
TB 級數(shù)據(jù)?外排 + 加大歸并路數(shù)/多線程/分布式

一句話總結(jié)

  • 內(nèi)排:數(shù)據(jù)裝得下內(nèi)存,所有元素隨意訪問,快排/歸并排/堆排隨便用
  • 外排:數(shù)據(jù)太大裝不下,分塊讀進內(nèi)存排好序,再通過歸并思想把各有序塊合并成整體有序

這里舉一個例子說一下

假如我們有10億個整數(shù),那他就要10億乘以4個比特位,而內(nèi)存就只有1G,這里我們見一下?lián)Q算單位
1G=1024MB
1MB=1024KB
1KB=1024byte

所以1G大約等于10的9次方byte也就是1億
所以這時候我們用內(nèi)存,直接直接排序根本排不了,這時候我們就要使用外排序了。
這里我們可以先把4G的大文件存在磁盤里面,在分別取1G存進內(nèi)存里面進行排序,內(nèi)存里面現(xiàn)在也不能用歸并排序,因為他還要開辟一個O(N)的空間,所以我們就用快排,排內(nèi)存的,然后依次排4個文件,在內(nèi)存里面排好了再取出來,進行歸并排序,下面我用一個圖來說一下。

結(jié)語

以上就是C語言實現(xiàn)八大排序算法的代碼詳解的詳細內(nèi)容,更多關于C語言八大排序算法的資料請關注腳本之家其它相關文章!

相關文章

  • C語言 數(shù)組指針詳解及示例代碼

    C語言 數(shù)組指針詳解及示例代碼

    本文主要介紹C語言 數(shù)組指針,這里整理了相關資料并附示例待會及實現(xiàn)結(jié)果,幫助大家學習C語言中指針的知識,有需要學習此部分內(nèi)容的朋友可以參考下
    2016-08-08
  • C語言超細致講解循環(huán)語句

    C語言超細致講解循環(huán)語句

    我們說到當滿足特定條件時,就會執(zhí)行if語句或者switch語句后面的語句,否則不執(zhí)行,但是這只能執(zhí)行一次,在日常生活中,有些事情是需要重復去做的,C語句就為此引入了循環(huán)語句。所以今天繼續(xù)為大家分享C語言循環(huán)家族
    2022-05-05
  • C++?opencv圖像處理實現(xiàn)灰度變換示例

    C++?opencv圖像處理實現(xiàn)灰度變換示例

    這篇文章主要為大家介紹了C++?opencv圖像處理灰度變換的實現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-05-05
  • C語言?超詳細梳理總結(jié)動態(tài)內(nèi)存管理

    C語言?超詳細梳理總結(jié)動態(tài)內(nèi)存管理

    動態(tài)內(nèi)存是相對靜態(tài)內(nèi)存而言的。所謂動態(tài)和靜態(tài)就是指內(nèi)存的分配方式。動態(tài)內(nèi)存是指在堆上分配的內(nèi)存,而靜態(tài)內(nèi)存是指在棧上分配的內(nèi)存,本文帶你深入探究C語言中動態(tài)內(nèi)存的管理
    2022-03-03
  • Cocos2d-x UI開發(fā)之場景切換代碼實例

    Cocos2d-x UI開發(fā)之場景切換代碼實例

    這篇文章主要介紹了Cocos2d-x UI開發(fā)之場景切換代碼實例,cocos2d-x中的場景切換是通過導演類調(diào)用相應的方法完成的,本文通過代碼和詳細注釋來說明,需要的朋友可以參考下
    2014-09-09
  • C++的虛析構(gòu)詳解及實例代碼

    C++的虛析構(gòu)詳解及實例代碼

    這篇文章主要介紹了C++的虛析構(gòu)詳解及實例代碼的相關資料,需要的朋友可以參考下
    2017-05-05
  • C/C++ chrono簡單使用場景示例詳解

    C/C++ chrono簡單使用場景示例詳解

    這篇文章主要介紹了C/C++ chrono簡單使用場景示例詳解,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2025-06-06
  • C++中的vector中erase用法實例代碼

    C++中的vector中erase用法實例代碼

    在vector數(shù)組中我們刪除數(shù)組經(jīng)常用的就是erase方法,但是earse的用法一不注意就會出錯,今天我就遇到了,所以在這里總結(jié)一下,避免大家用錯,對vector中erase用法感興趣的朋友跟隨小編一起看看吧
    2022-11-11
  • C++中重載、重寫(覆蓋)和隱藏的區(qū)別實例分析

    C++中重載、重寫(覆蓋)和隱藏的區(qū)別實例分析

    這篇文章主要介紹了C++中重載、重寫(覆蓋)和隱藏的區(qū)別,是C++面向?qū)ο蟪绦蛟O計非常重要的概念,需要的朋友可以參考下
    2014-08-08
  • C語言算法--有序查找(折半查找/二分查找)

    C語言算法--有序查找(折半查找/二分查找)

    我們知道無序查找只能靠遍歷,如果有序查找我們還挨個去遍歷,未免太浪費時間,所以這里我們會用到不一樣的方法,希望能給你帶來幫助
    2021-08-08

最新評論

贵溪市| 象州县| 山阴县| 山丹县| 绵阳市| 梅河口市| 鄂托克旗| 冀州市| 鞍山市| 洛宁县| 灵台县| 普洱| 奈曼旗| 石棉县| 仙游县| 监利县| 噶尔县| 都安| 玛曲县| 民丰县| 科技| 永登县| 土默特左旗| 大安市| 阿巴嘎旗| 无锡市| 安康市| 四会市| 军事| 仁怀市| 库伦旗| 澄城县| 毕节市| 佛教| 公主岭市| 工布江达县| 恩施市| 革吉县| 台山市| 南丹县| 乾安县|