归并排序算法的中心思想:先拆分-再合并。
具体思路:
先拆分:将数组从中间一分为二,形成两个左、右两个数组。再次分别递归左、右两个数组,重复此步骤(递归进行),直至数组中剩下的元素只有一个。(左右为代称,每一次的左、右数组实际都为两个新的数组。)
再合并:当左数组中只剩一个元素时,递归返回,右数组同样也只剩下一个元素,调用合并方法,将这两个元素合并为有序序列。此时返回上一层递归,再次合并一个有序序列,再将这两个有序序列进行合并。重复此步骤,最终合并成为一个大的有序序列。
代码实现:
public class Test {
/**
* 拆分操作:将原数组在中间拆分开,形成左右数组,再次重复此过程,直到跳出递归,既此时的左数组中只含有一个元素,返回上一层递归
* 右数组执行递归,同样跳出递归时只有一个元素,然后执行合并方法。
*/
private void mergeSort(int[] arr, int left, int right) {
int mid = (left + right) >>> 1;
if (left >= right)
return;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
/**
* 合并操作:有三个参数,arr数组中至少有2个元素,left是当前数组元素的起始位置,mid是将arr数组拆分开的位置,right是数组中的结束位置
*/
private void merge(int[] arr, int left, int mid, int right) {
//将arr拆分为两个归并段
//第一个归并段的起始位置
int start1 = left;
//第二个归并段的起始位置
int start2 = mid + 1;
//临时数组,用来存放归并完成的元素,数组的长度是根据传进来的开始结束位置决定的
int[] temp = new int[right - left + 1];
//指向临时数组要存元素的位置
int t = 0;
//当起始位置<=末尾位置时,说明至少有一个元素。当有一个归并段没有元素了就会退出循环,但另一个归并段还有没有元素呢?
while (start1 <= mid && start2 <= right) {
//如果第一个归并段的元素<=第二个归并段的元素,将第一个归并段的元素放进临时数组中,下标都要+1,否则就把第二个归并段的元素放进去
if (arr[start1] <= arr[start2]) {
temp[t] = arr[start1];
start1++;
t++;
} else {
temp[t] = arr[start2];
start2++;
t++;
}
}
//如果此时第一个归并段还有元素,直接把所有元素放到临时数组末尾
while (start1 <= mid) {
temp[t] = arr[start1];
start1++;
t++;
}
//如果此时第二个归并段还有元素,直接把所有元素放到临时数组末尾
while (start2 <= right) {
temp[t] = arr[start2];
start2++;
t++;
}
//将临时数组中的值取出来放进原数组中
//但要注意,不是每一个归并段都是从下标0开始的,要注意归并段的原始下标
for (int i = 0; i < temp.length; i++) {
arr[i + left] = temp[i];
}
}
public static void main(String[] args) {
// int[] arr = new int[100000];
// for (int i = 100000-1; i >= 0; i--) {
// arr[i] = i;
// }
int[] arr = {7,3,5,6,4,1,2,0};
long start = System.currentTimeMillis();
new Test().mergeSort(arr, 0, arr.length - 1);
// for (int i : arr) {
// System.out.println(i);
// }
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
归并算法的时间复杂度:
归并排序的时间复杂度为O(nlogn),其中n是待排序数组的长度。归并排序的分治思想保证了每一次合并操作的时间复杂度都是O(n),而每一次递归都会把数组分成两个子数组,因此递归层数为logn。因此,归并排序的时间复杂度为O(nlogn)。