七大經典排序算法總結(C語言描述)

簡介算法

  其中排序算法總結以下:shell

一.交換排序

  交換排序的基本思想都爲經過比較兩個數的大小,當知足某些條件時對它進行交換從而達到排序的目的。數組

1.冒泡排序

  基本思想:比較相鄰的兩個數,若是前者比後者大,則進行交換。每一輪排序結束,選出一個未排序中最大的數放到數組後面。性能

#include<stdio.h>
//冒泡排序算法
void bubbleSort(int *arr, int n) {
    for (int i = 0; i<n - 1; i++)
        for (int j = 0; j < n - i - 1; j++)
        {
            //若是前面的數比後面大,進行交換
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp;
            }
        }
}
int main() {
    int arr[] = { 10,6,5,2,3,8,7,4,9,1 };
    int n = sizeof(arr) / sizeof(int);
    bubbleSort(arr, n);
    printf("排序後的數組爲:\n");
    for (int j = 0; j<n; j++)
        printf("%d ", arr[j]);
    printf("\n"); 
    return 0;

分析:優化

  最差時間複雜度爲O(n^2),平均時間複雜度爲O(n^2)。穩定性:穩定。輔助空間O(1)。ui

  升級版冒泡排序法:經過從低到高選出最大的數放到後面,再從高到低選出最小的數放到前面,如此反覆,直到左邊界和右邊界重合。當數組中有已排序好的數時,這種排序比傳統冒泡排序性能稍好。spa

#include<stdio.h>
//升級版冒泡排序算法
void bubbleSort_1(int *arr, int n) {
    //設置數組左右邊界
    int left = 0, right = n - 1;
    //當左右邊界未重合時,進行排序
    while (left<right) {
        //從左到右遍歷選出最大的數放到數組右邊
        for (int i =left; i < right; i++)
        {
            if (arr[i] > arr[i + 1])
            {
                int temp = arr[i]; arr[i] = arr[i + 1]; arr[i + 1] = temp;
            }
        }
        right--;
        //從右到左遍歷選出最小的數放到數組左邊
        for (int j = right;j> left; j--)
        {
            if (arr[j + 1] < arr[j])
            {
                int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp;
            }
        }
        left++;
    }

}
int main() {
    int arr[] = { 10,6,5,2,3,8,7,4,9,1 };
    int n = sizeof(arr) / sizeof(int);
    bubbleSort_1(arr, n);
    printf("排序後的數組爲:\n");
    for (int j = 0; j<n; j++)
        printf("%d ", arr[j]);
    printf("\n");
    return 0;
}

  

2.快速排序

  基本思想:選取一個基準元素,一般爲數組最後一個元素(或者第一個元素)。從前向後遍歷數組,當遇到小於基準元素的元素時,把它和左邊第一個大於基準元素的元素進行交換。在利用分治策略從已經分好的兩組中分別進行以上步驟,直到排序完成。下圖表示了這個過程。code

  

#include<stdio.h>

void swap(int *x, int *y) {
    int tmp = *x;
    *x = *y;
    *y = tmp;
}

//分治法把數組分紅兩份
int patition(int *a, int left,int right) {
    int j = left;    //用來遍歷數組
    int i = j - 1;    //用來指向小於基準元素的位置
    int key = a[right];    //基準元素
    //從左到右遍歷數組,把小於等於基準元素的放到左邊,大於基準元素的放到右邊
    for (; j < right; ++j) {
        if (a[j] <= key)
            swap(&a[j], &a[++i]);
    }
    //把基準元素放到中間
    swap(&a[right], &a[++i]);
    //返回數組中間位置
    return i;
}
//快速排序
void quickSort(int *a,int left,int right) {
    if (left>=right)
        return;
    int mid = patition(a,left,right);
    quickSort(a, left, mid - 1);
    quickSort(a, mid + 1, right);
}
int main() {
    int a[] = { 10,6,5,7,12,8,1,3,11,4,2,9,16,13,15,14 };
    int n = sizeof(a) / sizeof(int);
    quickSort(a, 0,n-1);
    printf("排序好的數組爲:");
    for (int l = 0; l < n; l++) {
        printf("%d ", a[l]);
    }
    printf("\n");
    return 0;
}

 

分析:  blog

  最差時間複雜度:每次選取的基準元素都爲最大(或最小元素)致使每次只劃分了一個分區,須要進行n-1次劃分才能結束遞歸,故複雜度爲O(n^2);最優時間複雜度:每次選取的基準元素都是中位數,每次都劃分出兩個分區,須要進行logn次遞歸,故時間複雜度爲O(nlogn);平均時間複雜度:O(nlogn)。穩定性:不穩定的。輔助空間:O(nlogn)。排序

  當數組元素基本有序時,快速排序將沒有任何優點,基本退化爲冒泡排序,可在選取基準元素時選取中間值進行優化。

二.插入排序

  

1.直接插入排序

  基本思想:和交換排序不一樣的是它不用進行交換操做,而是用一個臨時變量存儲當前值。當前面的元素比後面大時,先把後面的元素存入臨時變量,前面元素的值放到後面元素位置,再到最後把其值插入到合適的數組位置。      

#include<stdio.h>
void InsertSort(int  *a, int n) {
    int tmp = 0;
    for (int i = 1; i < n; i++) {
        int j = i - 1;
        if (a[i] < a[j]) {
            tmp = a[i];
            a[i] = a[j];
            while (tmp < a[j-1]) {
                a[j] = a[j-1];
                j--;
            }
            a[j] = tmp;
        }
    }
}
int main() {
    int a[] = { 11,7,9,22,10,18,4,43,5,1,32};
    int n = sizeof(a)/sizeof(int);
    InsertSort(a, n);
    printf("排序好的數組爲:");
    for (int i = 0; i < n; i++) {
        printf(" %d", a[i]);
    }
    printf("\n");
    return 0;
}

 分析

  最壞時間複雜度爲數組爲逆序時,爲O(n^2)。最優時間複雜度爲數組正序時,爲O(n)。平均時間複雜度爲O(n^2)。輔助空間O(1)。穩定性:穩定。

 

2.希爾(shell)排序

  基本思想爲在直接插入排序的思想下設置一個最小增量dk,剛開始dk設置爲n/2。進行插入排序,隨後再讓dk=dk/2,再進行插入排序,直到dk爲1時完成最後一次插入排序,此時數組完成排序。

#include<stdio.h>
//    進行插入排序
//    初始時從dk開始增加,每次比較步長爲dk
void Insrtsort(int *a, int n,int dk) {
    for (int i = dk; i < n; ++i) {
        int j = i - dk;
        if (a[i] < a[j]) {    //    比較先後數字大小
            int tmp = a[i];        //    做爲臨時存儲    
            a[i] = a[j];
            while (a[j] > tmp) {    //    尋找tmp的插入位置
                a[j+dk] = a[j];
                j -= dk;
            }
            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;
    }
}

int main() {
    int a[] = { 5,12,35,42,11,2,9,41,26,18,4 };
    int n = sizeof(a) / sizeof(int);
    ShellSort(a, n);
    printf("排序好的數組爲:");
    for (int j = 0; j < n; j++) {
        printf("%d ", a [j]);
    }
    return 0;
}

分析:

  最壞時間複雜度爲O(n^2);最優時間複雜度爲O(n);平均時間複雜度爲O(n^1.3)。輔助空間O(1)。穩定性:不穩定。希爾排序的時間複雜度與選取的增量有關,選取合適的增量可減小時間複雜度。

三.選擇排序

1.直接選擇排序

基本思想:依次選出數組最小的數放到數組的前面。首先從數組的第二個元素開始日後遍歷,找出最小的數放到第一個位置。再從剩下數組中找出最小的數放到第二個位置。以此類推,直到數組有序。

#include<stdio.h>
void SelectSort(int *a, int n) {
    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;    //    交換最小值
            }
        
    }
}
int main() {
    int a[] = { 12,4,15,2,6,22,8,10,1,33,45,24,7 };
    int n = sizeof(a) / sizeof(int);
    SelectSort(a, n);
    printf("排序好的數組爲: ");
    for (int k = 0; k < n; k++)
        printf("%d ", a[k]);
    printf("\n");
    return 0;
}

