1. 程式人生 > 其它 >排序—時間複雜度為O(n2)的三種排序演算法

排序—時間複雜度為O(n2)的三種排序演算法

技術標籤:演算法

轉發:https://www.cnblogs.com/zpchya/p/10757878.html

排序—時間複雜度為O(n2)的三種排序演算法

1 如何評價、分析一個排序演算法?

很多語言、資料庫都已經封裝了關於排序演算法的實現程式碼。所以我們學習排序演算法目的更多的不是為了去實現這些程式碼,而是靈活的應用這些演算法和解決更為複雜的問題,所以更重要的是學會如何評價、分析一個排序演算法並在合適的場景下正確使用。

分析一個排序演算法,主要從以下3個方面入手:

1.1 排序演算法的執行效率

1)最好情況、最壞情況和平均情況時間複雜度

待排序資料的有序度對排序演算法的執行效率有很大影響,所以分析時要區分這三種時間複雜度。除了時間複雜度分析,還要知道最好、最壞情況複雜度對應的要排序的原始資料是什麼樣的。

2)時間複雜度的係數、常數和低階

時間複雜度反映的是演算法執行時間隨資料規模變大的一個增長趨勢,平時分析時往往忽略係數、常數和低階。但如果我們排序的資料規模很小,在對同一階時間複雜度的排序演算法比較時,就要把它們考慮進來。

3)比較次數和交換(移動)次數

內排序演算法中,主要進行比較和交換(移動)兩項操作,所以高效的內排序演算法應該具有儘可能少的比較次數和交換次數。

1.2 排序演算法的記憶體消耗

也就是分析演算法的空間複雜度。這裡還有一個概念—原地排序,指的是空間複雜度為O(1)的排序演算法。

1.3 穩定性

如果待排序的序列中存在值相等的元素,經過排序之後,相等元素之間原有的先後順序不變,那麼這種排序演算法叫做穩定的排序演算法;如果前後順序發生變化,那麼對應的排序演算法就是不穩定的排序演算法。

在實際的排序應用中,往往不是對單一關鍵值進行排序,而是要求排序結果對所有的關鍵值都有序。所以,穩定的排序演算法往往適用場景更廣。

2 三種時間複雜度為O(n2)的排序演算法

2.1 氣泡排序

2.1.1 原理

兩兩比較相鄰元素是否有序,如果逆序則交換兩個元素,直到沒有逆序的資料元素為止。每次冒泡都會至少讓一個元素移動到它應該在的位置。

2.1.2 實現

複製程式碼

void BubbleSort(int *pData, int n)    //氣泡排序
{
    int temp = 0;
    bool orderlyFlag = false;    //序列是否有序標誌

    for (int i = 0; i < n && !orderlyFlag; ++i)    //執行n次冒泡
    {
        orderlyFlag = true;
        for (int j = 0; j < n - 1 - i; ++j)    //注意迴圈終止條件
        {
            if (pData[j] > pData[j + 1])    //逆序
            {
                orderlyFlag = false;    
                temp = pData[j];
                pData[j] = pData[j + 1];
                pData[j + 1] = temp;
            }
        }
    }
}

複製程式碼

測試結果

2.1.3 演算法分析

逆序度舉個例子:

標準列是1 2 3 4 5,那麼 5 4 3 2 1 的逆序數演算法:

看第二個,4之前有一個5,在標準列中5在4的後面,所以記1個。

類似的,第三個 3 之前有 4 5 都是在標準列中3的後面,所以記2個。

同樣的,2 之前有3個,1之前有4個,將這些數加起來就是逆序數=1+2+3+4=10。

1)時間複雜度

最好情況時間複雜度:當待排序列已有序時,只需一次冒泡即可。時間複雜度為O(n);

最壞情況時間複雜度:當待排序列完全逆序時,需要n次冒泡。時間複雜度為O(n2);

平均情況時間複雜度:當待排序列完全逆序時,逆序度為n * (n - 1) / 2。只有當交換逆序對時才會才會使得有序,取中間逆序度n * (n - 1) / 4,那麼就要進行n * (n - 1) /4次交換,而比較的次數大於交換次數,所以平均情況時間複雜度為O(n2)。

2)空間複雜度

