参考链接:b站韩顺平老师的数据结构
思路:归并排序整个思路是首先递归拆解数组,每一次对数组相当于对半拆分,首先对左边递归拆分,再对右边递归拆分。这里用到了分治思想,先分再治(合并)
递归的思路都是先推公式再找终止条件
公式描述:merge(q.....r)=merge(merge(q....p)+merge(p+1.....r))///公式的意思就是把一个数组对半拆,拆到只剩一个元素
终止条件:p>=r,递归拆解结束,然后开始合并
q是头元素索引,r是尾元素索引,p是中间元素索引=(q+r)/2
代码描述和实现:
代码思路:【先捋思路再写代码】先写合并merge方法,以最后一次合并为例,需要的参数有原数组(待排序数组)首、尾、中间索引、临时数组(存放当前合并数据),中间索引是左序列的尾索引,加1为右序列的头索引
接着根据公式和终止条件写递归拆分以及调用合并(mergeSort)方法,左递归时,中间索引往“左边”走,到右递归时中间索引会往“右边”走【结合代码注释进行理解,merge中的第三步比较难】
最后写main方法测试
package Sort;
import java.util.Arrays;
public class MergeSort {
public static void main(String[] args) {
int arr[] = {10,1,5,4,2,7,3,9};
int temp[] = new int[arr.length];//额外空间,临时数组
mergeSort(arr, 0, arr.length-1, temp);
System.out.println();
System.out.println("归并完成:"+Arrays.toString(arr));
}
/**
*
* Description:分+合方法
* @param: @param arr
* @param: @param left
* @param: @param right
* @param: @param temp
* @author Jeason
* @date 2021年6月14日
* @version 1.0
*/
public static void mergeSort(int[] arr,int left ,int right ,int[] temp)
{
if(left < right)
{
int mid = (left + right)/2;//中间索引
mergeSort(arr, left, mid, temp);//左递归进行分解
mergeSort(arr, mid+1, right, temp);//右递归进行分解
merge(arr, left, mid, right, temp);//合并
}
}
/**
*
* Description:合并的方法
* @param: @param arr 待排序数组
* @param: @param left 左边有序序列的初始索引
* @param: @param right 右边有序序列的初始索引
* @param: @param right 中间索引,(也是左边数组的尾元素索引)
* @param: @param temp 中转临时数组
* @author Jeason
* @date 2021年6月13日
* @version 1.0
*/
public static void merge(int[] arr,int left,int mid,int right,int[] temp)
{
int i = left;//初始化i,左边有序序列的初始索引
int j = mid+1;//初始化j,右边有序序列的初始索引
int t = 0;//temp数组当前索引
//第一步
//先把左右两边的(有序)数组一次填充到temp数组
//直到左右两边的有序序列,有一边处理完成(意思:任意单边数据已填入数组中)
while(i<=mid && j<=right)//未合并完
{
if(arr[i] <= arr[j])//“左”边小,左边先入,temp数组往后
{
temp[t] = arr[i];
t += 1;
i += 1;
}
else//“右”边小,右边入,temp数组往后
{
temp[t] = arr[j];
t += 1;
j += 1;
}
}
//第二步
//把未处理完成得另一边数组剩余的元素全部填入temp数组尾部
while(i<=mid)//左边未处理完,剩余压入
{
temp[t] = arr[i];
t += 1;
i += 1;
}
while(j<=right)//右边剩余压入
{
temp[t] = arr[j];
t += 1;
j += 1;
}
//第三步
//将temp数组全部拷贝到arr原数组回去
//注意,并不是每次都拷贝所有,我把每一次合并都打印出来了,这里我们看到第四次打印是2 7 5 10,这是对的,因为我们没有擦除上次合并的数据 5 和 10
t = 0;
int tempLeft = left;
while(tempLeft<=right)//
{
arr[tempLeft] = temp[t];
t += 1;
tempLeft += 1;
}
System.out.println(Arrays.toString(temp));
}
}
结果:
第三遍:
package Sort;
import java.util.Arrays;
public class MergeSort {
public static void main(String[] args) {
MergeSort sort = new MergeSort();
int arrs[] = {3,2,5,1,6};
sort.mergeSort(arrs, 0, arrs.length-1, new int[arrs.length]);
System.out.println(Arrays.toString(arrs));
}
public void mergeSort(int arrs[],int left,int right,int[] tempList)
{
if(left>=right)//一个数,不用拆解
return;
int mid=left+(right-left)/2;
//左递归
mergeSort(arrs, left, mid, tempList);
//右递归
mergeSort(arrs, mid+1, right, tempList);
//合并
merge(arrs,left,right,mid, tempList);
}
private void merge(int arrs[],int left,int right,int mid,int[] tempList)
{
if(left>=right)//一个数,不用合并
return;
int i=left;
int j=mid+1;
int t=left;//可以为0,这里是为了和原数组索引对应起来
while(i<=mid&&j<=right)
{
if(arrs[i]<=arrs[j])
tempList[t++]=arrs[i++];
else
tempList[t++]=arrs[j++];
}
//左边剩余压入
while(i<=mid)
{
tempList[t++]=arrs[i++];
}
//右边剩余压入
while(j<=right)
{
tempList[t++]=arrs[j++];
}
//拷贝回去
t=left;
while(left<=right)
{
arrs[left]=tempList[left];
left++;
}
}
}
递归实现
注意点:tempLeft从未排序原数组开始,可以用left代替,tempList[t++]=arrs[i++];【经常写错的地方】
时间:由完全二叉树的深度可知,整个归并排序需要进行O(log2^n)次,每次进行n个数的合并 总时间复杂度为O(nlogn)
空间:在归并过程中需要一个与原始序列相同长度的临时数组,以及递归栈O(log2^n),因此空间复杂度为(n+logn)即O(N)
归并排序的时间复杂度与数组是否有序无关,这是从代码得出来的,所以时间复杂度稳定,最好最坏平均都是O(nlogn),即使是快速排序其最坏时间复杂度也为O(N^2),所以是非常优秀的,那为什么归并没有广泛应用?因为它不是原地排序算法,需要借助O(N)的辅助空间
归并排序需要两两比较,可以保证值相等的元素保持原来的顺序不变,也就是不存在跳跃,因此它是稳定的算法
综上:归并排序是稳定高效,比较占用内存的算法