冒泡排序及其優化

冒泡排序

  • 冒泡排序(Bubble Sort),一種交換排序,兩兩比較相鄰記錄的關鍵字,如果反序則交換,直到沒有反序的記錄爲止。

基本思想

  • 基本思想:從無序序列頭部開始,進行兩兩比較,根據大小交換位置,直到最後將最大(小)的數據元素交換到了無序隊列的隊尾,從而成爲有序序列的一部分;下一次繼續這個過程,直到所有數據元素都排好序。
  • 算法核心:每次通過兩兩比較交換位置,選出剩餘無序序列裏最大(小)的數據元素放到隊尾。
  • 運行過程:
    1. 比較相鄰元元素,第一個比第二個大(小),交換。
    2. 從序列頭到尾做同樣的工作,這樣序列中最大(小)的元素被交換到最後一位。
    3. 除去已經選出的元素,對剩下的元素重複以上的步驟。
    4. 持續對每次越來越少的元素(無序)重複上面的步驟,直到沒有任何一對數字需要比較,則序列最終有序。
  • 示例

算法實現(核心代碼)

代碼部分使用了Utils類,定義在這篇文章中有說明。
核心代碼:

//C++實現
void bubbleSort(int* A, int n){
    if(A==NULL || n==0){
        return;
    }
    for(int i=n-1; i>=0; i--){
        for(int j=0; j<i; j++){
            if(A[j]>A[j+1]){
                Utils::swap(A, j, j+1);
            }
        }
    }
}

算法優化和變種

  • 設置交換標誌變量flag

試想一下,如果待排序的序列是[1,2,3,4,5,6,7,9,8],也就是倒數第一和倒數第二個關鍵字需要交換,別的都已經是正常順序,當i=1時,交換了8和9,序列已經有序,但是算法仍然將i=2~9以及每個循環中的j循環都執行一遍,雖然沒有交換數據,但這種大量的比較也是多餘的,優化的方法是當一趟冒泡中並沒有發生交換時,說明序列已經時有序的了,這時候便可以停止運行,所以改進代碼,增加一個flag變量用來標記是否有交換的發生。C++實現如下:

void bubbleSort(int* A, int n){
    if(A==NULL || n==0){
        return;
    }
    bool flag = true;  //定義標誌變量flag
    for(int i=n-1; i>=0 && flag; i--){    
        flag = false;  //循環開始置爲false
        for(int j=0; j<i; j++){
            if(A[j]>A[j+1]){
                Utils::swap(A, j, j+1);
                flag = true;  //發生交換置爲true
            }
        }
    }
}
  • 雞尾酒排序

雞尾酒排序又叫定向冒泡排序,攪拌排序、來回排序等,是冒泡排序的一種變形。此算法與冒泡排序的不同處在於排序時是以雙向在序列中進行排序。

雞尾酒排序在於排序過程是先從低到高,然後從高到低;而冒泡排序則僅從低到高去比較序列裏的每個元素。它可以得到比冒泡排序稍微好一點的效能,原因是冒泡排序只從一個方向進行比對(由低到高),每次循環只移動一個項目。

以序列(2,3,4,5,1)爲例,雞尾酒排序只需要從低到高,然後從高到低就可以完成排序,但如果使用冒泡排序則需要四次。

但是在亂數序列的狀態下,雞尾酒排序與冒泡排序的效率都很差勁。

//C++實現
void cocktailSort(int* A, int n){
    int j, left = 0, right = n-1;
    while(left<right){
        for(j=left; j<right; j++){
            if(A[j]>A[j+1]){
                Utils::swap(A, j, j+1);
            }
        }
        right--;
        for(j=right; j>left; j--){
            if(A[j-1]>A[j]){
                Utils::swap(A, j-1, j);
            }
        }
        left++;
    }
}

性能分析

  1. 時間複雜度

    在設置標誌變量之後:

    當原始序列“正序”排列時,冒泡排序總的比較次數爲n-1,移動次數爲0,也就是說冒泡排序在最好情況下的時間複雜度爲O(n);

    當原始序列“逆序”排序時,冒泡排序總的比較次數爲n(n-1)/2,移動次數爲3n(n-1)/2次,所以冒泡排序在最壞情況下的時間複雜度爲O(n^2);

    當原始序列雜亂無序時,冒泡排序的平均時間複雜度爲O(n^2)。

  2. 空間複雜度

    冒泡排序排序過程中需要一個臨時變量進行兩兩交換,所需要的額外空間爲1,因此空間複雜度爲O(1)。

  3. 穩定性

    冒泡排序在排序過程中,元素兩兩交換時,相同元素的前後順序並沒有改變,所以冒泡排序是一種穩定排序算法。

附:完整實現和測試

#include <iostream>
#include "Utils.h"
using namespace std;

class BubbleSort{
    public:
        void bubbleSort(int* A, int n){
            if(A==NULL || n==0){
                return;
            }
            bool flag = true;
            for(int i=n-1; i>=0 && flag; i--){
                flag = false;
                for(int j=0; j<i; j++){
                    if(A[j]>A[j+1]){
                        Utils::swap(A, j, j+1);
                        flag = true;
                    }
                }
            }
        }

        void cocktailSort(int* A, int n){
            int j, left = 0, right = n-1;
            while(left<right){
                for(j=left; j<right; j++){
                    if(A[j]>A[j+1]){
                        Utils::swap(A, j, j+1);
                    }
                }
                right--;
                for(j=right; j>left; j--){
                    if(A[j-1]>A[j]){
                        Utils::swap(A, j-1, j);
                    }
                }
                left++;
            }
        }
};


int main(int argc, char const *argv[])
{
    int n = 10;
    int range = 100;
    int *arr = Utils::generateArray(n,range);
    Utils::printArray(arr, n);
    BubbleSort bubble = BubbleSort();
    // bubble.bubbleSort(arr, n);
    bubble.cocktailSort(arr, n);
    Utils::printArray(arr, n);
    return 0;
}


參考鏈接:

https://blog.csdn.net/guoweimelon/article/details/50902597

https://zh.wikipedia.org/zh/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章