归并排序是一个典型的基于分治的递归算法。它不断地将原数组分成大小相等的两个子数组(可能相差1),最终当划分的子数组大小为1时(即left小于right不成立时) ,将划分的有序子数组合并成一个更大的有序数组。
归并排序思路分析:
归并排序算法有两个基本的操作,一个是分,也就是把原数组划分成两个子数组的过程。另一个是治,它将两个有序数组合并成一个更大的有序数组。
它将数组平均分成两部分: center = (left + right)/2,当数组分得足够小时---数组中只有一个元素时,只有一个元素的数组自然而然地就可以视为是有序的,此时就可以进行合并操作了。因此,上面讲的合并两个有序的子数组,是从 只有一个元素 的两个子数组开始合并的。
合并后的元素个数:从 1-->2-->4-->8......
比如初始数组:[19,15,37,12,25]
0 |
1 |
2 |
3 |
4 |
19 |
15 |
37 |
12 |
25 |
第一步:分解
首先将数组拆分成两部分,即19 15 37为一组,12 25为一组,这是第一层,如图所示:
19 |
15 |
37 |
12 |
25 |
第二步:分解
将第一步得到的数组继续分解,19 15为一组,37为一组,12为一组,25为一组,这四组是第二层,如图所示:
19 |
15 |
|
37 |
|
12 |
|
25 |
第三步:分解
将第二步分解得到的数组再次分解,此时只剩下19 15这一组可以分解,所以继续分解,19为一组,15为一组,这两组为第三层,如下图:
19 |
|
15 |
第四步:归并
由于所有组都已经分解成只有1个元素,开始进行归并,从“高层”开始归并,即先归并“第三层”,比较“第三层”两组元素,19 < 15,因此将15排在19前面,由于已经没有元素,结束此次归并,如下图:
15 |
|
19 |
第五步:归并
继续归并,此次归并“第二层”,这一层有4个组,进行两两比较。首先,比较15、19和37:15 < 37,所以15放第一个位置,接着比较19和37,19 < 37,所以19放第二个位置,此时第一组15、19已经没有元素,于是将37填入15和19之后。接着比较:12和25:12 < 25,所以12放第一个位置,由于第一组12已经没有元素,于是将25填入12之后。归并的结果如下:
15 |
19 |
37 |
|
12 |
25 |
第六步:归并
继续归并,此次归并“第一层”,这一组有2个组,第一组:15、19、37,第二组:12、25。同样的,取两组的第1个数比较:15 > 12,所以12放第1个位置;接着取第二组的第2个数比较:15 < 25,所以15放第2个位置;接着取第一组的第2个数比较:19 < 25,所以19放第3个位置;接着取第一组的第3个数比较:37 > 25,所以25放第4个位置;由于第二组已经没有元素,所以37自然归入第5个位置。此时,归并结束,最终数组如下:
12 |
15 |
19 |
25 |
37 |
总结:归并排序先进行从外到内的分解,然后进行从内到外的合并。分解可以通过递归实现,通过改变数组左右两边的索引,完成分解的过程,当分解到最内层(只剩下一个元素,即i==j时),再调用归并函数。
代码实现如下:
package com.atguigu.sort;
import java.util.Arrays;
public class MergeSort {
public static void main(String[] args) {
int[] arr= {8,4,5,7,1,3,6};
int[] temp=new int[arr.length];
mergeSort(arr, 0, arr.length-1, temp);
System.out.println("归并排序后="+Arrays.toString(arr));
}
//分+合
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);
}
}
/**
* @param arr 待排序数组
* @param left 左边有序序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 做中转的数组
*/
//合并的方法
public static void merge(int[] arr,int left,int mid,int right,int[] temp) {
int i=left;//初始化i,i表示左边有序序列的初始索引
int j=mid+1;//初始化j,表示右边有序序列的初始索引
int t=0;//指向temp数组的当前索引
//1.先把左右两边有序的数据按照规则填充到temp数组
//直到左右两边的有序序列,有一边处理完毕为止
while(i<=mid&&j<=right) {
if(arr[i]<=arr[j]) {//左边有序序列的元素小于右边有序序列的元素
temp[t++]=arr[i++];
}
else {
temp[t++]=arr[j++];
}
}
//2.把有剩余数据的一方依次全部填充到temp
while(i<=mid) {//左边有序序列还有数据
temp[t++]=arr[i++];
}
while(j<=right) {//右边有序序列还有数据
temp[t++]=arr[j++];
}
//3.将temp数组的元素拷贝到arr
for(int k=0;k<t;++k) {//并不是每次都拷贝所有数据,
//所以要拷贝到的数组arr的起始位置并不一定从0开始
arr[left+k]=temp[k];
}
}
}
来源:CSDN
作者:我就是个渴望成长的小菜鸡
链接:https://blog.csdn.net/junjunjiao0911/article/details/103935935