C語言8大經典排序算法(詳細測試核心代碼)

排序算法小結

定義

排序算法的核心是比較和移動,排序算法分類爲內部排序和外部排序,區別的要點是排序過程是否需要外部的內存交換過程;也可以按照算法的思路分爲比較排序和非比較排序;
排序算法的穩定性,若排序對象中存在多個關鍵字相同的記錄,經過排序後,相同關鍵字的記錄之間的相對次序保持不變,則該排序方法是穩定的,若次序發生變化(哪怕只有兩條記錄之間),則該排序方法是不穩定的;
時間複雜度,一般情況下,算法中基本操作重複執行的次數是問題規模n的某個函數,用T(n)表示,若有某個輔助函數f(n),使得T(n)/f(n)的極限值(當n趨近於無窮大時)爲不等於零的常數,記T(n)=O(f(n))T(n),O(f(n))​爲算法的漸進時間複雜度,簡稱時間複雜度;
空間複雜度,空間複雜度(Space Complexity)是對一個算法在運行過程中臨時佔用存儲空間大小的量度,它是問題規模n
n的函數,記做S(n)=O(f(n))。比如直接插入排序的時間複雜度是O(n2),空間複雜度是O(1)。而一般的遞歸算法就要有O(n)的空間複雜度了,因爲每次遞歸都要存儲返回信息,需要輔助空間的大小隨着n的增大線性增大。

1、冒泡排序
基本原理:
N個元素,只剩一個首元素的時候,不需要比較,總共需要循環比對N-1次,每一次選出其中最大或者最小的數冒出到尾端;
在每一個循環 i 中,從當前循環的第一元素開始比較到最後一個,因此共有N-1-i次內部循環,內部進行比較操作,符合條件的兩數,則移動雙方位置。
改進:
1、如果某次冒泡不存在數據交換,則說明已經排序好了,可以直接退出排序
2、頭尾進行冒泡,每次把最大的沉底,最小的浮上去,兩邊往中間靠1

~~雞尾酒排序與冒泡排序~~ 的不同處在於排序時是以首尾雙向在序列中進行排序。
先對數組從左到右進行升序的冒泡排序;
再對數組進行從右到左的降序的冒泡排序;
以此類推,持續的、依次的改變冒泡的方向,並不斷縮小沒有排序的數組範圍。

2、直接插入排序
基本原理:
將第1和第2元素排好序,然後將第3個元素插入到已經排好序的元素中,依次類推,直到輪詢到最後一個元素。

    for (int i = 1; i < n; i++)//從第二個元素開始,依次與前一個元素比大小
    {
        if (a[i] < a[i - 1])//若前面的元素大於後面的元素,則尋找後面元素本應正確的位置
        {
            temp = a[i];
            for (j = i - 1; j >= 0 && a[j] > temp; j--)
            {
                a[j + 1] = a[j];
            }
            a[j + 1] = temp;//將無序元素放入到正確的位置
        }
    }

3、希爾排序
基本原理:
插入排序每次只能操作一個元素,效率低。元素個數N,取奇數k=N/2,將下標差值爲k的數分爲一組(一組元素個數看總元素個數決定),在組內構成有序序列,再取k=k/2,將下標差值爲k的數分爲一組,構成有序序列,直到k=1,然後再進行直接插入排序。
設置一個最小增量dk,剛開始dk設置爲n/2。進行插入排序,隨後再讓dk=dk/2,再進行插入排序,直到dk爲1時完成最後一次插入排序,此時數組完成排序。
例如,現在要對序列{ 1,8,3,9,5,7 }進行希爾排序

第一步,選擇增量爲3,進行排序:

則產生三個分組(括號爲數組元素下標)
{ 1(0),9(3) }
{ 8(1),5(4) }
{ 3(2),7(5) }
然後對該所有分組進行直接插入排序,得到新的分組(注意:此時下標是不會變的,只是用直接插入排序進行數組排序)
{ 1(0),9(3) }
{ 5(1),8(4) }
{ 3(2),7(5) }
因此按照下標,我們可以寫出第一趟按照增量爲3的希爾排序得到的序列:{ 1,5,3,9,8,7 }

第二步,選擇增量爲1,進行排序

