八大排序算法總結

排序算法能夠分爲內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納所有的排序記錄,在排序過程當中須要訪問外存。php

常見的內部排序算法有:插入排序、希爾排序、選擇排序、冒泡排序、歸併排序、快速排序、堆排序、基數排序等。算法

本文將依次介紹上述八大排序算法。shell

 

 

算法一:插入排序

 

 

插入排序示意圖數組

插入排序是一種最簡單直觀的排序算法,它的工做原理是經過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入。數據結構

算法步驟架構

1)將第一待排序序列第一個元素看作一個有序序列,把第二個元素到最後一個元素當成是未排序序列。ide

2)從頭至尾依次掃描未排序序列,將掃描到的每一個元素插入有序序列的適當位置。(若是待插入的元素與有序序列中的某個元素相等,則將待插入元素插入到相等元素的後面。)函數

代碼實現:oop

複製代碼
void insert_sort(int array[],unsignedint n)
{
    int i,j;
    int temp;
    for(i = 1;i < n;i++)
    {
        temp = array[i];
        for(j = i;j > 0&& array[j - 1] > temp;j--)
        {
            array[j]= array[j - 1];
        }
        array[j] = temp;
    }
}
複製代碼

算法二:希爾排序

希爾排序示意圖spa

希爾排序,也稱遞減增量排序算法,是插入排序的一種更高效的改進版本。但希爾排序是非穩定排序算法。

希爾排序是基於插入排序的如下兩點性質而提出改進方法的:

  • 插入排序在對幾乎已經排好序的數據操做時, 效率高, 便可以達到線性排序的效率
  • 但插入排序通常來講是低效的, 由於插入排序每次只能將數據移動一位

希爾排序的基本思想是:先將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序,待整個序列中的記錄「基本有序」時,再對全體記錄進行依次直接插入排序。

算法步驟

1)選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;

2)按增量序列個數k,對序列進行k 趟排序;

3)每趟排序,根據對應的增量ti,將待排序列分割成若干長度爲m 的子序列,分別對各子表進行直接插入排序。僅增量因子爲1 時,整個序列做爲一個表來處理,表長度即爲整個序列的長度。

 代碼實現:

複製代碼
#include<stdio.h>
#include<math.h>
 
#define MAXNUM 10
 
void main()
{
    void shellSort(int array[],int n,int t);//t爲排序趟數
    int array[MAXNUM],i;
    for(i = 0;i < MAXNUM;i++)
        scanf("%d",&array[i]);
    shellSort(array,MAXNUM,int(log(MAXNUM + 1) / log(2)));//排序趟數應爲log2(n+1)的整數部分
    for(i = 0;i < MAXNUM;i++)
        printf("%d ",array[i]);
    printf("\n");
}
 
//根據當前增量進行插入排序
void shellInsert(int array[],int n,int dk)
{
    int i,j,temp;
    for(i = dk;i < n;i++)//分別向每組的有序區域插入
    {
        temp = array[i];
        for(j = i-dk;(j >= i % dk) && array[j] > temp;j -= dk)//比較與記錄後移同時進行
            array[j + dk] = array[j];
        if(j != i - dk)
            array[j + dk] = temp;//插入
    }
}
 
//計算Hibbard增量
int dkHibbard(int t,int k)
{
    return int(pow(2,t - k + 1) - 1);
}
 
//希爾排序
void shellSort(int array[],int n,int t)
{
    void shellInsert(int array[],int n,int dk);
    int i;
    for(i = 1;i <= t;i++)
        shellInsert(array,n,dkHibbard(t,i));
}
 
//此寫法便於理解,實際應用時應將上述三個函數寫成一個函數。
複製代碼

算法三:選擇排序

選擇排序示意圖

選擇排序(Selection sort)也是一種簡單直觀的排序算法。

算法步驟

1)首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置

2)再從剩餘未排序元素中繼續尋找最小(大)元素,而後放到已排序序列的末尾。

3)重複第二步,直到全部元素均排序完畢。

 代碼實現:

複製代碼
void select_sort(int *a,int n)
{
    register int i,j,min,t;
    for(i = 0;i < n-1;i++)
    {
        min = i;//查找最小值
        for(j = i + 1;j < n;j++)
            if(a[min] > a[j])
                min = j;//交換
        if(min != i)
        {
            t = a[min];
            a[min] = a[i];
            a[i] = t;
        }
    }
}
複製代碼

