蠻力算法: 選擇排序 冒泡排序(詳解)

蠻力法:

蠻力法(brute force)是一種簡單直接地解決問題的方法,常常直接基於問題的描述和所涉及的概念定義。
雖然巧妙而高效的算法很少來自於蠻力法,但它還是具有重要地位。因爲它能解決幾乎任何問題,如果要解決的問題的規模不大,或者對性能沒有很高的要求,那麼花大工夫涉及複雜的算法顯然是不值得的。
下面就來介紹一下2大蠻力排序算法,然後是它們的分析。

框架介紹:

在介紹算法之前,有必要介紹一些以後會用到的方法。
使用前2個方法,我們就可以生成隨機數組,然後方便地判斷數列是否被正確排序了,以此驗證排序算法的正確性
第3個方法從標準輸入中讀取數據(通過重定向),進行大規模排序,以此比較不同算法的性能

    /**
     * 生成一個長度爲0~600的數組,每個元素的值爲0~99999的整數。
     * 
     * @return
     */
    public static Integer[] randomArray() {
        Integer[] r = new Integer[(int) (600 * Math.random())];
        for (int i = 0; i < r.length; i++)
            r[i] = (int) (99999 * Math.random());
        return r;
    }

    /**
     * 返回一個數組是否是有序的。
     * @param r
     * @return
     */
    public static boolean isSorted(Integer[] r) {
        for (int i = 1; i < r.length; i++)
            if (r[i].compareTo(r[i - 1]) < 0)
                return false;
        return true;
    }

    /**
     * 從標準輸入中讀取1000000個整數的數組。
     * @return
     */
    public static Integer[] arrayIn(){
        Scanner in = new Scanner(System.in);
        Integer[] r = new Integer[1000000];
        for(int i=0;i<1000000;i++)
            r[i] = in.nextInt();
        return r;
    }

選擇排序:

選擇排序開始的時候,我們掃描整個列表,找到它的最小元素,然後和第一個元素交換(如果第一個元素就是最小元素,那麼它就和自己交換。)。再次,在剩下的元素中找到最小元素,將它和數組的第二個元素交換位置,以此往復,直到整個數組有序。這種算法叫做選擇排序,因爲它每次都選擇剩餘元素之中最小的元素放在正確位置

    public static void selection(Integer[] r) {
        int N = r.length;
        for (int i = 0; i < N - 1; i++) {
            int min = i;//已知最小元素的索引
            for (int j = i + 1; j < N; j++)
                if (r[min] > r[j])//如果找到更小的元素,更新索引
                    min = j;
            int temp = r[i];//交換位置
            r[i] = r[min];
            r[min] = temp;
        }
    }

算法分析:
對於長度爲N的數組,選擇排序需要大約(N^2-N)次比較和N次交換。
選擇排序是一種容易理解和實現的簡單排序算法:
1. 它的運行時間和輸入無關: 也就是說,一個有序的數組和一個隨機排序的數組所用的排序時間是相同的!對於任何輸入來說,選擇排序都是一個O(N^2)的算法。
2. 數據移動是線性的: 鍵的交換次數僅爲O(N), 準確的是說n-1此,這個特點使得選擇排序優於很多其他算法。

冒泡排序:

另一個非常直觀的方法是冒泡排序,它比較表中的相鄰元素,如果它們是逆序的話,就交換它們的位置。重複多次之後,最大的元素就“沉到”列表最後一個位置。第二遍操作將第二大的元素“沉下去”。第n-1遍之後,該列表就排好序了。

    public static void bubble(Integer[] r) {
        int N = r.length;
        for (int i = 0; i < N - 1; i++) //第i遍,每次沉下一個最大的元素
            for (int j = 0; j < N - 1 - i; j++) //掃描數組
                if (r[j] > r[j + 1]) {//如果逆序就交換
                    int temp = r[j];
                    r[j] = r[j + 1];
                    r[j + 1] = temp;
                }
    }

算法分析:
對於大小爲N的數組,冒泡排序的比較次數和選擇排序一樣大約爲(N^2)/2, 但它的交換次數取決於特定的輸入。最壞情況下就是遇到降序數組,這時交換次數和比較次數一樣大約是(N^2)/2。根據冒泡排序的特點,我們可以提出下面的改良方案。

改良方案:
如果對列表比較一遍之後沒有交換元素的位置,那麼這個表已經有序,我們就可以停止這個算法了。
雖然對於某些輸入,這個版本的冒泡排序可以比較快,但是最壞情況平均情況下,它依然是個時間複雜度爲O(N^2)的算法。
但不管怎麼樣,冒泡排序除了名字好聽之外,也就並沒有什麼優點了。它不是一個好的選擇。

總結:

main函數代碼如下,循環生成隨機整數數組,進行排序,然後驗證。

    public static void main(String[] args) {
        while (true) {
            Integer[] r = randomArray();
            //selection(r);
            //bubble(r);
            for (int x : r)
                System.out.print(x + " ");
            System.out.println("\n" + isSorted(r) + "\t" + r.length);
            if(!isSorted(r))    break;
        }
    }

對於隨機排序的無重複主鍵的數組,它們的運行時間是平方級別的O(N^2)

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