這一步,直接按照直接插入排序的方法,進行直接插入排序,得到有序序列:{ 1,3,5,7,8,9 }

//    初始時從dk開始增長,每次比較步長爲dk
void Insrtsort(int *a, int n,int dk) {
    for (int i = dk; i < n; ++i) {
        int temp = a[i];
        for(j = i-dk; j >=0 && a[j] > temp; j=j-dk)
        {
        	a[j+dk] = a]j];
		}
            a[j+dk] = tmp;         //    插入tmp
        }
    }
}

void ShellSort(int *a, int n) {
    int dk = n / 2;        //    設置初始dk
    while (dk >= 1) {
        Insrtsort(a, n, dk);
        dk /= 2;
    }
}

4、簡單選擇排序
基本原理:
每次循環選出當前未排序的列表中最小的數,因此每次排序完後的,分爲有序組和無序組,無序組的容量會逐漸減少,外部循環N-1次。每一次循環內,並不是比較大小出現衝突就換,而是記錄下min的下標位置,一次循環完後才移動一次位置。

   for (int i = 0; i < n; i++)
    {
        int key = i;    //    臨時變量用於存放數組最小值的位置
        for (int j = i + 1; j < n; j++) {
            if (a[j] < a[key]) {    
                key = j;    //    記錄數組最小值位置
            }
        }
            if (key != i)
            {
                int tmp = a[key];
                a[key] = a[i];
                a[i] = tmp;    //    交換最小值
            }
   }

5、堆排序
推薦博文:
堆排序詳解
圖解堆排序過程
詳細的遞歸代碼

【基本原理】
先把數組構造成一個大頂堆(父親節點大於其子節點),然後把堆頂(數組最大值,數組第一個元素)和數組最後一個元素交換,這樣就把最大值放到了數組最後邊。把數組長度n-1,再進行構造堆,把剩餘的第二大值放到堆頂,輸出堆頂(放到剩餘未排序數組最後面)。依次類推,直至數組排序完成。

堆分爲大根堆和小根堆 完全二叉樹
大根堆 任意一個父節點的值大於等於其子節點的值【用於升序排列】
小根堆 任意一個父節點的值小於等於其子節點的值【用於降序排列】

【大根堆】
以升序排序爲例,利用大根堆的性質(堆頂元素最大)不斷輸出最大元素,直到堆中沒有元素
1.構建大根堆
2.輸出堆頂元素與堆底元素交換
3.將堆低元素放一個到堆頂,再重新構造成大根堆,再輸出堆頂元素,以此類推
【代碼思路】