算法四:冒泡排序

冒泡排序示意圖

 冒泡排序Bubble Sort)也是一種簡單直觀的排序算法。它重複地走訪過要排序的數列,一次比較兩個元素,若是他們的順序錯誤就把他們交換過來。走訪數列的工做是重複地進行直到沒有再須要交換,也就是說該數列已經排序完成。這個算法的名字由來是由於越小的元素會經由交換慢慢「浮」到數列的頂端。

算法步驟

1)比較相鄰的元素。若是第一個比第二個大,就交換他們兩個。

2)對每一對相鄰元素做一樣的工做,從開始第一對到結尾的最後一對。這步作完後,最後的元素會是最大的數。

3)針對全部的元素重複以上的步驟,除了最後一個。

4)持續每次對愈來愈少的元素重複上面的步驟,直到沒有任何一對數字須要比較。

 代碼實現:

複製代碼
#include <stdio.h>
#define SIZE 8void bubble_sort(int a[], int n)
{
    int i, j, temp;
    for (j = 0;j < n - 1;j++)
        for (i = 0;i < n - 1 - j;i++)
        {
            if(a[i] > a[i + 1])
            {
                temp = a[i];
                a[i] = a[i + 1];
                a[i + 1] = temp;
            }
        }
}
 
int main()
{
    int number[SIZE] = {95, 45, 15, 78, 84, 51, 24, 12};
    int i;
    bubble_sort(number, SIZE);
    for (i = 0; i < SIZE; i++)
    {
        printf("%d", number[i]);
    }
    printf("\n");
}
複製代碼

算法五:歸併排序

歸併排序示意圖

歸併排序(Merge sort)是創建在歸併操做上的一種有效的排序算法。該算法是採用分治法(Divide and Conquer)的一個很是典型的應用。

算法步驟:

1. 申請空間,使其大小爲兩個已經排序序列之和,該空間用來存放合併後的序列

2. 設定兩個指針,最初位置分別爲兩個已經排序序列的起始位置

3. 比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位置

4. 重複步驟3直到某一指針達到序列尾

5. 將另外一序列剩下的全部元素直接複製到合併序列尾

代碼實現:

複製代碼
#include <stdlib.h>
#include <stdio.h>
 
void Merge(int sourceArr[],int tempArr[], int startIndex, int midIndex, int endIndex)
{
    int i = startIndex, j=midIndex+1, k = startIndex;
    while(i != midIndex + 1 && j != endIndex + 1)
    {
        if(sourceArr[i] >= sourceArr[j])
            tempArr[k++] = sourceArr[j++];
        else
            tempArr[k++] = sourceArr[i++];
    }
    while(i != midIndex+1)
        tempArr[k++] = sourceArr[i++];
    while(j != endIndex+1)
        tempArr[k++] = sourceArr[j++];
    for(i = startIndex; i <= endIndex; i++)
        sourceArr[i] = tempArr[i];
}
 
//內部使用遞歸
void MergeSort(int sourceArr[], int tempArr[], int startIndex, int endIndex)
{
    int midIndex;
    if(startIndex < endIndex)
    {
        midIndex = (startIndex + endIndex) / 2;
        MergeSort(sourceArr, tempArr, startIndex, midIndex);
        MergeSort(sourceArr, tempArr, midIndex+1, endIndex);
        Merge(sourceArr, tempArr, startIndex, midIndex, endIndex);
    }
}
 
int main(int argc, char * argv[])
{
    int a[8] = {50, 10, 20, 30, 70, 40, 80, 60};
    int i, b[8];
    MergeSort(a, b, 0, 7);
    for(i=0; i<8; i++)
        printf("%d ", a[i]);
    printf("\n");
    return 0;
}
複製代碼

算法六:快速排序

快速排序示意圖

快速排序是由東尼·霍爾所發展的一種排序算法。在平均情況下,排序 n 個項目要Ο(n log n)次比較。在最壞情況下則須要Ο(n2)次比較,但這種情況並不常見。事實上,快速排序一般明顯比其餘Ο(n log n) 算法更快,由於它的內部循環(inner loop)能夠在大部分的架構上頗有效率地被實現出來。

