一些常用排序算法的實現(C語言版)

一些比較常用的排序算法(C語言)

  • 插入排序
  • 選擇排序
  • 歸併排序
  • 堆排序
  • 計數排序
  • 快速排序
插入排序
  • 算法分析
  1. 從第2個數開始,與之前第一個數比較,如果小於第一個數,那麼將第一個數後移,同時設置臨時變量保存第二個數,將第一個數設置爲第二個數
  2. 依次類推,當比較到arr[i]時,那麼將arr[i]與arr[i-1]個數相比較,如果第i個數小於第i-1個數, 那麼將第i-1個數後移,同時,與第i-2個數想比較…
  3. 如果第arr[i-1]個數>arr[i-2]個數,跳出循環,同時將保存的第i個數插入到i-2的位置
  • 算法演示
    圖片來源:https://www.cnblogs.com/onepixel/articles/7674659.html

  • 算法實現(帶主函數)

void InsertSort(int numbers[],int length)
{
    int p;  //保存當前的位置
    int tempValue; //保存當前變量
    for (int i = 1;i < length ;i++)
    {
        tempValue = numbers[i]; //從第二個元素開始
        p = i - 1; //標記上一個元素的位置
        while (p >= 0 && tempValue < numbers[p]) { 
        //與上一個數比較,如果上一個數>當前要比較的數,將上一個數後移
            numbers[p + 1] = numbers[p];
            p --;   //再與上一個數的上一個數進行比較
        }
        numbers[p+1] = tempValue;  //如果上一個數<當前要比較的數,則將當前數插入到相應的位置
    }
}

主函數的實現

/*
隨機生成一組數,測試使用,當然可以自己手動構建一組數進行測試
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void initARandom(int nums[],int length)
{
    srand((unsigned)time(NULL));
    for (int i = 0; i < length; ++i) {
        nums[i] = rand()%60;
    }
}
/*
打印數組中的值,方便測試
*/
void print_array(int A[],int length,char *print_string){
    printf("%s\n", print_string);
    for (int i = 0; i < length ; ++i){
        printf("%d\t",A[i]);
    }
    printf("\n");
}

int main(int argc, char const *argv[])
{
    int length = 9;
    int numbers[length];
    initARandom(numbers, length);
    print_array(numbers, length, "before sort:");
    InsertSort(numbers, length);
    print_array(numbers,length,"after sort:");
    return 0;
}

輸出結果:

before sort:
38	15	54	18	35	31	51	34	46	
after sort:
15	18	31	34	35	38	46	51	54	
Program ended with exit code: 0
  • 算法時間與空間複雜度:
    最好情況:O(n)
    最壞情況:O(n^2)
    平均: O(n^2)
    空間複雜度:O(1)
    是一種穩定的排序算法
選擇排序
  • 算法描述
  1. 查找每組數中的最大值,與最後一位數交換
  2. 依次遞減,再次查找最大值,與倒數第二個交換
  3. …直到交換到第一個數字,循環結束
  • 算法演示
    圖片來源:https://www.cnblogs.com/onepixel/articles/7674659.html
  • 算法實現:
/*
查找最大值,返回index
*/
int findMax(int arr[],int length)
{
    int max = arr[0];
    int index = 0;
    for (int i = 0; i < length; i++) {
        if (max < arr[i])
        {
            max = arr[i];
            index = i;
        }
    }
    return index;
}
void selection_sort(int arr[],int length)
{
    int maxPos;
    while (length > 1)
    {
        maxPos = findMax(arr, length);
        int temp = arr[length - 1];
        arr[length - 1] = arr[maxPos];
        arr[maxPos] = temp;
        length--;
    }
}

主函數中的實現

int main(int argc, char const *argv[])
{
    int length = 9;
    int numbers[length];
    initARandom(numbers, length);
    print_array(numbers, length, "before sort:");
    selection_sort(numbers, length);
    print_array(numbers,length,"after sort:");
    return 0;
}

測試結果

before sort:
25	16	10	52	51	21	7	59	49	
after sort:
7	10	16	21	25	49	51	52	59	
Program ended with exit code: 0
  • 算法時間與空間複雜度:
    最好情況:O(n^2)
    最壞情況:O(n^2)
    平均:O(n^2)
    空間複雜度:O(1)
    是不穩定排序算法
