java-歸併排序與快排的效率比較
用java完整地實現了歸併排序和快速排序,然後測試兩個算法在數組長度由100增加到一億過程中的時間複雜度,比較兩個算法效率。
歸併排序
public class MergeSort {
public int[] A;
public MergeSort(int[] array) {
this.A = array.clone();
sort(0, array.length - 1);
}
public void sort(int low, int high) {
if (low < high) {
int mid = (low + high) / 2;
sort(low, mid);
sort(mid + 1, high);
merge(low, mid, high);
}
}
public void merge(int low, int mid, int high) {
// 聲明新的數組,臨時儲存歸併結果
int[] B = new int[high - low + 1];
int h = low;
int i = 0;
int j = mid + 1;
while (h <= mid && j <= high) {
if (A[h] <= A[j]) {
B[i] = A[h];
h++;
} else {
B[i] = A[j];
j++;
}
i++;
}
// 等號很重要
if (h <= mid) {
for (int k = h; k <= mid; k++) {
B[i] = A[k];
i++;
}
} else {
for (int k = j; k <= high; k++) {
B[i] = A[k];
i++;
}
}
for (int k = low; k < high; k++) {
A[k] = B[k - low];
}
}
}
快速排序
public class QuickSort {
public int[] A;
public QuickSort(int[] array) {
this.A = array.clone();
sort(0, array.length - 2);
}
public void sort(int p, int q) {
if (p < q) {
int j = partition(p, q);
sort(p, j - 1);
sort(j + 1, q);
}
}
public int partition(int p, int q) {
int j = q;
int axis = A[p];
int i = p;
while (true) {
//從左往右找到第一個比axis大的數字
while (A[i] <= axis) {
i++;
};
//從右往左找到第一個小於等於axis的數字
while (A[j] > axis) {
j--;
};
if (i < j) {
swap(i, j);
} else {
break;
}
}
A[p] = A[j];
A[j] = axis;
return j;
}
public void swap(int i, int j) {
int tmp = A[i];
A[i] = A[j];
A[j] = tmp;
}
}
效率比較
新建TimeCounter類,用來實例化兩個算法類,並測試兩個算法。size指定測試數組的長度,maximum 指定生成的數組中元素的最大值,注意:由於快排的數組最後一個數字要求最大,因此實際的數組長度是是size+1
import java.util.Arrays;
import java.util.Random;
public class TimeCounter {
public static void main(String[] args) {
// TODO Auto-generated method stub
int size = 101;
int maximum = 100000;
int[] array = new int[size];
for (int i = 0; i < array.length - 1; i++) {
array[i] = new Random().nextInt(maximum);
}
//快排數組中的最後一個數字最大
array[size - 1] = maximum + 1;
long QuickStart = System.currentTimeMillis();
new QuickSort(array);
long QuickEnd = System.currentTimeMillis();
long MergeStart = System.currentTimeMillis();
MergeSort mergesort=new MergeSort(array);
long MergeEnd = System.currentTimeMillis();
// System.out.println(Arrays.toString(array));
// System.out.println(Arrays.toString(mergesort.A));
System.out.println("quick sort: " + (QuickEnd - QuickStart));
System.out.println("merge sort: " + (MergeEnd - MergeStart));
}
}
在同一臺計算機上,修改size的值,運行TimeCounter.java,得到兩個算法在不同數組長度下的執行時間:
數組長度 | 快速排序(運行時間/毫秒) | 歸併排序(運行時間/毫秒) |
---|---|---|
100 | 0 | 0 |
1000 | 1 | 1 |
10000 | 1 | 3 |
100000 | 14 | 14 |
1000000 | 79 | 120 |
10000000 | 982 | 1186 |
100000000 | 55733 | 12328 |
結果
我們知道快排和歸併的理論上的時間複雜性如下表:
算法 | 最壞時間複雜性 | 平均時間複雜性 |
---|---|---|
快速排序 | n^2 | n*log(n) |
歸併排序 | n*log(n) | n*log(n) |
1. 在數組長度小於一千萬的時候,如下圖,快速排序的速度要略微快于歸並排序,可能是因爲歸併需要額外的數組開銷(比如聲明臨時local數組用來儲存排序結果),這些操作讓歸併算法在小規模數據的並不佔優勢。
2. 但是,當數據量達到億級時,歸併的速度開始超過快速排序了,如下圖,因爲歸併排序比快排要穩定,所以在數據量大的時候,快排容易達到O(n^2)的時間複雜度,當然這裏是指未改進的快排算法。