快速排序使用分治法(Divide and conquer)策略來把一個串行(list)分爲兩個子串行(sub-lists)。

算法步驟:

1 從數列中挑出一個元素,稱爲 「基準」(pivot),

2 從新排序數列,全部元素比基準值小的擺放在基準前面,全部元素比基準值大的擺在基準的後面(相同的數能夠到任一邊)。在這個分區退出以後,該基準就處於數列的中間位置。這個稱爲分區(partition)操做。

3 遞歸地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。

遞歸的最底部情形,是數列的大小是零或一,也就是永遠都已經被排序好了。雖然一直遞歸下去,可是這個算法總會退出,由於在每次的迭代(iteration)中,它至少會把一個元素擺到它最後的位置去。

代碼實現:

複製代碼
void Qsort(int a[], int low, int high)
{
    if(low >= high)
    {
        return;
    }
    int first = low;
    int last = high;
    int key = a[first];/*用字表的第一個記錄做爲樞軸*/
 
    while(first < last)
    {
        while(first < last && a[last] >= key)
        {
            --last;
        }
 
        a[first] = a[last];/*將比第一個小的移到低端*/
 
        while(first < last && a[first] <= key)
        {
            ++first;
        }
         
        a[last] = a[first];    
/*將比第一個大的移到高端*/
    }
    a[first] = key;/*樞軸記錄到位*/
    Qsort(a, low, first-1);
    Qsort(a, first+1, high);
}
複製代碼

算法七:堆排序

堆排序示意圖

堆排序(Heapsort)是指利用堆這種數據結構所設計的一種排序算法。堆積是一個近似徹底二叉樹的結構,並同時知足堆積的性質:即子結點的鍵值或索引老是小於(或者大於)它的父節點。

堆排序的平均時間複雜度爲Ο(nlogn) 。

算法步驟:

1)建立一個堆H[0..n-1]

2)把堆首(最大值)和堆尾互換

3)把堆的尺寸縮小1,並調用shift_down(0),目的是把新的數組頂端數據調整到相應位置

4) 重複步驟2,直到堆的尺寸爲1

代碼實現:

複製代碼
//array是待調整的堆數組,i是待調整的數組元素的位置,nlength是數組的長度
//本函數功能是:根據數組array構建大根堆
void HeapAdjust(int array[],int i,int nLength)
{
    int nChild;
    int nTemp;
    for(; 2 * i + 1 < nLength;i = nChild)
    {
        //子結點的位置=2*(父結點位置)+1
        nChild = 2 * i + 1;
        //獲得子結點中較大的結點
        if(nChild < nLength - 1 && array[nChild + 1] > array[nChild]) ++nChild;
        //若是較大的子結點大於父結點那麼把較大的子結點往上移動,替換它的父結點
        if(array[i] < array[nChild])
        {
            nTemp = array[i];
            array[i] = array[nChild];
            array[nChild] = nTemp; 
        }
        else break; //不然退出循環
    }
}
//堆排序算法
void HeapSort(int array[],int length)
{
    int i;
    //調整序列的前半部分元素,調整完以後第一個元素是序列的最大的元素
    //length/2-1是最後一個非葉節點,此處"/"爲整除
    for(i = length / 2 - 1;i >= 0;--i)
    HeapAdjust(array,i,length);
    //從最後一個元素開始對序列進行調整,不斷的縮小調整的範圍直到第一個元素
    for(i = length - 1;i > 0;--i)
    {
        //把第一個元素和當前的最後一個元素交換,
        //保證當前的最後一個位置的元素都是在如今的這個序列之中最大的
        array[i] = array[0] ^ array[i];
        array[0] = array[0] ^ array[i];
        array[i] = array[0] ^ array[i];
        //不斷縮小調整heap的範圍,每一次調整完畢保證第一個元素是當前序列的最大值
        HeapAdjust(array,0,i);
    }
}
int main()
{
    int i;
    int num[]={9,8,7,6,5,4,3,2,1,0};
    HeapSort(num,sizeof(num)/sizeof(int));
    for(i = 0;i < sizeof(num) / sizeof(int);i++)
    {
        printf("%d ",num[i]);
    }
    printf("\nok\n");
    return 0;
}
複製代碼