只借助了一個臨時變數temp,所以空間複雜度為O(1)。

3)穩定性

該演算法中只有交換操作會改變資料元素的順序,只要我們在資料元素值相等時不交換資料元素,那麼演算法就是穩定的。

4)比較和交換的次數

交換操作的執行次數與逆序度相等,比較操作的執行次數大於等於逆序度小於等於n * (n - 1) / 2。

2.2 插入排序

2.2.1 原理

將待排序序列分為已排序區間和未排序區間,開始時已排序區間只有一個數據元素也就是序列的第一個元素,將未排序區間中的資料元素插入已排序區間中同時保持已排序區間的有序,直到未排序區間沒有資料元素。

2.2.2 實現

複製程式碼

void InsertSort(int *pData, int n)    //插入排序
{
    int temp = 0, i, j;

    for (i = 1; i < n; ++i)    //未排序區間
    {
        if (pData[i] < pData[i - 1])    //逆序
        {
            temp = pData[i];
            for (j = i - 1; pData[j] > temp; --j)    //搬移資料元素
                pData[j + 1] = pData[j];
            pData[j + 1] = temp;    //插入資料
        }
    }
}

複製程式碼

測試結果:

2.2.3 演算法分析

1)時間複雜度

最好情況時間複雜度:當待排序列已有序時,只需遍歷一次即可完成排序。時間複雜度為O(n);

最壞情況時間複雜度:當待排序列完全逆序時,需要進行n-1次資料搬移和插入操作。時間複雜度為O(n2);

平均情況時間複雜度:與冒泡法的分析過程一樣,平均情況時間複雜度為O(n2)。

2)空間複雜度

排序過程中只需要一個臨時變數儲存待插入資料,空間複雜度為O(1)。

3)穩定性

插入排序過程中只有插入操作會改變資料元素的相對位置,只要元素大小比較時相等情況下不進行插入操作,插入排序演算法就是穩定的。

4)比較操作和資料搬移操作執行次數

資料搬移操作執行次數和逆序度相同。比較操作次數大於等於逆序度,小於等於n * (n - 1) / 2。

2.3 選擇排序

2.3.1 原理

選擇排序的原理類似於插入排序都分為已排序區間和未排序區間,選擇排序的已排序區間初始大小為零,每次從未排序區間取關鍵值最大(或最小)的資料元素放在已排序區間的後一個位置,直到未排序區間沒有資料元素則完成排序。

2.3.2 實現

複製程式碼

void SelectSort(int *pData, int n)    //選擇排序
{
    int i, j, min, temp;

    for (i = 0; i < n; ++i)    //未排序區間
    {
        min = i;    //最小值下標
        for (j = i + 1; j < n; ++j)
        {
            if (pData[min] > pData[j])    //逆序
                min = j;    //儲存當前較小值下標
        }
        if (i != min)    //如果不是最小值,交換元素
        {
            temp = pData[i];
            pData[i] = pData[min];
            pData[min] = temp;
        }
    }
}

複製程式碼

測試結果:

2.3.3 演算法分析

1)時間複雜度

不管是已有序序列還是完全逆序序列,都要進行n次遍歷無序區間操作,時間複雜度為O(n2)。

2)空間複雜度

排序過程中只需要儲存每次遍歷無序區間最小值的下標和第i個元素的數值,所以空間複雜度為O(1)。

3)穩定性

選擇排序演算法中改變資料元素相對位置的操作為交換操作,當第i次中第i個數據元素不為當前無序區間最小值時則和最小值交換資料元素。當有重複元素時,就有可能發生相對位置改變。例如5,3,4,5,1第一次選擇操作後為1,3,4,5,5,此時兩個5的相對位置已經改變。所以選擇排序演算法不是穩定的。

4)比較操作和交換操作的執行次數

比較操作執行次數為n * (n - 1) / 2,交換操作執行次數小於等於n-1。

2.4 三種演算法之間的比較

1)一般待排序列長度n較小時,我們選擇這三種排序演算法;

2)當排序要求穩定時,一般選擇插入排序,因為相同的情況下,移動資料比交換資料執行速度快;

3)當資料元素資訊量較大時,可以考慮用選擇排序,因為它交換操作執行次數最少。