归并排序思想
有一个数组,
- 首先我们要做的就是将这个初始数组划分成两半
- 然后想办法把左边的数组进行排序,把右边的数组也进行排序
- 最后在合并起来(归并起来)
归并排序使用的就是分治思想。分治,顾名思义,就是分而治之,将一个大问题分解成小的子问题来解决。小的子问题解决了,大问题也就解决了。
但是,这会有一个疑问,我怎么对左右两边的数组进行排序呢?这时候就需要重复进行上面的三个步骤,把左半部分、右半部分分别再划分成两半,直到左右两个部分只剩一个元素不能再进行划分就停止。就像下面这样:
这个时候左右部分只有一个元素,它们已经是排好序的了。我们只需要对他们进行归并就可以了。
然后就是逐层的向上归并,一直归并到整个数组的最后一层,整个数组就全部有序了。
在归并的时候,我们需要开辟一个临时数组存放每一次归并后的数据。
代码实现
递推公式:
merge_sort(l…r) = merge(merge_sort(l…mid), merge_sort(mid+1…r))
终止条件:
l >= r 不用再继续分解
merge_sort(l...r)
,表示需要给 l
到 r
之间的数组进行排序。 然后我们将这整个数组的排序问题转化为左右两个子数组的排序问题。
其中下标 mid
等于 l
和 r
的中间位置,也就是 (l+r)/2
。当下标从 l
到 mid
和从 mid+1
到r
这两个子数组都排好序之后,我们再将两个有序的子数组合并在一起,这样下标从 l
到r
之间的数据就也排好序了。
mergeSort(int[] arr){
if(arr == null || arr.length < 2) return;
mergeSort(arr,l,r);
}
mergeSort(int[] arr,int l,int r){
if(l >= r) return;
int mid = l + ((r - l) >> 1);
// 递归调用
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
merge(arr, l, mid , r);
}
merge(int[] arr, int l, int mid, int r){
int[] tmp = new int[r - l + 1];
int i = 0;
int p1 = l;
int p2 = mid + 1;
while(p1 <= mid && p2 <= r){
tmp[i++] = arr[p1 <= p2] ? arr[p1] : arr[p2];
}
// 两个必有一个越界
while(p1 <= mid){
tmp[i++] = arr[p1++];
}
while(p2 <= r){
tmp[i++] = arr[p2++];
}
for(int j = 0;j < tmp.length;j++ ){
arr[l + 1] = tmp[j];
}
}
在归并的操作中,我们准备了两个游标p1
和p2
,用于表示arr[l...mid]
和 arr[mid +1,r]
的第一个位置。
比较这两个元素arr[p1]
和 arr[p2]
,如果 arr[p1] <= arr[p2]
,我们就把 arr[p1]
放入到临时数组 tmp,并且p1
后移一位,否则将 arr[p2]
放入到数组 tmp,p2
后移一位。
继续上面的过程,直到其中一个子数组中的所有数据都放入临时数组中,然后再把另一个数组中的数据依次加入到临时数组的末尾,这个时候,临时数组中存储的就是两个子数组合并之后的结果了。最后再把临时数组 tmp 中的数据拷贝到原数组 arr[l…r]
中。