排序算法系列——冒泡排序

冒泡排序是交換排序的一種,其思想是從序列頭部開始逐步往後遍歷,每次遍歷比較相鄰兩個元素,如果順序不對則交換,n-1次遍歷之後序列就完成了排序。由於每次遍歷都是把最大的元素一步步讓最後移動,類似於水泡慢慢浮出水面,於是得名冒泡算法。冒泡算法的思想很簡單,實現起來也很容易,但是效率太低,所以即使是小數據量也很少推薦使用冒泡算法,更多的使用直接插入排序。
基本思想
從待排序序列頭部開始循環遍歷n-1次,每次遍歷比較相鄰兩個元素,如果順序不對則交換,也就是說如果你想要從小到大排列,那麼如果相鄰兩個元素比較結果是前面的大於後面的,則交換就可以了,那麼通過一次遍歷最大的元素就到達了序列的尾部,n-1次遍歷之後序列就完成了排序。這裏顯然有可優化的空間,下面會提到。
實現要點
第i次(i=0…n-1)遍歷從0開始比較到n-1-i(n序列長度),因爲每次遍歷都會將未排序部分最大的元素移動到序列尾部,所以序列尾部是有序的,故在之後的遍歷過程中無需繼續比較有序的部分。
算法改進
該算法有兩處可優化的地方:

  • 如果一次遍歷過程中未發生任何交換,即所有相鄰元素的順序都是正確的,則說明整個序列已經完成排序,故無需繼續遍歷。
  • 每次遍歷過程中記錄下最後一次發生遍歷的位置,則在改位置之後的部分已經是有序的,下次遍歷時就可以提前結束。

實驗表明以上兩種改進之後的效率並未有太大的提高,第一種改進效率反而比爲改進的低,第二種改進效率稍微提高一點點。雖然這兩種改進從理論上來看是有一定的優化的,但是測試時使用的序列一般都是隨機的,即在n-1次遍歷之前完成排序以及部分有序的可能性都很小,所以這兩種改進的效果都不是很明顯,可能根本不會發生,反而由於加入了一些邏輯判斷反而導致效率降低。不過如果是真是數據,那麼之前提到的兩種情況還是很可能發生的。

Java實現

package com.vicky.sort;

import java.util.Random;

/**
 * 交換排序:冒泡排序
 * 
 * 時間複雜度:O(n^2)
 * 
 * 空間複雜度:O(1)
 * 
 * 穩定性:穩定
 * 
 * @author Vicky
 * 
 */
public class BubbleSort {

    /**
     * 未改進的冒泡排序
     * 
     * @param <T>
     * @param data
     */
    public static <T extends Comparable<T>> void sort(T[] data) {
        long start = System.nanoTime();
        if (null == data) {
            throw new NullPointerException("data");
        }
        if (data.length == 1) {
            return;
        }
        // n-1趟遍歷
        for (int i = 0; i < data.length - 1; i++) {
            // 每次遍歷從0開始依次比較相鄰元素
            for (int j = 0; j < data.length - 1 - i; j++) {
                // 前面元素>後面元素則交換
                if (data[j].compareTo(data[j + 1]) > 0) {
                    T temp = data[j];
                    data[j] = data[j + 1];
                    data[j + 1] = temp;
                }
            }
        }
        System.out.println("sort, use time:" + (System.nanoTime() - start)
                / 1000000);
    }

    /**
     * 改進後冒泡排序
     * 
     * 改進原理:如果一次遍歷過程未發生交換,則說明序列已經是有序的,故無需再進行遍歷。
     * 
     * @param <T>
     * @param data
     */
    public static <T extends Comparable<T>> void sortImprove(T[] data) {
        long start = System.nanoTime();
        if (null == data) {
            throw new NullPointerException("data");
        }
        if (data.length == 1) {
            return;
        }
        boolean exchange = false;// 記錄一趟遍歷是否發生交換
        // n-1趟遍歷
        for (int i = 0; i < data.length - 1; i++) {
            // 每次遍歷從0開始依次比較相鄰元素
            for (int j = 0; j < data.length - 1 - i; j++) {
                // 前面元素>後面元素則交換
                if (data[j].compareTo(data[j + 1]) > 0) {
                    T temp = data[j];
                    data[j] = data[j + 1];
                    data[j + 1] = temp;
                    exchange = true;
                }
            }
            // 如果本次遍歷未發生交換,則說明序列已是有序的,則無需繼續遍歷
            if (!exchange) {
                return;
            }
        }
        System.out.println("sortImprove1, use time:"
                + (System.nanoTime() - start) / 1000000);
    }

