给定你一个长度为 n的整数数列。
请你使用归并排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。
输入格式
输入共两行,第一行包含整数 n。
第二行包含 n个整数(所有整数均在 1∼1091∼109 范围内),表示整个数列。
输出格式
输出共一行,包含 n个整数,表示排好序的数列。
数据范围
1≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5
代码如下:
java
package com.zy.Acwing.mergeSort;
import java.util.Scanner;
//本段代码主要实现了归并排序算法
public class P787 {
static int N = 100010;
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int[] arr = new int[N];
for(int i = 0 ; i < n ; i++) {
arr[i] = scan.nextInt();
}
merge_sort(arr,0,n-1);
for(int i = 0 ; i < n ; i++) {
System.out.print(arr[i]+" ");
}
}
public static void merge_sort(int[] arr,int left,int right) {
if(left>=right) return;
int mid = left + right >> 1;
merge_sort(arr,left,mid);
merge_sort(arr,mid + 1,right);
int k = 0,i = left,j = mid+1;
int[] temp = new int[right-left+1];
while(i <= mid && j <= right) {
if(arr[i] <= arr[j]) temp[k++] = arr[i++];
else temp[k++] = arr[j++];
}
while(i <= mid) temp[k++] = arr[i++];
while(j <=right) temp[k++] = arr[j++];
for(i = left,k = 0;i <= right;i++,k++) arr[i] = temp[k];
}
}
自己基于目前的了解对此段代码进行一些解释:
- **if(left>=right) return; 递归结束的条件,**当数组中只有一个数或者达到left>=right条件时,直接结束递归;
- **int mid = left + right >> 1;**取整个数组的分隔线,向上取整,使整个数组一分为二;
- merge_sort(arr,left,mid); merge_sort(arr,mid + 1,right);
使用递归思想,使用mid作为分隔线,划分为arr[left,....mid]和arr[mid+1,....right]两个数组;
递归之后的两个数组都是有序的,下面的代码就是在两个有序数组中,归并为一个大的有序数组;
- int k = 0,i = left,j = mid+1; int[] temp = new int[right-left+1];
定义一个temp数组,用来存放排序之后的元素,
i为一个数组的左边界,j为另一段数组的左边界(前面已经提到一个数组被分割为两个数组)
java
while(i <= mid && j <= right) {
if(arr[i] <= arr[j]) temp[k++] = arr[i++];
else temp[k++] = arr[j++];
}
本段代码首两个数组的元素都没有比较完,如果其中一个已将全部比较完成,另一组没有,则本循环结束,较小的放进temp,同时下标向右移,较大的留下继续比较;
java
while(i <= mid) temp[k++] = arr[i++];
while(j <=right) temp[k++] = arr[j++];
本段代码实现的功能是如果其中一个数组已将全部比较完成,另一组没有还剩下的元素,把剩下的元素((剩下的元素都是有序的,无须再次比较)放进temp中;
- for(i = left,k = 0;i <= right;i++,k++) arr[i] = temp[k];
将temp中排好序的元素放到arr中;
- 为什么不用 mid - 1 作为分隔线呢?
即 merge_sort(q, l, mid - 1 ), merge_sort(q, mid, r)
因为 mid = l + r >> 1 是向下取整,mid 有可能取到 l (数组只有两个数时),造成无限划分
解决办法: mid 向上取整就可以了, 即 mid = l + r + 1 >> 1;
- 为什么进行递归之后,两段数组变为有序的呢?
首先要明白归并算法的整个流程是什么的,B站中有一个动画展示的比较清楚,地址如下:归并算法动画演示https://www.bilibili.com/video/BV1Je4y1K7Qd/?spm_id_from=333.999.top_right_bar_window_custom_collection.content.click&vd_source=f83925eb71a2ec1683580bbf22db27ec 通过递归的不断执行,当划分为n个数组时,一个数组只有一个元素,一个数本身就是有序的,满足l==j的条件,无需执行后面的代码,结束最后一次的递归代码的执行;接着实现倒数第二次的递归,此时的数组元素是由两个元素组成,通过后面代码的比较,将这个含有两个元素的数组进行排序,完成之后,接着实现倒数第三次的递归,此时的数组元素是由四个元素组成,将这个含有两个元素的数组进行排序......,知道第一次递归结束,此时一个数组中的n个元素是有序的,排序完成;
其实,整个递归执行过程是一棵二叉树,先会递归到整棵树的底部,底部只有一个节点一定有序,然后在回溯时不断合并相邻两个区间,每次合并之后都会将合并之后的区间排好序。那么当合并到根节点时,整个区间就有序了。