BubbleSort冒泡排序的理解

【紙上得來終覺淺,絕知此事要躬行】

廢話不多說,網上有關冒泡排序的文章太多了,但是光看或者上來就使用別人最終優化的代碼總會沒有什麼感覺。所以這次一點點通過代碼,自己去感受一下優化的效果,文章是寫給自己以後看的,我是個話癆,寫的不會太專業。但是我還是要寫!要感悟

開整!

先亂寫一個數組,隨便敲了16個元素,太少了沒啥感覺。然後按照冒泡排序的思路去寫了個算法,並添加一些記錄參數供我們打印出來觀察比較用。

private static int[] array = { 5, 2, 6, 17, 8, 39, 10, 11, 22, 13, 15, 9, 56, 1, 22, 27 };

public static void main(String[] args) {
    BubbleSort(array);
}

/**
  * 原始冒泡算法
  */ 
public static void BubbleSort(int[] array) {

    int round=0; //比較輪數
    int count=0; //比較次數
    int lastIndex=array.length-1; //末元素下標
    
    for (int i = 0; i < lastIndex; i++) {
        round++;
        System.out.println("********************   第" + round + "輪      ********************");
        System.out.println("比較元素:" +printSortUnit(array, lastIndex));
        for (int j = 0; j < lastIndex; j++) {
            if(array[j]>array[j+1]){
                swap(array, j, j+1);
            }
            count++;
            System.out.println("第"+count+"次       "+Arrays.toString(array));
        }
    }
}

/**
  * 交換數組元素
  */ 
public static void swap(int[] array, int p, int q) {
    int temp = 0;
    temp = array[p];
    array[p] = array[q];
    array[q] = temp;
}

/**
  * 打印需要比較的所有元素
  */
public static String printSortUnit(int[] array, int end) {
    StringBuffer str = new StringBuffer();
        str.append(array[0]);
        for (int i = 1; i <= end; i++) {
            str.append(","+array[i] );
        }
    return str.toString();
}

複製上面代碼,跑一下,發現跑了15輪,每輪都比較了15次,所以一共15*15=225次比較。

這裏說明一下,1次表示某兩個相鄰元素比較大小,1輪表示這16個元素從左往右比較完一遍。


我們可以發現,每一輪結束後數組從右往左就會依次從大到小排列好元素,所以我們把整個數組分爲兩塊,左邊一塊是未排序完的元素數組,右邊一塊是從最大值開始依次排序的元素數組,並且未排序完的任意元素肯定小於已排序完的任意元素,所以我們可以將右邊已排好序的“大元素”數組在下一輪的排序中剔除掉,只比較左邊的這些元素並繼續從大到小依次剔除即可。

 

第一次優化

很簡單,改動一下j遍歷即可:

/**
  *  冒泡算法第一次優化
  */
public static void BubbleSort1(int[] array) {
    int count=0;
    int round=0;
    int lastIndex = array.length-1;
    for (int i = 0; i < lastIndex; i++) {
        round++;
        System.out.println("********************   第" + round + "輪      ********************");
        System.out.println("比較元素:" +printSortUnit(array, lastIndex-i));
        for (int j = 0; j < lastIndex-i; j++) {
            if(array[j]>array[j+1]){
                swap(array, j, j+1);
            }
            count++;
            System.out.println("第"+count+"次       "+Arrays.toString(array));
        }
    }
}

這次再Run:

還是15輪,但是次數只用了120次!每一輪的比較元素都在一個一個減少,大大減少了比較次數。

 

第二次優化

觀察上面的圖片可以發現在第13輪比較的元素爲“2,1,5,6”還沒有拍好,但是第14輪比較的元素爲“1,2,5”,正好是排好序的,所以不管後面還要比較幾輪都可以提前結束了。所以我們需要設置一個flag,如果某一輪比較元素時沒有任何發生交換,說明這個數組已經排好序了,立刻結束。

代碼如下:

/**
  *  冒泡算法第二次優化
  */
public static void BubbleSort2(int[] array) {
    int count=0;
    int round=0;
    int lastIndex = array.length-1;
    for (int i = 0; i < lastIndex; i++) {
        boolean isOver = true;
        round++;
        System.out.println("********************   第" + round + "輪      ********************");
        System.out.println("比較元素:" +printSortUnit(array, lastIndex-i));
        for (int j = 0; j < lastIndex-i; j++) {
            if(array[j]>array[j+1]){
                swap(array, j, j+1);
                isOver=false;
            }
            count++;
            System.out.println("第"+count+"次       "+Arrays.toString(array));
        }
        if(isOver){
            break;
        }
    }
}

