歸并排序的遞歸寫法
歸并排序的
核心在于如何将數組兩個區間重新排序成一個新的數組
,這裡需要開辟新的空間,它不可以原地排序。
import java.util.Arrays;
public class MergeSort {
private MergeSort() {
}
public static <E extends Comparable<E>> void sort(E[] arr) {
sort(arr, 0, arr.length - 1);
}
// 遞歸函數
private static <E extends Comparable<E>> void sort(E[] arr, int l, int r) {
if (l >= r) return;
int mid = (r - l) / 2 + l;
// int mid2 = (r + l) / 2;
sort(arr, l, mid);
sort(arr, mid + 1, r);
merge(arr, l, mid, r);
}
// 合并區間 arr[l...mid],arr[mid+1...r]
private static <E extends Comparable<E>> void merge(E[] arr, int l, int mid, int r) {
// 複制一份元素組用于排序,Arrays.copyOfRange()是一個閉區間[] 是以需要複制[l,r+1)
E[] temp = Arrays.copyOfRange(arr, l, r + 1);
int i = l, j = mid + 1; // i和j分别指向temp中兩端區間的頭部
// 每次都判斷arr[i]與arr[j]的大小即temp[i-l]和temp[j-l] l...r區間内
for (int k = l; k <= r; k++) {
// i超出中間位置,直接從j中取元素
if (i > mid) {
arr[k] = temp[j - l];
j++;
} else if (j > r) {
arr[k] = temp[i - l];
i++;
} else if (temp[i - l].compareTo(temp[j - l]) <= 0) {
arr[k] = temp[i - l];
i++;
} else {
arr[k] = temp[j - l];
j++;
}
}
}
}
進行第一次優化
調用merge函數合并兩個區間,合并前arr[l…mid],arr[mid+1…r]是兩個有序的區間,此時如果
arr[mid]比arr[mid+1]的時候不需要進行重新排序
,就不需要調用merge函數。是以在遞歸是加上if語句進行條件限制。
// 如果mid>mid+1的情況下執行merge
if (arr[mid].compareTo(arr[mid + 1]) > 0)
merge(arr, l, mid, r);
最優的情況下,當數組越接近有序,歸并排序的速度越快。時間複雜度為O(n)。
使用插入排序優化
如果數組元素個數非常少的話,越大機率是一個近乎有序的數組,使用插入排序進行,插入排序前的常數很小。數組規模小,插入排序速度快于歸并排序。
插入排序對小規模的資料,由于常數低,速度反而快。插入排序的優化在進階語言中不一定可行。意義并不大。
public static <E extends Comparable<E>> void isertionSort(E[] arr, int l, int r) {
for (int i = l; i < r; i++) {
// 進行暫存
E temp = arr[i];
// 實際存儲位置
int j;
for (j = i; j - 1 >= l && temp.compareTo(arr[j - 1]) < 0; j--)
arr[j] = arr[j - 1];
arr[j] = temp;
}
}
public static <E extends Comparable<E>> void sort(E... arr) {
sort(arr, 0, arr.length - 1);
}
// 遞歸函數
private static <E extends Comparable<E>> void sort(E[] arr, int l, int r) {
if (r - l <= 15) {
isertionSort(arr, l, r);
return;
}
int mid = (r - l) / 2 + l;
// int mid2 = (r + l) / 2;
sort(arr, l, mid);
sort(arr, mid + 1, r);
merge(arr, l, mid, r);
}
// 合并區間 arr[l...mid],arr[mid+1...r]
private static <E extends Comparable<E>> void merge(E[] arr, int l, int mid, int r) {
// 複制一份元素組用于排序,Arrays.copyOfRange()是一個閉區間[] 是以需要複制[l,r+1)
E[] temp = Arrays.copyOfRange(arr, l, r + 1);
int i = l, j = mid + 1; // i和j分别指向temp中兩端區間的頭部
// 每次都判斷arr[i]與arr[j]的大小即temp[i-l]和temp[j-l] l...r區間内
for (int k = l; k <= r; k++) {
// i超出中間位置,直接從j中取元素
if (i > mid) {
arr[k] = temp[j - l];
j++;
} else if (j > r) {
arr[k] = temp[i - l];
i++;
} else if (temp[i - l].compareTo(temp[j - l]) <= 0) {
arr[k] = temp[i - l];
i++;
} else {
arr[k] = temp[j - l];
j++;
}
}
}
優化merge函數
臨時數組開創次數過多,每一次歸并都開辟了新的空間。從記憶體上優化歸并排序。在遞歸調用前開辟一次空間。
public static <E extends Comparable<E>> void sort(E[] arr) {
E[] temp = Arrays.copyOf(arr, arr.length);
sort(arr, 0, arr.length - 1, temp);
}
// 遞歸函數
private static <E extends Comparable<E>> void sort(E[] arr, int l, int r, E[] temp) {
if (l >= r) return;
int mid = (r - l) / 2 + l;
// int mid2 = (r + l) / 2;
sort(arr, l, mid, temp);
sort(arr, mid + 1, r, temp);
// 如果mid>mid+1的情況下執行merge
if (arr[mid].compareTo(arr[mid + 1]) > 0)
merge(arr, l, mid, r, temp);
}
// 合并區間 arr[l...mid],arr[mid+1...r]
private static <E extends Comparable<E>> void merge(E[] arr, int l, int mid, int r, E[] temp) {
// 保證temp和arr相應元素相同
System.arraycopy(arr, l, temp, l, r - l + 1);
// i和j分别指向temp中兩端區間的頭部
int i = l, j = mid + 1;
// 每次都判斷arr[i]與arr[j]的大小即temp[i]和temp[j] l...r區間内
for (int k = l; k <= r; k++) {
// i超出中間位置,直接從j中取元素
if (i > mid) {
arr[k] = temp[j];
j++;
} else if (j > r) {
arr[k] = temp[i];
i++;
} else if (temp[i].compareTo(temp[j]) <= 0) {
arr[k] = temp[i];
i++;
} else {
arr[k] = temp[j];
j++;
}
}
}