歸併排序
  • 算法分析
    主要分兩段:
    第一段是合併算法
    第二段是分治思想遞歸分解每一段,對每一小段進行排序
    第一段合併算法:假設我們已經有了一個分爲兩段排好序的數組(例如:[6, 7, 8, 9,0,1,2,3,4])

    1. 首先定義三個指針變量,i,j k,i指針指向第一段[6,7,8,9]中的第一個首元素,j指向第二段[0,1,2,3,4]中的0,k指針指向要輸出的數組首地址,[6,7,8,9,0,1,2,3,4]中的6
    2. 通過比較A[i] 與 B[j]的大小,如果A[i] < B[j],那麼,k指向A[i],i++,k++,反之,j++,k++,如果i 與 j 其中的一個已經達到了A 或者B的長度減一,那麼跳出循環,將另外一個數組中的元素輸出到k所指向的數組中,完成合並

    第二段:產生有序的兩段數組,爲步驟1,步驟2做準備

    1. 產生一個分兩段排好序的數組,用遞歸操作,首先將數組分爲兩段,A數組和B數組,再對A數組分段,對B數組分段,直到分段數組中左右兩段元素個數都爲1個,跳出遞歸
    2. 調用上述合併算法,將左右兩段進行合併,完成排序

    *算法演示
    圖片來源:https://www.cnblogs.com/onepixel/articles/7674659.html

    • 算法實現:
      合併算法:
/*
leftStart:表示數組開始點
rightEnd:表示數組結束點
m:表示要將數組分割的分割點
*/
void merge(int array[],int leftStart,int m,int rightEnd)
{
    int LEFT_SIZE = m - leftStart; //計算左邊數組的長度
    int RIGHT_SIZE = rightEnd - m + 1; //計算右邊數組的長度
    int leftArray[LEFT_SIZE]; //初始化分段左邊的數組
    int rightArray[RIGHT_SIZE];//初始化分段右邊的數組
    //1.將傳入的數組中前leftSize個元素分別輸出的leftArray數組中
    for (int i = leftStart ; i < m; i++)
    {
        leftArray[i - leftStart] = array[i];
    }

    //2.將m - rightEnd之間的數據輸出到rightArray數組中
    for (int i = m; i <= rightEnd; i++)
    {
        rightArray[i - m] = array[i];
    }
    int i = 0,j = 0,k = leftStart;
    //將兩個數組內容合併,假如leftArray 或者 rightArray中某一個數組已經達到了它的長度,那麼跳出循環
    while (i < LEFT_SIZE && j < RIGHT_SIZE)
    {
        if (leftArray[i] < rightArray[j])
        {
            array[k] = leftArray[i];
            i++;
            k++;
        }else
        {
            array[k] = rightArray[j];
            j++;
            k++;
        }
    }
    //如果是rightArray達到了它的長度,則將leftArray剩餘的數據輸出到輸出的數組
    while (i < LEFT_SIZE)
    {
        array[k] = leftArray[i];
        k++;
        i++;
    }
    //如果是leftArray達到了它的長度,則將rightArray剩餘的數據輸出到輸出的數組
    while (j < RIGHT_SIZE)
    {
          array[k] = rightArray[j];
          k++;
          j++;
    }
}

分段算法:

//分治法思想,切分亂序數組
void mergeSort(int array[],int leftStart,int rightEnd)
{
    if (leftStart == rightEnd) return; else {
          int m = (leftStart + rightEnd) / 2;//找出中心位置
          mergeSort(array, leftStart, m);//切分左邊數組
          mergeSort(array, m + 1, rightEnd);//切分右邊數組
          merge(array, leftStart, m + 1, rightEnd);//合併左右兩邊的數組
    }
}

主函數中的實現:

int main(int argc, char const *argv[])
{
    int length = 9;
    int numbers[length];
    initARandom(numbers, length);
    print_array(numbers, length, "before sort:");
    mergeSort(numbers, 0, length - 1);
    print_array(numbers,length,"after sort:");
    return 0;
}

實現結果:

before sort:
14	49	50	50	11	13	59	51	56	
after sort:
11	13	14	49	50	50	51	56	59	
Program ended with exit code: 0
  • 算法時間複雜度與空間複雜度
    最好情況:O(nlog2n)
    最壞情況:O(nlog2n)
    平均:O(nlog2n)
    空間複雜度:O(n)
    是一種穩定的排序算法