運行結果正好在第14輪後就結束了,應該是數組元素不多的原因吧,所以比第一次優化只少了一輪比較,效果不明顯,但確實是少了,不服氣的小老弟可以多敲幾十個元素再試試。

還能優化嗎?可以!

 

第三次優化

我們還是從第二次優化的打印結果裏找找看:

不難發現,每一輪我們都只是在一個個的去尋找【比較元素】裏的最大值放在右邊,但是如果【比較元素】裏右邊若干個元素已經排好序了,是不是就可以直接剔除比較了?

打個比方,如上圖。第5輪,【比較元素】中17,22很明顯已經是排好序的最大值,所以只需要比較前面十個元素即可,即比較9次即可。第6、7輪也是一樣的問題。通俗點說只要後面N個元素比較後一直不發生交換,就說明這N個元素都是排好序了,且左邊的元素都比這些值小(如果有大的肯定會通過交換推到右面),所以我們批量剔除這N個元素而不是每一輪只剔除一個最大元素。

因此,我們需要記錄每一輪最後交換元素的位置changeIndex,下一輪直接遍歷array[0]到array[changeIndex]即可,直到整輪一次交換都不發生(第二次優化的目的),就表示排序完成。

代碼如下:

/**
 * 冒泡算法第三次優化
 */
public static void BubbleSort3(int[] array) {
    int count = 0;
    int round = 0;
    int lastIndex = array.length - 1;
    int changeIndex = lastIndex;
    for (int i = 0; i < lastIndex; i++) {
        int tempChangeIndex = -1;// 記錄每一輪最後交換元素的下標
        round++;
        System.out.println("********************   第" + round + "輪      ********************");
        System.out.println("比較元素:" + printSortUnit(array, changeIndex));
        for (int j = 0; j < changeIndex; j++) {
            if (array[j] > array[j + 1]) {
                swap(array, j, j + 1);
                tempChangeIndex = j;
            }
            count++;
            System.out.println("第" + count + "次       " + Arrays.toString(array));
        }
        if (tempChangeIndex <= 0) {
            break; // 這裏表示前兩個元素交換或者一次都未交換的情況,均表示排序結束
        } else {
            changeIndex = tempChangeIndex;
        }
    }
}

幾處改動:

1.添加一個變量changeIndex,用來控制每輪j從0循環到changeIndex,並在每輪結束後及時更新,給下一輪使用。

2.添加tempChangeIndex變量,在一輪中的每一次交換後更新,一輪全部結束後把這一輪最後一次交換元素的下標值賦給changeIndex變量。

(注:這裏一定要分清changIndex是上一輪確定的值,tempChangeIndex是這一輪通過比較獲得的值,tempChangeIndex一直小於changeIndex,兩者不能混淆)

3.打印“比較元素”的下標也要改成changeIndex,而不是lastIndex-i。

4.因爲tempChangeIndex自身表示交換元素的下標,當一輪比較結束後tempChangeIndex仍爲“-1”時表示一次未交換,替代了之前的isOver==true(tempChangeIndex>=0表示isOver==false),所以可把isOver刪掉了,tempChangeIndex爲“0”時表示只有前兩個元素交換,所以這裏tempChangIndex爲-1或者0都表示整個數組排序結束。代碼最後的if語句就表示這個意思。

結果:

 

感悟:這一次比第二次優化又少了一輪,一共只比較了97次。反觀最原始的225次確實少了很多,而且這個測試數組只有16個元素,如果是幾萬幾十萬甚至更多的數組元素需要排序的話,效果可想而知。

 

 

————————————————————————————————————————————————————————

算法思想總結

我們根據冒泡算法的原理寫了一個最原始的算法,然後根據結果優化了三次。

原始思想:每次都對N個元素兩兩相鄰進行比較,比較N-1輪,每輪N-1次,排序完成。

第一次優化思想:從次的角度考慮,每一輪都能得到一個元素按順序排在數組右邊,這些排好序的元素不用參與後面的比較,故每一輪結束後剔除右邊一個元素,下一輪只比較剩下的元素即可。

第二次優化思想:從輪的角度考慮,排序不一定要執行完N-1輪,當某一輪結束後未發生任何元素交換就表示已經排好順序,不用繼續比較了。

第三次優化思想:還是從次的角度考慮,是對第一次優化的再優化,第一次優化是一個一個剔除已排好序的元素,而第三次我們通過每輪最後一次交換元素的下標,快速排除已排好序的元素。一輪可剔除一個或多個最大有序元素。

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