排序算法上——冒泡排序、插入排序和選擇排序

1. 排序算法?

排序算法應該算是咱們最熟悉的算法了,咱們學的第一個算法,可能就是排序算法,而在實際應用中,排序算法也常常會被用到,其重要做用不言而喻。

經典的排序算法有:冒泡排序、插入排序、選擇排序、歸併排序、快速排序、計數排序、基數排序、桶排序。按照時間複雜度,能夠分爲如下三類。算法

排序算法


2. 如何分析一個排序算法?

2.1. 排序算法的執行效率

  • 最好狀況、最壞狀況、平均狀況時間複雜度。咱們不只要知道一個排序算法的最好狀況、最壞狀況、平均狀況時間複雜度,還要知道它們分別對應的原始數據是什麼樣的。有序度不一樣的數據,對於排序算法的執行時間確定是有影響的。
  • 時間複雜度的係數、常量、低階。時間複雜度反映的是數據規模很是大時的一個增加趨勢,但實際中,咱們面臨的數據多是 10 個、100 個、 1000 個這樣的小數據,所以以前咱們忽略的係數、常量、低階也要考慮進來。
  • 比較次數和交換(或移動)次數。基於比較的排序算法涉及到兩個主要操做,一個是元素比較大小,另外一個是元素交換或移動。

2.2. 排序算法的內存消耗

  • 算法的內存消耗能夠用空間複雜度來衡量,針對排序算法的空間複雜度,咱們引入了一個新的概念,原地排序(Sorted in place),特指空間複雜度爲 $O(1)$ 的排序算法,即指直接在原有數據結構上進行排序,無需額外的內存消耗。

2.3. 排序算法的穩定性

  • 排序算法的穩定性指的是,若是待排序的序列中存在值相等的數據,通過排序以後,相等元素之間原有的前後循序不變。
  • 好比一組數據 2, 9, 3, 4, 8, 3,按照大小排序以後就是 2, 3, 3, 4, 8, 9,若是排序後兩個 3 的順序沒有發生改變,咱們就把這種算法叫做穩定的排序算法,反之就叫做不穩定的排序算法
  • 咱們學習排序的時候通常都是用整數來舉例,但在真正的軟件開發中,待排序的數據每每不是單純的整數,而是一組對象,咱們須要按照對象的某一個鍵值對數據進行排序
  • 假設咱們有 10 萬條訂單數據,要求金額從小到大排序,而且金額相同的訂單按照下單時間從早到晚排序。這時候,穩定排序就發揮了其做用,咱們能夠先按照下單時間對數據進行排序,而後再用穩定排序算法按照訂單金額從新排序

穩定排序算法


3. 冒泡排序(Bubble Sort)?

3.1. 冒泡排序算法實現

  • 冒泡排序只會操做相鄰的兩個數據,將其調整到正確的順序。一次冒泡會讓至少一個數據移動到它應該在的位置,冒泡 n 次,就完成了 n 個數據的排序工做。

冒泡排序

冒泡排序

  • 若某一次冒泡沒有數據移動,則說明數據已經徹底達到有序,不用再繼續執行後續的冒泡操做,針對此咱們能夠再對剛纔的算法進行優化。

冒泡排序

  • 代碼實現
// O(n^2)
void Bubble_Sort(float data[], int n)
{
    int i = 0, j = 0;
    int temp = 0;
    int flag = 0;
    for(i = n-1; i > 0; i--)
    {
        flag = 0;
        for(j = 0; j < i; j++)
        {
            if(data[j+1] < data[j])
            {
                temp = data[j];
                data[j] = data[j+1];
                data[j+1] = temp;
                flag = 1;
            }
        }

        if(!flag)//If no data needs to be exchanged, the sort finishes.
        {
            break;
        }
    }
}

3.2. 冒泡排序算法分析

  • 冒泡排序是一個原地排序算法,只須要常量級的臨時空間。
  • 冒泡排序是一個穩定的排序算法,當元素大小相等時,咱們沒有進行交換。
  • 最好狀況下,數據已是有序的,咱們只須要進行一次冒泡便可,時間複雜度爲 $O(n)$。最壞狀況下,數據恰好是倒序的,咱們須要進行 n 次冒泡,時間複雜度爲 $O(n^2)$。

3.3. 有序度和逆序度

  • 有序度是數組中具備有序關係的元素對的個數。有序元素對定義:a[i] <= a[j], 若是 i < j。

