歸併排序
歸併排序是建立在歸併操作上的一種有效的排序算法,是採用分治法(Divide and Conquer)的一個非常典型的應用。
後面的擴展題目,小和問題和逆序對個數問題,用文字真是解釋的稀爛,自己都看不下去。。。還是再紙上畫一畫,Debug看一下代碼執行的流程會比較好。
算法代碼如下:
public class MergeSort {
public static void main(String[] args) {
int[] array = new int[]{4, 2, 9, 1, 0, 8, 5, 3};
mergeSort(array, 0, array.length-1);
System.out.println(Arrays.toString(array));
return;
}
private static void mergeSort(int[] array, int left, int right) {
if(left == right)
return;
int mid = left + ((right - left) >> 1);
mergeSort(array, left, mid);
mergeSort(array, mid+1, right);
merge(array, left, mid, right);
}
private static void merge(int[] array, int left, int mid, int right) {
int[] helper = new int[right - left + 1];
int l1 = left, l2 = mid + 1;
int helperIndex = 0;
while(l1 <= mid && l2 <= right){
helper[helperIndex++] = (array[l1] < array[l2] ? array[l1++] : array[l2++]);
}
while(l1 <= mid){
helper[helperIndex++] = array[l1++];
}
while(l2 <= right){
helper[helperIndex++] = array[l2++];
}
for(int i = 0; i < helper.length; i++){
array[left+i] = helper[i];
}
}
}
mergeSort(int[] array, int left, int right)方法中主要關注三個部分:
mergeSort(array, left, mid); //左半部分歸併排序
mergeSort(array, mid+1, right); //右半部分歸併排序
merge(array, left, mid, right); //左右兩邊進行合併
這也就是歸併排序主要的思路。
歸併排序的時間複雜度
在master公式中,a是子問題的個數,N/b是子問題的規模,O(N^d)是其他的開銷時間。
在歸併排序中,子問題的個數是2,子問題的規模是N/2,至於其他的開銷,在歸併排序中就是左右兩邊進行合併的開銷,時間複雜度爲O(N)。所以,T(N) = 2*T(N/2) + O(N),也就是時間複雜度爲O(N * logN)。
擴展1——小和問題
[3, 1, 4, 5, 2]
3左邊比3小的數:
1左邊比1小的數:
4左邊比4小的數:3, 1
5左邊比5小的數:3, 1, 4
2左邊比2小的數:1
把這些數求和得到的就是小和: 13
public class MergeSortToSmallSum {
public static void main(String[] args) {
int[] array = new int[]{3, 1, 4, 5, 2};
System.out.println(mergeSort(array, 0, array.length-1));
return;
}
private static int mergeSort(int[] array, int left, int right) {
if(left == right)
return 0;
int mid = left + ((right - left) >> 1);
return mergeSort(array, left, mid)
+ mergeSort(array, mid+1, right)
+ merge(array, left, mid, right);
}
private static int merge(int[] array, int left, int mid, int right) {
int[] helper = new int[right - left + 1];
int l1 = left, l2 = mid + 1;
int helperIndex = 0;
int result = 0;
while(l1 <= mid && l2 <= right){
result += array[l1] < array[l2] ? (right-l2+1) * array[l1] : 0;
helper[helperIndex++] = (array[l1] < array[l2] ? array[l1++] : array[l2++]);
}
while(l1 <= mid){
helper[helperIndex++] = array[l1++];
}
while(l2 <= right){
helper[helperIndex++] = array[l2++];
}
for(int i = 0; i < helper.length; i++){
array[left+i] = helper[i];
}
return result;
}
}
擴展2——逆序對個數
[4, 3, 5, 2, 1]
4右邊比4小的數:3, 2, 1
3右邊比3小的數:2, 1
5右邊比5小的數:2, 1
2右邊比2小的數:1
1右邊比1小的數:
總共8對逆序對
小和問題中,我們還是按照從小到大對數組進行排序,因爲小和問題是要找到一個數左邊比自己小的數,所以在merge方法中,我們判斷左邊數比右邊數小的時候,那麼這個數比右邊數及其之後的數都要小,所以可以使用公式:
result += array[l1] < array[l2] ? (right-l2+1) * array[l1] : 0;
但是在逆序對問題中,是找左邊比右邊數大的,顯然使用從大到小排序更適合處理這個問題。判斷出左邊數比右邊數大的時候,那麼可以肯定左邊這個數,比右邊數及其後邊的數都要大。
public class MergeSortToInversion {
public static void main(String[] args) {
int[] array = new int[]{3, 4, 5, 2, 1};
System.out.println(mergeSort(array, 0, array.length-1));
System.out.println(Arrays.toString(array));
return;
}
private static int mergeSort(int[] array, int left, int right) {
if(left == right)
return 0;
int mid = left + ((right - left) >> 1);
return mergeSort(array, left, mid)
+ mergeSort(array, mid+1, right)
+ merge(array, left, mid, right);
}
private static int merge(int[] array, int left, int mid, int right) {
int[] helper = new int[right - left + 1];
int l1 = left, l2 = mid + 1;
int helperIndex = 0;
int result = 0;
while(l1 <= mid && l2 <= right){
result += array[l1] > array[l2] ? (right-l2+1) : 0;
helper[helperIndex++] = (array[l1] < array[l2] ? array[l2++] : array[l1++]);
}
while(l1 <= mid){
helper[helperIndex++] = array[l1++];
}
while(l2 <= right){
helper[helperIndex++] = array[l2++];
}
for(int i = 0; i < helper.length; i++){
array[left+i] = helper[i];
}
return result;
}
}