分析:

  最差、最優、平均時間複雜度都爲O(n^2)。輔助空間爲O(1)。穩定性:不穩定。

2.堆(Heap)排序

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

  下圖爲堆結構及其在數組中的表示。能夠知道堆頂的元素爲數組的首元素,某一個節點的左孩子節點爲其在數組中的位置*2,其右孩子節點爲其在數組中的位置*2+1,其父節點爲其在數組中的位置/2(假設數組從1開始計數)。

  

  下圖爲怎麼把一個無序的數組構形成一個大堆頂結構的數組的過程,注意其是從下到上,從右到左,從右邊第一個非葉子節點開始構建的。

 

#include<stdio.h>

//  建立大堆頂,i爲當節點,n爲堆的大小
//    從第一個非葉子結點i從下至上,從右至左調整結構
//    從兩個兒子節點中選出較大的來與父親節點進行比較
//    若是兒子節點比父親節點大,則進行交換
void CreatHeap(int a[], int i, int  n) {

    //    注意數組是從0開始計數,因此左節點爲2*i+1,右節點爲2*i+2
    for (; i >= 0; --i)
    {
        int left = i * 2 + 1;    //左子樹節點
        int right = i * 2 + 2;    //右子樹節點
        int j = 0;
        //選出左右子節點中最大的
        if (right < n) {
            a[left] > a[right] ? j= left : j = right;
        }
        else
            j = left;
        //交換子節點與父節點
        if (a[j] > a[i]) {
            int tmp = a[i];
            a[i] = a[j];
            a[j] = tmp;
        }
    }
}