有序度

  • 徹底倒序排列的數組,其有序度爲 0;徹底有序的數組,其有序度爲 $C_n^2 = \frac{n*(n-1)}{2}$,咱們把這種徹底有序的數組叫做滿有序度
  • 逆序度和有序度正好相反,逆序元素對定義:a[i] > a[j], 若是 i < j。
  • 逆序度 = 滿有序度 - 有序度。排序的過程就是一個增長有序度減小逆序度的過程,最後達到滿有序度,排序就完成了。
  • 在冒泡排序中,每進行一次交換,有序度就加 1。無論算法怎麼改進,交換次數老是肯定的,即爲逆序度。
  • 最好狀況下,須要的交換次數爲 0;最壞狀況下,須要的交換次數爲 $\frac{n * (n-1)}{2}$。平均狀況下,須要的交換次數爲 $\frac{n * (n-1)}{4}$,而比較次數確定要比交換次數多,而複雜度的上限是 $O(n^2)$,因此,平均時間複雜度也就是 $O(n^2)$。

4. 插入排序(Insertion Sort)

4.1. 插入排序算法實現

  • 往一個有序的數組插入一個新的元素時,咱們只須要找到新元素正確的位置,就能夠保證插入後的數組依然是有序的。

插入元素

  • 插入排序就是從第一個元素開始,把當前數據的左側看做是有序的,而後將當前元素插入到正確的位置,依次日後進行,直到最後一個元素。
  • 對於不一樣的查找插入點方法(從頭至尾、從尾到頭),元素的比較次數是有區別的,但移動次數是肯定的就等於逆序度
  • 代碼實現
// O(n^2)
void Insertion_Sort(float data[], int n)
{
    int i = 0, j = 0;
    int temp = 0;
    for(i = 1; i < n; i++)
    {
        temp = data[i];
        for(j = i; j > 0; j--)
        {
            if(temp < data[j-1])
            {
                data[j] = data[j-1];
            }
            else// The data ahead has been sorted correctly.
            {
                break;
            }
        }
        data[j] = temp; // insert the data
    }
}

4.2. 插入排序算法分析

  • 插入排序是一個原地排序算法,只須要常量級的臨時空間。
  • 插入排序是一個穩定的排序算法,當元素大小相等時,咱們不進行插入。
  • 最好狀況下,數據已是有序的,從尾到頭進行比較的話,每次咱們只須要進行一次比較便可,時間複雜度爲 $O(n)$。最壞狀況下,數據恰好是倒序的,咱們每次都要在數組的第一個位置插入新數據,有大量的移動操做,時間複雜度爲 $O(n^2)$。
  • 在數組中插入一個元素的平均時間複雜度爲$O(n)$,這裏,咱們須要循環執行 n 次插入操做,因此平均時間複雜度爲 $O(n^2)$。

5. 選擇排序(Selection Sort)

5.1. 選擇排序算法實現

  • 選擇排序就是從第一個元素開始,從當前數據的右側未排序區間中選取一個最小的元素,而後放到左側已排序區間末尾,依次日後進行,直到最後一個元素。

選擇排序

  • 代碼實現
// O(n^2)
void Selection_Sort(float data[], int n)
{
    int i = 0, j = 0, k = 0;
    int temp = 0;
    for(i = 0; i < n-1; i++)
    {
        k = i;
        for(j = i+1; j < n; j++)
        {
            if(data[j] < data[k])
            {
                k = j;
            }
        }
        if(k != i)
        {
            temp = data[i];
            data[i] = data[k];
            data[k] = temp;
        }
    }
}

5.2. 選擇排序算法分析

  • 選擇排序是一個原地排序算法,只須要常量級的臨時空間。
  • 選擇排序的最好狀況時間複雜度、最壞狀況時間複雜度和平均狀況時間複雜度都爲 $O(n^2)$,由於無論數據排列狀況怎樣,都要進行相同次數的比較
  • 選擇排序是一個不穩定的排序算法,由於每次都要從右側未排序區間選擇一個最小值與前面元素交換,這種交換會打破相等元素的原始位置。

6. 爲何插入排序比冒泡排序更受歡迎?

交換排序和插入排序的時間複雜度都爲 $O(n^2)$,也都是原地排序算法,爲何插入排序更受歡迎呢?
  • 前面分析,插入排序的移動次數等於逆序度,冒泡排序的交換次數等於逆序度,但冒泡排序每次交換須要進行三次賦值操做,而插入排序每次移動只須要一次賦值操做,其相應的真實運行時間也會更短。
冒泡排序中數據的交換操做:
if (a[j] > a[j+1]) { // 交換
   int tmp = a[j];
   a[j] = a[j+1];
   a[j+1] = tmp;
   flag = true;
}

插入排序中數據的移動操做:
if (a[j] > value) {
  a[j+1] = a[j];  // 數據移動
} else {
  break;
}

7. 小結

三種排序算法對比

  • 冒泡排序和選擇排序在實際中應用不多,僅僅停留在理論層次便可,選擇排序算法仍是挺有用的,並且其還有很大的優化空間,好比希爾排序。

參考資料-極客時間專欄《數據結構與算法之美》數組

獲取更多精彩,請關注「seniusen」!
seniusen數據結構

相關文章
相關標籤/搜索