○  ○  ○  ○  ○  ○  ○
根 左 右
1、若根的索引爲0開始,最遠的非葉子節點(最後的父節點)按照層次排列:i = n/2 -1 2(n爲數目,i爲索引)——其左孩紙(2i+1) 右孩子(2i+2)
若根的索引爲1開始,則最遠的非葉子節點(最後的父節點)按照層次排列:i = n/2
——其左孩紙(2i) 右孩子(2i+1
#include <stdio.h>
#include <stdlib.h>
 
void swap(int* a, int* b)
{
    int temp = *b;
    *b = *a;
    *a = temp;
}
 //從頂到尾迭代實現每一個父節點和子結點的比較和交換
void max_heapify(int arr[], int start, int end) 
{
    //建立父節點指標和子節點指標
    int dad = start;
    int son = dad * 2 + 1;
    while (son <= end)  //若子節點指標在範圍內才做比較
    {
        if (son + 1 <= end && arr[son] < arr[son + 1]) 
        //先比較兩個子節點大小,選擇最大的
        son++;
	    if (arr[dad] > arr[son]) //如果父節點大於子節點代表調整完畢,直接跳出函數
	        return;
	    else  //否則交換父子內容再繼續子節點和孫節點比較
		    {
		        swap(&arr[dad], &arr[son]);
		        dad = son;
		        son = dad * 2 + 1;
		    }
	}
}
 
void heap_sort(int arr[], int len) 
{
    int i;
    //初始化,i從最後一個父節點開始調整
    //建立一個初始化的大頂堆
    for (i = len / 2 - 1; i >= 0; i--)
    	//迭代實現堆的構建
        max_heapify(arr, i, len - 1);
    //先將第一個元素和已排好元素前一位做交換,再重新調整,直到排序完畢
    for (i = len - 1; i > 0; i--) 
    {
        swap(&arr[0], &arr[i]);
        max_heapify(arr, 0, i - 1);
    }
}
 
int main() {
    int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
    int len = (int) sizeof(arr) / sizeof(*arr);
    heap_sort(arr, len);
    int i;
    for (i = 0; i < len; i++)
        printf("%d ", arr[i]);
    printf("\n");
    return 0;
}

6、快速排序
基本原理:
選擇一個基準元素,比基準元素小的放基準元素的前面,比基準元素大的放基準元素的後面,這種動作叫分區,每次分區都把一個數列分成了兩部分,每次分區都使得一個數字有序,然後將基準元素前面部分和後面部分繼續分區,一直分區直到分區的區間中只有一個元素的時候。
快速排序的一趟算法思想是:
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.重複第3、4步,直到 i = j 結束。
領域:
原本在正確位置的元素是不會移動,每一次前後的指針相同時,表示一次排序完成,接着以該位置分爲前後兩個部分,重複操作。

//快速排序
void QuickSort(int a[], int left, int right)
{
    if (left >= right) {
        return;
    }
    // 定義兩個標識
    int i = left;
    int j = right;
    int key = a[left];//將序列的第一個元素設爲基數
    int temp;
    while (i < j)
    {
        while (i < j && a[j] >= key)
        {
            j--;
        }
        if (i != j)//如果i j沒有相遇,交換位置
        {
            temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
        while (i < j && a[i] <= key)
        {
            i++;
        }
        if (i != j)//如果i j沒有相遇,交換位置
        {
            temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
        else
        {
            printArr(a, 8);//打印數組
        }
    }
    QuickSort(a, left, i - 1);
    QuickSort(a, i + 1, right);
}

7、歸併排序
基本原理:分治思想
將一個無序的數列一直一分爲二,直到分到序列中只有一個數的時候,這個序列肯定是有序的,因爲只有一個數,然後將兩個只含有一個數字的序列合併爲含有兩個數字的有序序列,這樣一直進行下去,最後就變成了一個大的有序數列

// 合併兩個已排好序的數組
void Merge(int a[], int left, int mid, int right)
{
    int len = right - left + 1;        //    數組的長度
    int *temp = new int[len];       // 分配個臨時數組
    int k = 0;
    int i = left;                   // 前一數組的起始元素
    int j = mid + 1;                // 後一數組的起始元素
    while (i <= mid && j <= right)
    {
        //    選擇較小的存入臨時數組
        temp[k++] = a[i] <= a[j] ? a[i++] : a[j++];  
    }
    while (i <= mid)
    {
        temp[k++] = a[i++];
    }
    while (j <= right)
    {
        temp[k++] = a[j++];
    }
    for (int k = 0; k < len; k++)
    {
        a[left++] = temp[k];
    }
}

// 遞歸實現的歸併排序
void MergeSort(int a[], int left, int right)  
{
    if (left == right)    //結束的標誌
        return;
    int mid = (left + right) / 2;
    MergeSort(a, left, mid);
    MergeSort(a, mid + 1, right);
    Merge(a, left, mid, right);
}

8、桶排序
桶排序算法詳解
1、注意桶範圍的選取、排序數值的最小值和最大值的差、或者將原數按照相同的過程進行處理
2、注意相同數的排序

#include <stdio.h>
int main()
{
    int book[1001],i,j,t,n;
    for(i=0;i<=1000;i++)
        book[i]=0;
    scanf("%d",&n);//輸入一個數n,表示接下來有n個數
    for(i=1;i<=n;i++)//循環讀入n個數,並進行桶排序
    {
        scanf("%d",&t);  //把每一個數讀到變量t中
        book[t]++;  //進行計數,對編號爲t的桶放一個小旗子
    }
    for(i=1000;i>=0;i--)  //依次判斷編號1000~0的桶
        for(j=1;j<=book[i];j++)  //出現了幾次就將桶的編號打印幾次
             printf("%d ",i);
    getchar();getchar();
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章