堆排序
  • 算法分析:
    首先要了解一下二叉樹的概念:

    • 每一個堆就是一個完全二叉樹,堆中的每一個根節點都小於兩個孩子子結點稱爲小頂堆,每一個根節點大於兩個孩子結點稱爲大頂堆
    • 已知第i個孩子結點,那麼父結點爲(i- 1)/2 向下取整
    • 已知一個父親結點i:那麼左孩子結點爲:2i + 1,右孩子結點爲2i+2
    • 最後一個葉子結點爲:n - 1,n爲結點個數
    • 升序排序,要找大頂堆
    • 降序排序,要找小頂堆

    過程:

    1. 升序排序,創建大根堆
    2. 將大根堆的根節點與最後一個葉子結點進行交換
    3. 交換後將最後一個結點剔除(並不是物理剔除),重新構建大根堆(不包含最後一個最大的葉子結點),直到下次構建大根堆的時候當前結點已經大於n,循環結束,排序完成
    • 算法演示
      圖片來源:https://www.cnblogs.com/onepixel/articles/7674659.html
    • 算法實現:
/*
 交換數組中兩個元素之間的順序
*/
void swap(int arr[],int i, int j)
{
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;

}
/*
  除該節點外,子結點均爲堆時,可以直接調用此方法,此方法是堆排序的最小化單元
*/
void heapify(int tree[],int n,int i)
{
    if (i >= n)  //遞歸出口
    {
        return;
    }
    int left = 2 * i + 1;  //第i個結點的左結點
    int right = 2 * i + 2; //第i個結點的右結點
    int max = i;           
    if (left < n && (tree[max] < tree[left])) 
    {   //當左結點不是葉子結點的時候(因爲葉子結點沒有子結點了)
        max = left;
    }
    if (right < n && (tree[max] < tree[right]))
    {
        max = right;
    }
    //以上兩個判斷是找最大值,如果最大值不是當前結點,交換使得當前結點最大
    if (max != i)
    {
        swap(tree, max, i);
        //遞歸當前結點,當前結點達到跳出遞歸的條件,也就是到了葉子結點,跳出遞歸
        heapify(tree, n, max);
    }
}

以上heapify算法是除了當前傳入的結點,子結點已經是堆了,纔會調用該方法,那麼如果是一個亂序的數組,則需要首先創建大根堆

void build_heap(int tree[],int n)
{
    int last_node = n - 1;//找到最後一個葉子結點,自下而上的將所有父親結點都變爲大根堆
    int parent = (last_node - 1) / 2;//找到父親結點
    for (int i = parent; i >= 0; i--)
    {
        heapify(tree, n, i);將每一個結點都變爲大根堆
    }
}

排序實現算法

void heap_short(int tree[],int n)
{
    build_heap(tree,n); //首先要創建一個大根堆
    int last_node = n - 1; //找到最後一個葉子結點
    for (int i = last_node;i >= 0; i--)
    {
        swap(tree, i, 0);//交換根節點與葉子結點,交換後,根節點再是大根堆結點,但是除了最後一個結點,其他結點的子結點均爲大根堆結點,所以需要對根節點重新做一次heapify
        heapify(tree, i, 0);
    }

}

主函數中的實現:

int main(int argc, char const *argv[])
{
    int length = 9;
    int numbers[length];
    initARandom(numbers, length);
    print_array(numbers, length, "before sort:");
    heap_short(numbers, length);
    print_array(numbers,length,"after sort:");
    return 0;
}
before sort:
10	47	40	30	51	37	13	25	23	
after sort:
10	13	23	25	30	37	40	47	51	
Program ended with exit code: 0
  • 算法時間與空間複雜度:
    最好情況:O(nlog2n)
    最壞情況: O(nlog2n)
    平均:O(nlog2n)
    空間複雜度:O(1)
計數排序
  • 算法分析:

    1. 首先,需要一個輔助數組cArray,用於存放計數的數目,還需要一個bArray,用於存放排好序的數據
    2. 獲取原數據數組中的最大值,已確定輔助數組cArray的長度,長度爲最大值+1
    3. 將原數組中第i個元素出現的次數放入到輔助數組cArray對應的下標,例如 原數組中的數據爲3,那麼就將1放入cArray下標爲3的位置cArray[3]=1
    4. 如果有重複,那麼就將該位置計數器加1,重複幾次,就加幾,比如3出現了4次,那麼cArray[3]=4
    5. 將原數組中的元素逐個輸出到bArray中,沒輸出一次,cArray對應的數據就要減1,比如已經輸出了3,那麼 cArray[3] = 4-1
  • 算法演示:
    在這裏插入圖片描述

  • 算法實現:

//首先要獲取數組的最大值
int getMax(int nums[],int length)
{
    int max = nums[0];
    for (int i = 1; i < length; i++) {
        if (nums[i] > max)
        {
            max = nums[i];
        }
    }
    return max;
}
//計數方法
void counting(int numbers[],int bArray[],int k,int length)
{
    //找出最大值,確認在計數排序數組中的位置
    int cArray[k + 1];
    for (int i = 0; i <  k + 1; i++)
    {
        cArray[i] = 0;
    }
    //計數排序核心,記錄每個數據出現的次數,在cArray中對應的位置做+1操作
    for (int i = 0; i < length; i++) {
        cArray[numbers[i]] += 1;
    }
    //將cArray中每一項與前一項做加法,得到i(這裏的i其實是原數組中的元素) 元素在即將要輸出數組中的位置
    for (int i = 1 ;i < k + 1; i++)
    {
        cArray[i] += cArray[i - 1];
    }
    //輸出到B數組
    for (int i = 0; i < length ; i++)
    {
        //輸出一次,那麼久相應的要在c數組中對應的位置減一
        cArray[numbers[i]] --;
        bArray[cArray[numbers[i]]] = numbers[i];
    }
    numbers

}
//排序的實現
void counting_sort(int array[],int bArray[],int length)
{
    int max = getMax(array, length);
    counting(array, bArray, max, length);
}

在主函數中的實現:

int main(int argc, char const *argv[])
{
    int length = 9;
    int numbers[length];
    initARandom(numbers, length);
    print_array(numbers, length, "before sort:");
    int bArray[length];
    counting_sort(numbers,bArray, length);
    print_array(bArray,length,"after sort:");
    return 0;
  • 輸出結果
before sort:
6	21	14	32	2	21	33	34	25	
after sort:
2	6	14	21	21	25	32	33	34	
Program ended with exit code: 0
  • 算法時間與空間複雜度分析:
    最好情況:O(n + k)
    最壞情況:O(n + k)
    平均:O(n + k)
    空間複雜度:O(n+k)
    計數排序是一個穩定的排序
快速排序
  • 算法分析:
    在快排中,還是用用到分治的思想,將問題最小化到一個子問題,然後通過遞歸到全部問題。
    1. 首先,我們要找一個basicKey,也叫pivotKey,作爲左右指針比較的基準值,從最右邊的指針開始,依次向左移動,直到出現一個比pivotKey小的值,將a[right]賦值給a[left],然後移動左指針left,找到一個比pivokey大的值,將a[left]賦值給a[right],循環直到left=right,此時返回left與right所處的值爲整個數組的中心點
    2. 將pivokey賦值給a[left]
    3. 以返回的樞值爲中心,分別對左右兩個子數組進行步驟一,直到數組中的元素爲1,此時,跳出遞歸
    4. 整個循環結束,排序完成
  • 算法演示圖:
    在這裏插入圖片描述
  • 算法實現過程:
//返回樞值(中心值,也就是left與right指針重合的地方)
int partition(int array[],int left,int right)
{
    int key = array[left];// 將第1個作爲關鍵字
    while (left < right) {// 兩端交替向中間掃描
        // 移動最右邊的指針,找到比pivotKey值小的值
        while (left < right && array[right] >= key) --right;
        array[left] = array[right];// 將比關鍵字小的移動到左邊
        // 移動左指針,找到比pivotKey大的值
        while (left < right && array[left] <= key) ++left;
        array[right] = array[left];// 將比關鍵字大的移動到右邊
    }
    array[left] = key;// 此時left就是一趟快速排序後的關鍵字所在的位置
    return left;
}
//排序算法
void quickShort(int array[],int left,int right)
{
    if (left < right)
    {
        int mid = partition(array,left,right);
        quickShort(array, left, mid - 1); //遞歸對左子數組排序
        quickShort(array, mid + 1, right);//遞歸對右子數組排序
    }
}

主函數中的實現

int main(int argc, char const *argv[])
{
    int length = 9;
    int numbers[length];
    initARandom(numbers, length);
    print_array(numbers, length, "before sort:");
    quickShort(numbers, 0, length - 1);
    print_array(numbers,length,"after sort:");
    return 0;
}
  • 輸出結果:
int main(int argc, char const *argv[])
{
    int length = 9;
    int numbers[length];
    initARandom(numbers, length);
    print_array(numbers, length, "before sort:");
    quickShort(numbers, 0, length - 1);
    print_array(numbers,length,"after sort:");
    return 0;
}
  • 時間複雜度與空間複雜度:
    最好:O(nlog2n)
    最壞:O(n^2)
    平均:O(nlog2n)
    空間複雜度:O(nlog2n)
    快速排序是不穩定排序
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章