java經常使用7種排序算法-選擇、歸併...

/**
 * 一、冒泡排序
 * <p>
 * 原理:<p>循環n-1次,每次循環找到當次循環最大/最小元素</p>
 * 實現:<p>外層控制循環次數(n-1),內層對相鄰元素進行比較交換</p>
 * 複雜度:{[@code](https://my.oschina.net/codeo) O(n^2)}
 */
private static void bubbleSort(int[] arr) {
    //外層控制循環次數
    for (int m = 0; m < arr.length - 1; m++) {

        //內層比較交換
        for (int n = 0; n < arr.length - 1 - m; n++) {
            if (arr[n] > arr[n + 1]) {
                int tmp = arr[n];
                arr[n] = arr[n + 1];
                arr[n + 1] = tmp;
            }
        }
    }
}

/**
 * 二、選擇排序
 * <p>
 * 原理:<p>循環n次,內部循環每次找到當前循環元素範圍內最大/最小下標,如發生改變,則交換數值</p>
 * 實現:<p>外層控制循環次數(n-1),內層對相鄰元素進行比較、記錄下標、交換數值</p>
 * 優缺點:對比冒泡排序,少了每次對比後的交互,只在每次外層循環後交互,優於冒泡
 * 複雜度:{[@code](https://my.oschina.net/codeo) O(n^2)}
 */
public static void selectSort(int[] arr) {
    //外層控制次數
    for (int i = 0; i < arr.length; i++) {
        int min_index = i;
        //內層控制元素範圍並實現對比、記錄、交換
        for (int j = i + 1; j < arr.length; j++) {
            if (arr[j] < arr[min_index]) {
                min_index = j;
            }
        }
        //若是最大/小index發生改變,則交換
        if (i != min_index) {
            int temp = arr[i];
            arr[i] = arr[min_index];
            arr[min_index] = temp;
        }
    }
}

/**
 * 三、插入排序
 * <p>
 * 原理:<p>依次將原數組每一個元素與該元素以前的元素對比,根據排序規則插入到合適位置</p>
 * 實現:<p>外層循環控制對哪一個下標的元素進行處理,內部循環將次元素與其以前的元素進行對比,找到正確的位置</p>
 * 優缺點:在數組元素隨機排列的狀況下,插入排序優於冒泡和選擇
 * 複雜度:{[@code](https://my.oschina.net/codeo) O(n^2)}
 */
private static void insertSort(int[] arr) {
    for (int i = 1; i < arr.length; i++) {
        //將當前元素與其以前的元素進行對比、交換
        for (int j = i; j > 0; j--) {
            if (arr[j] < arr[j - 1]) {
                //交換
                int temp = arr[j];
                arr[j] = arr[j - 1];
                arr[j - 1] = temp;
            } else {
                break;
            }
        }
    }
}

/**
 * 四、希爾排序
 * <p>
 * 原理:
 * <p>
 * 希爾排序也是一種插入排序,它將數組以增量進行增量組,而後分別進行插入排序,不斷縮小增量,直至增量爲1,數組即排序完成,建議增量爲{n/2,(n/2)/2...1},稱爲希爾增量
 * </p>
 * 實現:<p>最外層循環控制增量,中層負責對當前增量下的元素進行循環處理,內層使用插入排序進行排序</p>
 * 優缺點:在數組元素隨機排列的狀況下,插入排序優於冒泡和選擇
 * 複雜度:在{n/2,(n/2)/2...1}增量序列下爲{[@code](https://my.oschina.net/codeo) O(n^2)|,通過優化後能夠達到{[@code](https://my.oschina.net/codeo) O(n^2/3)}
 */
public static void shellSort(int[] arr) {

    //外層控制增量
    for (int add = arr.length / 2; add > 0; add /= 2) {
        //從增量大小處開始對每組進行排序操做,由於在增量處恰好每一組都出現了一個元素
        for (int i = add; i < arr.length; i++) {
            //插入排序操做,與該組該元素以前元素進行對比、交換
            for (int j = i; j > 0; j -= add) {
                //判斷的時候注意j-add>=0,而不是>0,這樣才能確保第0個元素也能被排序進來
                if (j - add >= 0 && arr[j] < arr[j - add]) {
                    //交換
                    int temp = arr[j];
                    arr[j] = arr[j - add];
                    arr[j - add] = temp;
                } else {
                    break;
                }
            }
        }
    }
}

/**
 * 五、歸併排序
 * <p>
 * 原理:<p>將原數組一直二分下去,直到不能再分,對分離的每個部分,從新組合起來,組合的時候對相鄰部分按照順序依次排序(藉助臨時數組重組後拷貝)</p>
 * 實現:<p>使用遞歸實現,在對相鄰節點進行合併的時候,藉助臨時數組進行存儲和拷貝</p>
 * 優缺點:利用徹底二叉樹,最好,最壞,平均時間複雜度均爲O(nlogn)
 * 複雜度:{@code O(nlogn)}
 */
public static void mergeSort(int[] arr) {
    //臨時數組,避免遞歸中頻繁開闢空間
    int[] temp = new int[arr.length];
    doMergeSort(arr, 0, arr.length - 1, temp);


}

/**
 * @param arr   排序數組
 * @param left  最左邊index
 * @param right 最右邊index
 * @param temp  臨時數組
 */
