排序算法之归并排序
经典排序算法有冒泡排序,插入排序,选择排序,归并排序,快速排序,计数排序,基数排序,桶排序。咱们最了解的应该就是冒泡排序,插入排序,选择排序,这里我们就一起学习一下归并排序。
核心思想
归并排序的核心思想其实非常简单,就是把数组两两分解,将分解好的两部分排序,最后再合并在一起,这样最后整个数组就是有序的了。归并排序采用的是分治思想,将一个大问题分解为若干个小问题解决,小的问题解决了,大问题自然迎刃而解。分治的思想和递归挺相似,递归是代码上的体现,而分治是解决问题的思路,这里我们就采用递归实现归并排序。
例如数组
{3,2,5,4,1,3,8,7}=>{3,2,5,4}+{1,3,8,7}=>{3,2}+{5,4}+{1,3}+{8,7}=>{3}+{2}+{5}+{4}+{1}+{3}+{8}+{7}=>{2,3}+{4,5}+{1,3}+{7,8}=>{2,3,4,5}+{1,3,7,8}=>{1,2,3,3,4,5,7,8}
代码实现
因为我们知道,归并排序就是将数组分解,排序,整合,如果分解后的数组还能再分解,那么就继续分解,直至无法分解为止。这里我们就可以写出递归公式,mergeSort(p...r)=merge(mergeSort(p...q),mergeSort(q+1...r))
,这里q=(p+r)/2
,mergeSort(p...r)
中p和r是指数组中下标为p到r的数据,merge方法是合并两个数组的同时去排序。
递归的终止条件也就是无法再分解,即p>=r
递推公式我们写出来了,代码写起来也就比较容易了。
package com.test;
import java.util.Arrays;
public class MergeSort {
public static void main(String[] args) {
int[] arr = new int[]{3, 2, 5, 4, 1, 7, 6, 9};
mergeSort(arr, arr.length);
System.out.println(Arrays.toString(arr));
}
/**
* @param arr, n
* @return: void
* @Title:
* @Description: 递归调用
* @date: 23:56 2021/3/21 0021
* Modification History:
* Date Author Description
*-------------------------------------------*
* 23:56 2021/3/21 0021 xx.huang 修改原因
*/
static void mergeSort(int[] arr, int n) {
mergeSortA(arr, 0, n - 1);
}
/**
* @param arr, p, r
* @return: void
* @Title:
* @Description: 往下分解数组
* @date: 23:56 2021/3/21 0021
* Modification History:
* Date Author Description
*-------------------------------------------*
* 23:56 2021/3/21 0021 xx.huang 修改原因
*/
static void mergeSortA(int[] arr, int p, int r) {
if (p >= r) {
return;
}
int q = (p + r) / 2;
mergeSortA(arr, p, q);
mergeSortA(arr, q + 1, r);
merge(arr, p, q, r);
}
/**
* @param arr, p, q, r
* @return: void
* @Title:
* @Description: 排序合并数组
* @date: 23:56 2021/3/21 0021
* Modification History:
* Date Author Description
*-------------------------------------------*
* 23:56 2021/3/21 0021 xx.huang 修改原因
*/
static void merge(int[] arr, int p, int q, int r) {
int[] temp = new int[r - p + 1];
int i = p;
int j = q + 1;
int k = 0;
while (i <= q && j <= r) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
if (i <= q) {
while (i <= q) {
temp[k++] = arr[i++];
}
} else if (j <= r) {
while (j <= r) {
temp[k++] = arr[j++];
}
}
for (int s = 0; s <= r - p; s++) {
arr[p + s] = temp[s];
}
}
}
通过我的注释就可以知道每个方法的作用,我们着重来解释merge方法。因为我们知道归并排序是拆分数组再排序,但是实际上我们并没有真正的拆分数组,还是在原数组也就是arr上面操作的,我们每次操作的数据数量是r-p+1,所以我们需要创建一个临时数组temp存放这些数据。虽然我们在代码中没有真正的把数组拆开,但是思路上确实是当作被分割了的数组,我们知道根据分治的思想,最后是被拆成若干个无法被继续分割的数组,然后往回排序合并,我们每次是要操作两个数组(思想上当作两个数组,实际还是在一个数组中),p即需要往回合并排序数组1的起始下标,q+1是往回合并排序数组2的起始下标,当数组无法被继续分割的时候,实际上每个数组就只有一个元素(再强调一下,实际代码中并没有拆分数组,只是思路上确实是这么想的),然后根据后面的排序代码进行两个数组的排序,最后把排序好的数组temp放回arr数组中,如此递归,最后的数组就是有序的了。