算法八:基數排序

基數排序是一種非比較型整數排序算法,其原理是將整數按位數切割成不一樣的數字,而後按每一個位數分別比較。因爲整數也能夠表達字符串(好比名字或日期)和特定格式的浮點數,因此基數排序也不是隻能使用於整數。

說基數排序以前,咱們簡單介紹桶排序:

算法思想:是將陣列分到有限數量的桶子裏。每一個桶子再個別排序(有可能再使用別的排序算法或是以遞迴方式繼續使用桶排序進行排序)。桶排序是鴿巢排序的一種概括結果。當要被排序的陣列內的數值是均勻分配的時候,桶排序使用線性時間(Θ(n))。但桶排序並非 比較排序,他不受到 O(n log n) 下限的影響。
簡單來講,就是把數據分組,放在一個個的桶中,而後對每一個桶裏面的在進行排序。

例如要對大小爲[1..1000]範圍內的n個整數A[1..n]排序

首先,能夠把桶設爲大小爲10的範圍,具體而言,設集合B[1]存儲[1..10]的整數,集合B[2]存儲   (10..20]的整數,……集合B[i]存儲(   (i-1)*10,   i*10]的整數,i   =   1,2,..100。總共有  100個桶。

而後,對A[1..n]從頭至尾掃描一遍,把每一個A[i]放入對應的桶B[j]中。  再對這100個桶中每一個桶裏的數字排序,這時可用冒泡,選擇,乃至快排,通常來講任  何排序法均可以。

最後,依次輸出每一個桶裏面的數字,且每一個桶中的數字從小到大輸出,這  樣就獲得全部數字排好序的一個序列了。

假設有n個數字,有m個桶,若是數字是平均分佈的,則每一個桶裏面平均有n/m個數字。若是

對每一個桶中的數字採用快速排序,那麼整個算法的複雜度是

O(n   +   m   *   n/m*log(n/m))   =   O(n   +   nlogn   –   nlogm)

從上式看出,當m接近n的時候,桶排序複雜度接近O(n)

固然,以上覆雜度的計算是基於輸入的n個數字是平均分佈這個假設的。這個假設是很強的  ,實際應用中效果並無這麼好。若是全部的數字都落在同一個桶中,那就退化成通常的排序了。

前面說的幾大排序算法 ,大部分時間複雜度都是O(n2),也有部分排序算法時間複雜度是O(nlogn)。而桶式排序卻能實現O(n)的時間複雜度。但桶排序的缺點是:

1)首先是空間複雜度比較高,須要的額外開銷大。排序有兩個數組的空間開銷,一個存放待排序數組,一個就是所謂的桶,好比待排序值是從0到m-1,那就須要m個桶,這個桶數組就要至少m個空間。

2)其次待排序的元素都要在必定的範圍內等等。

 代碼實現:

複製代碼
int maxbit(int data[], int n) //輔助函數,求數據的最大位數
{
    int d = 1; //保存最大的位數
    int p = 10;
    for(int i = 0; i < n; ++i)
    {
        while(data[i] >= p)
        {
            p *= 10;
            ++d;
        }
    }
    return d;
}
void radixsort(int data[], int n) //基數排序
{
    int d = maxbit(data, n);
    int *tmp = newint[n];
    int *count = newint[10]; //計數器
    int i, j, k;
    int radix = 1;
    for(i = 1; i <= d; i++) //進行d次排序
    {
        for(j = 0; j < 10; j++)
            count[j] = 0; //每次分配前清空計數器
        for(j = 0; j < n; j++)
        {
            k = (data[j] / radix) % 10; //統計每一個桶中的記錄數
            count[k]++;
        }
        for(j = 1; j < 10; j++)
            count[j] = count[j - 1] + count[j]; //將tmp中的位置依次分配給每一個桶
        for(j = n - 1; j >= 0; j--) //將全部桶中記錄依次收集到tmp中
        {
            k = (data[j] / radix) % 10;
            tmp[count[k] - 1] = data[j];
            count[k]--;
        }
        for(j = 0; j < n; j++) //將臨時數組的內容複製到data中
            data[j] = tmp[j];
        radix = radix * 10;
    }
    delete[]tmp;
    delete[]count;
}
複製代碼
相關文章
相關標籤/搜索