    /**
     * 改進後冒泡排序
     * 
     * 改進原理:在冒泡排序的每趟掃描中,記住最後一次交換髮生的位置lastexchange也能有所幫助。因爲該位置之後的部分已經是有序的(未發生交換,
     * 所以是有序),
     * 故下一趟排序開始的時候,只需處理0到lastexchange部分,lastexchange到n-1是有序區。同時如果未發生交換則退出即可
     * 
     * @param <T>
     * @param data
     */
    public static <T extends Comparable<T>> void sortImprove2(T[] data) {
        long start = System.nanoTime();
        if (null == data) {
            throw new NullPointerException("data");
        }
        if (data.length == 1) {
            return;
        }
        int lastChange = data.length - 1;// 記錄一趟遍歷最後一次發生交換的位置,該位置之後是有序的
        // 上次遍歷發生交換則lastChange>0,繼續遍歷
        while (lastChange > 0) {
            // 本次遍歷從0開始到上次遍歷最後一次交換的位置結束
            int end = lastChange;
            lastChange = 0;
            // 每次遍歷從0開始依次比較相鄰元素
            for (int j = 0; j < end; j++) {
                // 前面元素>後面元素則交換
                if (data[j].compareTo(data[j + 1]) > 0) {
                    T temp = data[j];
                    data[j] = data[j + 1];
                    data[j + 1] = temp;
                    lastChange = j + 1;
                }
            }
        }
        System.out.println("sortImprove2, use time:"
                + (System.nanoTime() - start) / 1000000);
    }

    public static void main(String[] args) {
        // 用於記錄三種冒泡排序的用時
        long[] useTime1 = new long[10];
        long[] useTime2 = new long[10];
        long[] useTime3 = new long[10];
        // 循環測試10次,取均值
        for (int times = 0; times < 10; times++) {
            // 構建10000個元素的序列進行排序
            Random ran = new Random();
            Integer[] data = new Integer[10000];
            for (int i = 0; i < data.length; i++) {
                data[i] = ran.nextInt(10000000);
            }
            // 使用System.arraycopy複製三個數組分別用於排序
            Integer[] data1 = new Integer[data.length];
            Integer[] data2 = new Integer[data.length];
            Integer[] data3 = new Integer[data.length];
            System.arraycopy(data, 0, data1, 0, data.length);
            System.arraycopy(data, 0, data2, 0, data.length);
            System.arraycopy(data, 0, data3, 0, data.length);
            // 分別記錄三種冒泡排序的用時
            long start = System.nanoTime();
            BubbleSort.sort(data1);
            useTime1[times] = (System.nanoTime() - start) / 1000000;
            // SortUtils.printArray(data1);
            start = System.nanoTime();
            BubbleSort.sortImprove(data2);
            useTime2[times] = (System.nanoTime() - start) / 1000000;
            start = System.nanoTime();
            // SortUtils.printArray(data2);
            BubbleSort.sortImprove2(data3);
            useTime3[times] = (System.nanoTime() - start) / 1000000;
            // SortUtils.printArray(data3);
        }
        // 計算用時最大值,最小值,均值
        long[] res1 = SortUtils.countArray(useTime1);
        long[] res2 = SortUtils.countArray(useTime2);
        long[] res3 = SortUtils.countArray(useTime3);
        System.out.println("method\tmax\tmin\tavg\t");
        System.out.println("sort" + "\t" + res1[0] + "\t" + res1[1] + "\t"
                + res1[2]);
        System.out.println("sortImprove" + "\t" + res2[0] + "\t" + res2[1]
                + "\t" + res2[2]);
        System.out.println("sortImprove2" + "\t" + res3[0] + "\t" + res3[1]
                + "\t" + res3[2]);
        // 測試結果,第一種改進方法效率比不改進還差一些,
        // 可能由於出現提前完成排序的可能性較小,每次遍歷加入了過多的賦值以及判斷操作導致效率反而降低
        // 第二種改進方法還是有一些效果的
        // method max min avg
        // sort 1190 1073 1123
        // sortImprove 1258 1097 1146
        // sortImprove2 1205 1056 1099
    }
}

效率分析
(1)時間複雜度
O(n^2)
冒泡排序最好的時間複雜度是O(n),一次遍歷即可,無需交換(第一種改進)。最壞情況需要遍歷n-1次,比較且交換n-1-i次,故時間複雜度是O(n^2)。
(2)空間複雜度
O(1)
從空間來看,它只需要一個元素的輔助空間,用於元素的位置交換O(1)。
(3)穩定性
穩定
排序過程中只有相鄰兩個元素會發生交換,同時爲了減少交換次數相同的元素不會進行交換,所以兩個相同元素的相對位置不會發生改變。

發佈了55 篇原創文章 · 獲贊 35 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章