//    進行堆排序,依次選出最大值放到最後面
void HeapSort(int a[], int n) {
    //初始化構造堆
    CreatHeap(a, n/2-1, n);
  //交換第一個元素和最後一個元素後,堆的大小減1
    for (int j = n-1; j >= 0; j--) {
        
        //最後一個元素和第一個元素進行交換
        int tmp = a[0];
        a[0] = a[j];
        a[j] = tmp;

        int i = j / 2 - 1;
        CreatHeap(a, i, j);
    }
}
int main() {
    int a[] = { 10,6,5,7,12,8,1,3,11,4,2,9,16,13,15,14 };
    int n = sizeof(a) / sizeof(int);
    HeapSort(a, n);
    printf("排序好的數組爲:");
    for (int l = 0; l < n; l++) {
        printf("%d ", a[l]);
    }
    printf("\n");
    return 0;
}

 

分析:

  最差、最優‘平均時間複雜度都爲O(nlogn),其中堆的每次建立重構花費O(lgn),須要建立n次。輔助空間O(1)。穩定性:不穩定。

四.歸併排序

  基本思想:歸併算法應用到分治策略,簡單說就是把一個答問題分解成易於解決的小問題後一個個解決,最後在把小問題的一步步合併成總問題的解。這裏的排序應用遞歸來把數組分解成一個個小數組,直到小數組的數位有序,在把有序的小數組兩兩合併而成有序的大數組。

  下圖爲展現如何歸併的合成一個數組。

  下圖展現了歸併排序過程各階段的時間花費。

 

 

#include <stdio.h>
#include <limits.h>

// 合併兩個已排好序的數組
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);
}


int main() {
    int a[] = { 5,1,9,2,8,7,10,3,4,0,6 };
    int n = sizeof(a) / sizeof(int);
    MergeSort(a, 0, n - 1);
    printf("排序好的數組爲:");
    for (int k = 0; k < n; ++k)
        printf("%d ", a[k]);
    printf("\n");
    return 0;
}

分析:

  最差、最優、平均時間複雜度都爲O(nlogn),其中遞歸樹共有lgn+1層,每層須要花費O(n)。輔助空間O(n)。穩定性:穩定。

相關文章
相關標籤/搜索