private static void doMergeSort(int[] arr, int left, int right, int[] temp) {
    //當元素不能再分時中止
    if (left < right) {
        int mid = (left + right) / 2;
        doMergeSort(arr, left, mid, temp);
        doMergeSort(arr, mid + 1, right, temp);

        int left_start_index = left;
        int rigth_start_index = mid + 1;
        int temp_index = 0;

        //先對比在兩邊數組都有數據的時候
        while (left_start_index <= mid && rigth_start_index <= right) {
            if (arr[left_start_index] < arr[rigth_start_index]) {
                temp[temp_index++] = arr[left_start_index++];
            } else {
                temp[temp_index++] = arr[rigth_start_index++];
            }
        }
        //考慮左還遺留元素
        while (left_start_index <= mid) {
            temp[temp_index++] = arr[left_start_index++];
        }

        //考慮右還遺留元素
        while (rigth_start_index <= right) {
            temp[temp_index++] = arr[rigth_start_index++];
        }

        //將臨時數組元素拷貝進原數組
        int copy_index = 0;
        int arr_index = left;
        while (copy_index < right - left + 1) {
            arr[arr_index++] = temp[copy_index++];
        }

    }

}

/**
 * 六、快速排序-三數取中法
 * <p>
 * 原理:<p>首先對原數組的首中(樞紐)尾三個元素進行排序交換,而後將中值移動到緊鄰尾元素的位置,對中間剩餘的非首中尾元素分別從前(記下標爲i)找到大於樞紐,尾(記下標爲j)找到
 * 小於樞紐的值,若是配對成功則交換,當i>=j時,中止匹配,再將樞紐值與i(只可i不可j)值交換,再將該數組以i(此時爲樞紐值)拆分爲兩個數組,遞歸進行此操做,直至排序完成
 * </p>
 * 實現:<p>先取得前、中、尾三數,再排序並把中移到緊鄰尾的位置,將剩餘元素以樞紐值劃分配對交換,還原樞紐值,遞歸</p>
 * 複雜度:{@code O(nlogn)}
 */
private static void quickSort(int[] arr) {
    doQuickSort(arr, 0, arr.length - 1);
}


/**
 * @param arr   排序數組
 * @param left  左元素下標
 * @param right 右元素下標
 */
private static void doQuickSort(int[] arr, int left, int right) {
    if (left < right) {
        //取得首、中、尾並調整順序
        int mid = (left + right) / 2;
        //左兩數
        if (arr[left] > arr[mid]) {
            int temp = arr[left];
            arr[left] = arr[mid];
            arr[mid] = temp;
        }
        //右兩數
        if (arr[mid] > arr[right]) {
            int temp = arr[mid];
            arr[mid] = arr[right];
            arr[right] = temp;
        }
        //左兩數
        if (arr[left] > arr[mid]) {
            int temp = arr[left];
            arr[left] = arr[mid];
            arr[mid] = temp;
        }

        //將中值放到緊鄰尾元素處
        int temp = arr[right - 1];
        arr[right - 1] = arr[mid];
        arr[mid] = temp;


        //真正的排序操做,若是left與right之間元素個數大於2個時才須要作拆分排序,其餘狀況在上面構建樞紐的時候就已經排序好了
        if (right - left > 2) {
            int flag = arr[right - 1];
            int left_index = left + 1;
            int right_index = right - 2;
            while (left_index <= right_index) {
                //大於樞紐值
                if (arr[left_index] > flag) {
                    if (arr[right_index] < flag) {
                        int temp1 = arr[left_index];
                        arr[left_index] = arr[right_index];
                        arr[right_index] = temp1;
                        left_index++;
                        right_index--;
                    } else {
                        right_index--;
                    }
                } else {
                    left_index++;
                }
            }

            //把樞紐值和left_index處值交換
            int temp2 = arr[left_index];
            arr[left_index] = flag;
            arr[right - 1] = temp2;

            //將兩邊的數組繼遞歸處理
            doQuickSort(arr, left, left_index - 1);
            doQuickSort(arr, left_index + 1, right);

        }
    }
}

/**
 * 七、堆排序
 * <p>
 * 原理:<p>將數組構建爲大頂堆或小頂堆,而後依次將頂元素存於數組,直至排序完成</p>
 * 實現:<p>構建大/小頂堆,交換元素</p>
 * 複雜度:{@code O(nlogn)}
 */
public static void heapSort(int[] arr) {
    //構建大頂堆
    for (int i = arr.length / 2 - 1; i >= 0; i--) {
        build(arr, i, arr.length);
    }
    //交換元素
    for (int j = arr.length - 1; j > 0; j--) {
        //交換
        int temp = arr[0];
        arr[0] = arr[j];
        arr[j] = temp;

        build(arr, 0, j);
    }

}

/**
 * 調整大頂堆(僅是調整過程,創建在大頂堆已構建的基礎上)
 *
 * @param arr 數組
 * @param i   第一個非葉子節點下標
 */
public static void build(int[] arr, int i, int length) {
    int temp = arr[i];
    for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
        if (k + 1 < length && arr[k] < arr[k + 1]) {
            k++;
        }
        if (arr[k] > temp) {
            arr[i] = arr[k];
            i = k;
        } else {
            break;
        }
    }
    arr[i] = temp;
}
相關文章
相關標籤/搜索