1. 归并排序原理
归并排序的思想是分而治之。什么意思呢?就是把一件大的问题拆分成很多小问题来解决,先解决一个个小问题,最后大问题就随之解决了。
如上图,假设需要对上面的数组进行排序。分而治之看待这个问题:将数组分成两半,那么整个问题就演变成左边需要排好序,右边也需要排好序,最后把左边排好序的数组和右边排好序的数组进行合并。
左边的数组又如何排序呢?同样将数组对半分成【1,4】和【6,7,10】,排好左边和右边的数组再合并。
依上写出归并排序的伪步骤:
- 将数组分成左右两半
- 左边用归并排序排好序
- 右边用归并排序排好序
- 左右两边已经有序的数组合并
总结出来发现,不管怎样,都需要一个合并两个有序数组的算法。普通的想法,就是把第二个数组的数和第一个数组的数进行比较然后移动位置。这样做下去会发现时间复杂度太高了,每次插入一个数都需要把其他的数往后移动,相当于做了一遍插入排序。
考虑以空间换时间的方式,new一个长度相同的数组,将比较后的结果移动到这个新建的数组中,这样移动的次数就是O(n)了。
2.合并代码实现
public static void merge(int[] arrays, int left, int mid, int right) {
//模板数组,用来存放排好的数字
int[] temp = new int[right - left+1];
int i = left;
int j = mid+1;
int k = 0;
while (i <= mid && j <= right) {
// if (arrays[i] <= arrays[j]) {
// temp[k] = arrays[i];
// i++;
// k++;
// } else {
// temp[k] = arrays[j];
// j++;
// k++;
// }
temp[k++] = arrays[i] <= arrays[j] ? arrays[i++] : arrays[j++];
}
while (i <= mid) {
temp[k++] = arrays[i++];
}
while (j <= right) {
temp[k++] = arrays[j++];
}
//将temp数组赋值给arrays数组
for (int l = 0; l < temp.length; l++) {
arrays[left + l] = temp[l];
}
}
3. 递归的归并排序算法实现
public static void mergeSort(int[] arrays, int left, int right) {
//对半拆分数组 递归算法不太好理解,凡是递归算法都会有一个条件跳出递归,不然就会一直递归下去,造成栈溢出,这里left==right的时候表示只有一个元素的数组的时候
if (left == right) return;
int mid = (left + right) / 2;
mergeSort(arrays, left, mid);
mergeSort(arrays, mid+1, right);
merge(arrays, left, mid, right);
}
4. 归并排序的时间复杂度和空间复杂度
时间复杂度:归并排序中,每一层处理的数据量为 O(n) 级别,同时有 logn 层,时间复杂度便是 O(nlogn)
空间复杂度:我们在写代码时,完全可以用空间来换取时间,比如字典树,哈希等都是这个原理。算法在运行过程中临时占用的存储空间随算法的不同而异,有的算法只需要占用少量的临时工作单元,而且不随问题规模的大小而改变,我们称这种算法的空间复杂度为O(1),注意这并不是说仅仅定义一个临时变量;有的算法需要占用的临时工作单元数与解决问题的规模n有关,它随着n的增大而增大,当n较大时,将占用较多的存储单元,比如归并排序,每次执行的时候都会用到一个辅助数组,这个辅助数组的空间就跟数组的长度n有关。所以归并排序的空间复杂度为O(n)