Java實現二元選擇排序

Nisus發表於2019-03-03

僅以此篇部落格致敬掘金部落格. 就在昨晚被CSDN傻x般的互動方式折磨的怒火難洩的時候, 百度一起吐槽它, 偶遇一知乎貼吧, 正式集體吐槽CSDN的, 於是乎, 痛快的罵了幾句, 也就是在哪裡看到掘金的字樣, 還有stackoverflow, 鑑於後者外網響應較慢. 於是瞅瞅掘金, 看看是個什麼鬼.
進來一看, 首先, 介面較為簡潔, 不想某些網站就像釣魚網站一樣. 進來後, 發現文章支援Markdown, 大大加分(本尊以為不支援markdown的部落格直接pass得了). 更爽的是, 這裡提供markdown很多快捷鍵, 而且左下角自帶語法提示, 很像印象筆記馬克飛象(馬克飛象私人化太重, 不能很好的分享, 這是我鬱悶的小地方).

廢話說完, 接下來簡單的分享下昨晚研究的二元選擇排序Java程式碼, 作為進駐掘金的第一炮.

程式碼

public static void selectSort2(int arr[]) {
    int i, j, min, max;
    int min_value = 0;
    int max_value = 0;
    int n = arr.length;


    /**搜尋比較**/
    for (i = 0; i <= (n - 1) / 2; i++) {
        // 做不超過n/2趟選擇排序
        // 分別記錄最大和最小關鍵字記錄位置  每次迴圈更新索引, 一般待遍歷區域的首末假設為最大值或最小值位置
        max = i;
        min = i;
        for (j = i + 1; j <= n - 1 - i; j++) {
            if (arr[j] >= arr[max]) { //>= -->保證相等數值中最後面的為最大值, 這樣甩到後面去, 能夠保證順序穩定(升序情況下適用)
                max = j;
                continue;
            }
            if (arr[j] < arr[min]) { //相等數值中最前面的為最小值
                min = j;
            }

        }
        /**資料交換**/
        // 交換的部分最需要注意了, 網上很多部落格, 在這裡其實是有bug的
        // 總體思路是: 最小值放在搜尋取首位, 最大值放在搜尋區末位
        //first:i last:n-1-i min max

        if(i==max){

            max_value = arr[i];
            arr[i] = arr[min];
            arr[min] = arr[n-1-i];
            arr[n-1-i] = max_value;                    
        }else if((n-1-i)==min) {
            min_value = arr[n-1-i];
            arr[n-1-i] = arr[max];
            arr[max] = arr[i];
            arr[i] = min_value;
        }else {
            //首位不和最大重疊且末尾不和最小重疊
            min_value = arr[min];
            arr[min] = arr[i];
            arr[i] = min_value;

            max_value = arr[max];
            arr[max] = arr[n-1-i];
            arr[n-1-i] = max_value;
        }
    }

}複製程式碼

坑點

二元排序主要分兩部分:

  • 搜尋比較, 確定最大, 最小值索引
  • 交換資料

第一部分, 大家一般都沒有問題。問題往往出在第二部分交換資料。很多朋友在這裡沒有細想,導致程式碼是有問題的。
如下面這個程式碼片(來源:八大排序演算法之二元選擇排序):

int maxtmp = 0;
int mintmp = 0;     //注意:這裡不能把a[max],a[min]直接和a[i]和a[n-i-1]調換
maxtmp = a[max];
mintmp = a[min];
a[max] = a[i];
a[min] = a[n-i-1];
a[i] = maxtmp;
a[n-i-1] = mintmp;複製程式碼

通過觀察這段程式碼片, 可知博主實現的降序。如果依據這個程式碼,當imax重合或者n-i-1min重合時, 基本就會出現數值丟失的情況,更不用談排序正確與否了!
舉個例子:

陣列擷取: … (2), 6, (9), 8, 5, (4), …
首位2, 也是min.
首->max: 2, 6, 2, 8, 5, 4
末->min: 4, 6, 2, 8, 5, 4
max->首: 9, 6, 2, 8, 5, 4
min->末: 9, 6, 2, 8, 5, 2
到這一步, 不用看順序, 只需要看數字的值, 已經變了, 多出了一個2! 4卻丟失了!!

測試

通過比較二元選擇排序普通的二元排序,二元選擇排序速度的確有所提升,但是由於增加了程式碼複雜度,提升效果有限!

1. 時間對比

int[] arr = MyUtils.randomArr(100000, 100000, -123); //自定義工具, 生成10萬長度, 範圍[0,100000)的整型陣列, -123是種子值.
long t0 = System.currentTimeMillis(); //獲取開始時間
//selectSort(arr);  //普通選擇排序
//selectSort2(arr); //二元選擇排序
long t1 = System.currentTimeMillis(); //獲取結束時間
System.out.println((t1-t0)+"ms");複製程式碼

結果顯示:
對於10萬長度的整形陣列,

  • 二元選擇排序耗時: 2,208ms
  • 普通選擇排序: 2,620ms

可見, 兩者差異很小。
另外,

  • 當陣列長度增長到100萬時,選擇排序耗時太長,只能殺死它。。。
  • 普通選擇排序中,在搜尋比較的時候是隻更新索引, 找到最小的索引後再交換的思路。如果比較一次交換一次,耗時明顯提高近5倍。

附錄

普通選擇排序——比較一次交換一次

public static void selectSort(int[] arr) {
     int count = 0;
    for (int i = 0; i < arr.length - 1; i++) {
        for (int j = i + 1; j < arr.length; j++) {
            if (arr[i] > arr[j]) {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
             count ++;
        }
    }
    System.out.println(count);
}複製程式碼

普通選擇排序——比較僅更新索引

public static void selectSort3(int[] arr) {
    int min_idx = 0;  //初始化最小值位置
    int temp = 0;
    for (int i = 0; i < arr.length - 1; i++) {
        min_idx = i;
        for (int j = i + 1; j < arr.length; j++) {
            if (arr[min_idx] > arr[j]) {
                min_idx = j;  //先只儲存最小值所在位置, 暫不做交換
            }
            // count ++;
        }
        temp = arr[min_idx];
        arr[min_idx] = arr[i];
        arr[i] = temp;
    }
    // System.out.println(count);
}複製程式碼

相關文章