1. 程式人生 > >八大排序以及其時間複雜度

八大排序以及其時間複雜度

排序演算法可以分為內部排序和外部排序,內部排序是資料記錄在記憶體中進行排序,而外部排序是因排序的資料很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存。

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

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

演算法一:插入排序

插入排序示意圖

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

演算法步驟

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

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

程式碼實現:

複製程式碼

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;
    }
}

複製程式碼

演算法二:希爾排序

希爾排序示意圖

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

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

  • 插入排序在對幾乎已經排好序的資料操作時, 效率高, 即可以達到線性排序的效率
  • 但插入排序一般來說是低效的, 因為插入排序每次只能將資料移動一位

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

演算法步驟

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;
}

複製程式碼

 總結

各種排序的穩定性,時間複雜度、空間複雜度、穩定性總結如下圖:

相關推薦

八大排序及其時間複雜

排序演算法可以分為內部排序和外部排序,內部排序是資料記錄在記憶體中進行排序,而外部排序是因排序的資料很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存。 常見的內部排序演算法有:插入排序、希爾排序、選擇排序、氣泡排序、歸併排序、快速排序、堆排序、基數排序等。 本文

幾種排序及其時間複雜 總結

氣泡排序方法是最簡單的排序方法。這種方法的基本思想是,將待排序的元素看作是豎著排列的“氣泡”,較小的元素比較輕,從而要往上浮。在氣泡排序演算法中我們要對這個“氣泡”序列處理若干遍。所謂一遍處理,就是自底向上檢查一遍這個序列,並時刻注意兩個相鄰的元素的順序是否正確。如果發現兩個相鄰元素的順序不對,即“輕”的元素

【整理】常見排序演算法及其時間複雜總結

原文出處: 本篇主要整理了氣泡排序,直接插入排序,直接選擇排序,希爾排序,歸併排序,快速排序,堆排序七種常見演算法,是從上面三篇博文中摘抄整理的,非原創。 一、氣泡排序 主要思路是: 通過交換相鄰的兩個數變成小數在前大數在後,這樣每次遍歷後,最大的數就“沉”到最後面了。重複N次即可以使陣列有序。 氣泡

八大排序演算法的時間複雜

排序演算法的穩定性: 假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,ri=rj,且ri在rj之前,而在排序後的序列中,ri仍

斐波那契數列遞迴演算法和非遞迴演算法及其時間複雜分析

1、在學習資料結構這門課的過程中,發現斐波那契數列的遞迴演算法以及非遞迴演算法,以及其時間複雜度分析是一個小難點。所以特別總結一下。 斐波那契數列的表示式: Fibonacci數列簡介: F(1)=

排序(箱排序)原理及其時間複雜詳解

排序充斥著我們的生活,比如站隊、排隊買票、考試排名、公司業績排名、將電子郵件按時間排序、QQ 好友列表中的會員紅名靠前,等等。 這裡先舉個例子,通過這個例子讓我們接觸第 1 個演算法。 在某個期末考試中,老師要把大家的分數排序,比如有 5 個學生,分別考 5、9、5、1、6 分(滿分 10 分),從大到小

排序演算法之 插入排序、希爾(shell)排序 及其時間複雜和空間複雜

        有一個已經有序的資料序列,要求在這個已經排好的資料序列中插入一個數,但要求插入後此資料序列仍然有序,這個時候就要用到一種新的排序方法——插入排序插入排序的基本操作就是將一個數據插入到已經排好序的有序資料中,從而得到一個新的、個數加一的有序資料,演算法適用於少

排序演算法之 歸併排序 及其時間複雜和空間複雜

        在排序演算法中快速排序的效率是非常高的,但是還有種排序演算法的效率可以與之媲美,那就是歸併排序;歸併排序和快速排序有那麼點異曲同工之妙,快速排序:是先把陣列粗略的排序成兩個子陣列,然後遞迴再粗略分兩個子陣列,直到子數組裡面只有一個元素,那麼就自然排好序了,可

排序演算法之 基數排序 及其時間複雜和空間複雜

        基數排序(radix sort)屬於“分配式排序”(distribution sort),又稱“桶子法”(bucket sort)或bin sort,顧名思義,它是透過鍵值的部份資訊,將要排序的元素分配至某些“桶”中,藉以達到排序的作用,基數排序法是屬於穩定

