题目:
给定你一个长度为 n 的整数数列。
请你使用归并排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。
快排和归并排序的时间复杂度都是 n * log2 (n)
思想:
分治(找分界点 递归排序)
快排是找一个 数组中的一个 值进行排序
而归并排序是以 中间点(数组的左右边界下标索引的平均值) 为分界点
1、确定分界点,以中间点((left + right) / 2 是左右边界下标索引的平均值)划分
2、递归排序左边 和 右边。(递归完后,左右两边就会变为有序的列表)
3、归并(难点),将两个有序的数组 归并为 一个有序的数组
我们使用双指针排序进行 归并,期间用一个临时数组temp[] ,记录每次调用排好的数据,排好此次调用排序方法的数据后 将数据赋给 原数组
临时数组temp[]的一个说明
这里特地对临时数组temp[]的使用 说明 因为吃坑了,
在递归中我们给定temp的大小不能直接是array.length而应该根据merge_Sort方法中形参而定,即根据左右边界left 和right来定,设置为right - left + 1(要排序的数据的数量,这样可以确保排完序后没有位置是空的,不会造成不必要的空值影响结果。
//right - left + 1是因为每次调用该方法需要排序的元素个数 就是 right - left + 1
int temp[] = new int[right - left + 1];//创建临时数组 存储 此次须排序的数据
还有一点关于临时数组的说明就是,临时数组排好序后给原数组赋值时,要根据其对应的索引位置直接赋值到 原数组相同的索引位置上,这一点可以根据 left和 right来 实现
for (int m = left, t = 0; m <= right; m++, t++) {
//m记录的是 此次排序的数据 在原数组对应的索引位置,所以直接将其赋值给原索引位置
//left 和 right就方便计算 其原始索引位置
array[m] = temp[t];
}
归并举例:
例如 数组 5 3 7 1 9 4 2 8 5 10,经过上面的中间点(0 + 9 >> 1 = 4,中间点的索引为4)分解后 ,变为两个数组 一个是索引从0到4 的 [5 3 7 1 9] 另一个是 索引从5到9的 [4 2 8 5 10]
首先经过 上面的递归后,左右两个数组均为从小到大的有序数组
即[1 3 5 7 9] 和 [2 4 5 8 10]
因为两个数组 均是 有序数组,左侧的均为 min值 ,所以两个指针都放在左侧
循环比较两个指针所对应的数字,哪个指针对应的数字小,就将该数字加入到temp数组,并将指针加1,
while(i <= mid && j <= right){
if(array[i] <= array[j]){
temp[k++] = array[i++];
}else{
temp[k++] = array[j++];
}
}
因为1比较小 所以将 1加入到 临时数组中,并将索引 ++
继续比较发现 2小于3 于是 将 2加入结果 并将下面的索引++
。。。。
。。。。
不断循环
最后两个指针一个 是 9 一个是10,比较后 将9 加入临时数组 ,并将索引++
注意此时 第一个指针已经 大于了mid,不符合循环条件了,所以跳出循环,
但是还有一部分 需要添加到临时数组(剩下的都是已经递归排好序的,所以直接循环添加入临时数组即可)
//只要左半边没有循环完(i <= mid),就将 左半边的都加到 temp数组中
while(i <= mid){temp[k++] = array[i++];}
//只要右半边没有循环完(j <= right),就将 右半边的都加到 temp数组中
while(j <= right){temp[k++] = array[j++];}
由于结果存储在 临时数组temp中,现在需要将 其复制回原数组
for (int m = left, t = 0; m <= right; m++, t++) {
//m记录的是 此次排序的数据 在原数组对应的索引位置,所以直接将其赋值给原索引位置
//left 和 right就方便计算 其原始索引位置
array[m] = temp[t];
}
整体代码
package algorithm_;
/**
* @author TJU第一炼丹师
* @since 2024-02-22 15:55:20
*/
import java.util.*;
public class merge_Sort {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int array[] = new int[n];
for (int i = 0; i < n; i++) {
array[i] = sc.nextInt();
}
merge_Sort(array, 0, n - 1);
for (int i = 0; i < n; i++) {
System.out.print(array[i] + " ");
}
}
public static void merge_Sort(int array[], int left, int right){
if(left >= right) return;
//1、设置分界点
int mid = left + right >> 1;
//2、递归
merge_Sort(array, left, mid);
merge_Sort(array, mid + 1, right);
//3、归并 将两个有序数组 合二为一 变成一个有序数组
//right - left + 1是因为每次调用该方法需要排序的元素个数 就是 right - left + 1
int temp[] = new int[right - left + 1];//创建临时数组 存储 此次须排序的数据
int k = 0;//表示在归并过程中,临时结果数组 temp中已有多少个数字了
int i = left;//i是指针 指向 左边数组的起点
int j = mid + 1;//j是指针 指向 右边数组的起点
//比较两个指针所对应的 数字,哪个指针对应的数字小,就将该数字加入到temp数组,并将指针加1
while(i <= mid && j <= right){
if(array[i] <= array[j]){
temp[k++] = array[i++];
}else{
temp[k++] = array[j++];
}
}
//只要左半边没有循环完(i <= mid),就将 左半边的都加到 temp数组中
while(i <= mid){temp[k++] = array[i++];}
//只要右半边没有循环完(j <= right),就将 右半边的都加到 temp数组中
while(j <= right){temp[k++] = array[j++];}
//由于结果存储在 临时数组temp中,现在需要将 其复制回原数组
//
for (int m = left, t = 0; m <= right; m++, t++) {
//m记录的是 此次排序的数据 在原数组对应的索引位置,所以直接将其赋值给原索引位置
//left 和 right就方便计算 其原始索引位置
array[m] = temp[t];
}
}
}