2-路歸併排序(C程式碼)及其時間複雜的具體分析

時間複雜度:        這是一個遞推公式(Recurrence),我們需要消去等號右側的T(n),把T(n)寫成n的函式。其實符合一定條件的Recurrence的展開有數學公式可以套,這裡我們略去嚴格的數學證明,只是從直觀上看一下這個遞推公式的結果。當n=1時可以設T(1)=c1,當n>1

排序演算法之 選擇排序 及其時間複雜和空間複雜

        選擇排序(Selection sort)是一種簡單直觀的排序演算法。它的工作原理是每一次從待排序的資料元素中選出最小(或最大)的一個元素,存放在序列的起始位置,直到全部待排序的資料元素排完。 選擇排序是不穩定的排序方法(比如序列[5, 5, 3]第一次就將第

排序演算法之 氣泡排序 及其時間複雜和空間複雜

        氣泡排序(Bubble Sort),是一種電腦科學領域的較簡單的排序演算法。它重複地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重複地進行直到沒有再需要交換,也就是說該數列已經排序完成。這個演算法的名字由來是因

排序演算法之 堆排序 及其時間複雜和空間複雜

        堆排序是由1991年的計算機先驅獎獲得者、斯坦福大學計算機科學系教授羅伯特.弗洛伊德(Robert W.Floyd)和威廉姆斯(J.Williams)在1964年共同發明了的一種排序演算法( Heap Sort );         堆排序(Heapsort

快速排序 及其時間複雜和空間複雜

 快速排序是排序演算法中效率相對較高的,但使用的人卻是比較少,大家一般信手拈來的排序演算法就是氣泡排序。因為氣泡排序主觀,容易理解,而快速排序使用到了遞迴,大家可能就有點不知所措了。 演算法分析         快速排序由C. A. R. Hoare在1962年提出。

排序演算法之 快速排序 及其時間複雜和空間複雜

原文:http://blog.csdn.net/yuzhihui_no1/article/details/44198701 總結: 最好的情況是樞紐元選取得當,每次都能均勻的劃分序列。 時間複雜度O(nlogn)最壞情況是樞紐元為最大或者最小數字,那麼所有數都劃分到一個序

快速排序的兩種方式及其時間複雜

首先快速排序是C.R.A.Hoare於1962年提出的一種劃分交換排序。它採用了一種分治的策略,通常稱其為分治法(Divide-and-ConquerMethod)。 方法一: 該方法的基本思想是

排序演算法之 計數排序 及其時間複雜和空間複雜

       計數排序是一個非基於比較的排序演算法,該演算法於1954年由 Harold H. Seward 提出。它的優勢在於在對一定範圍內的整數排序時,它的複雜度為Ο(n+k)(其中k是整數的範圍),快於任何比較排序演算法。 演算法分析         主要思想:根據

常用排序演算法的時間複雜和空間複雜及特點

一、常用排序演算法的時間複雜度和空間複雜度表格 二、特點 1.歸併排序: (1)n大時好,歸併比較佔用記憶體,記憶體隨n的增大而增大,但卻是效率高且穩定的排序演算法。 (2)歸併排序每次遞迴都要用到一個輔助表,長度與待排序的表長度相同,雖然遞迴次數是O(log2n),但每次

圖解常見排序方法的時間複雜

常見排序方法及其複雜度 幾種複雜的表示方式: O(1): 耗時/耗空間與輸入資料大小無關 O(n): 就代表資料量增大幾倍,耗時也增大幾倍。比如常見的遍歷演算法。 O(n2),就代表資料量增大n倍時,耗時增大n的平方倍,這是比線性更高的時間複雜度。比如氣泡排序、遞迴演算法,就是典型的O

八種排序演算法的時間複雜複雜

https://www.cnblogs.com/dll-ft/p/5861210.html 轉載 1、穩定性 歸併排序、氣泡排序、插入排序。基數排序是穩定的 選擇排序、快速排序、希爾排序、堆排序是不穩定的   2、時間複雜度 最基礎的四個演算法:冒泡、選擇