vlambda博客
学习文章列表

归并排序里的“分分合合”


归并排序里的“分分合合”


归并排序的思想

归并排序采用分治思想,即分而治之,就是把原问题分解,通过解决小问题从而解决原问题。解决这些小问题我们可以通过递归。

归并排序里的“分分合合”



那归并排序是如何采用分治思想进行排序的呢?

可以分为两步,【分】和【合】

1,【分】:把待排序的数组划分成左右两个区间,左右两个区间继续划分,直到数组不能够再划分为止。这个过程我们可以用递归。

那么问题来了,怎么判断数组无法再进行划分了?

数组中只有一个元素的时候就无法进行划分了。这也是递归的终止条件。(此处敲黑板!!!)

2,【合】:当数组区间不能再划分的时候开始进行有序合并。

我们来看例子进行详解:

归并排序里的“分分合合”

Step1:

定义 low=0(数组的初始位置下标)

high=array.length-1(数组最后一位数据的下标,array.length为数组长度)

mid=(low+high)/2

划分规则如下:

low~mid划分为左区间,mid+1~high划分为右区间,第一次划分完结果如下:

归并排序里的“分分合合”

Step2:

此时还没有到basecase,还可以进行分解,继续划分,
对于左区间  low=low     high=mid
对于右区间  low=mid+1    high=high
这里注意了,等号右边的low   high  mid分别为上一个step中的值
对于左,右区间划分的规则和step1相同。
结果如下:
归并排序里的“分分合合”
目前为止,整个数组已经不能再分解了,因为每一个区间只有一个数据了。

Step3:

现在开始合并(merge
合并就是把两组数据按照大小顺序来排好,这里我们需要借助指针和额外的数组。
额外的数组来存放合并后有序的数据。
这里我们首先把长度为1的数组合并成长度为2数组。
归并排序里的“分分合合”

再把两个长度为2的数组合并成一个数组。

归并排序里的“分分合合”
每一次合并完,需要把额外数组的数据拷贝到原数组相应区间。

归并排序里的“分分合合”


那两个数组区间是怎么合并成一个有序区间的呢?

分别用两个指针指向两个数组初始位置,比较两个指针指向数据的大小,较小的数据排到前面放入新数组后,排完后相应数组的指针向后移动,继续比较,直到两个数组分别到各自数组最后一位。

用以上连两个数组合并成一个数组举例,

归并排序里的“分分合合”


好了,我们整体代码如下:

 public void mergeSort(int[] array){ int[] newArray=new int[array.length]; dealMerge(array,0,array.length-1,newArray); }    //处理整个递归和排序过程 public void dealMerge(int[] array,int low,int high,int[] newArray){ if(low>=high){ return; } int mid=(low+high)/2; //分 dealMerge(array,low,mid,newArray); dealMerge(array,mid+1,high,newArray); sort(array,low,mid,high,newArray); //合       //排序后,将辅助数组中对应区间的数据拷贝到原数组相应区间 for (int i = low; i <=high ; i++) { array[i]=newArray[i]; } } public void sort(int[] array,int low,int mid,int high,int[] newArray){ int i=low; int j=mid+1; int k=low; while(i<=mid && j<=high){ if(array[i]<=array[j]){ newArray[k++]=array[i++]; }else{ newArray[k++]=array[j++]; } } while(i<=mid){ newArray[k++]=array[i++]; } while(j<=high){ newArray[k++]=array[j++]; } }

这里需要讲解一下,合并数组的时候两个指针的边界需要注意下,分别为划分数组时两个数组的区间的范围。

i=low  , i<=mid

j=mid+1 ,j<=high


时间复杂度

首先要明确,归并排序计算时间复杂度的核心在于,排序的时候元素的比较次数;

还是上面的例子,整个归并过程可以用树状图来描述,我们来看下整个函数大致调用过程:

归并排序里的“分分合合”

这里注意,每次执行完两个mergeSort后才开始合并两个数组进行排序,所以分层讨论更容易计算时间复杂度。

 

对于长度为4的数组来说,每次除以2进行划分,总共划分log(4)即2层,每一层数组的个数为2^k(k为层数, 自顶向下从0开始排序),每一层排序需要比较的次数为:每一层数组的个数*数组的长度 =2^k*2^(2-k)=2^2


推导出
对于有n个数据的待排序数组,总共划分为log(n)层;
自顶向下从0开始,第k层总共有2^k个子数组,每一个数组的长度为2^(log(n)-k),在排序的时候每一层的比较次数为:2^k*2^(log(n)-k)=2^log(n)=n;
那么归并排序的时间复杂度为O(nlogn)。
空间复杂度

这个过程中我们申请了n个空间来辅助排序,所以空间复杂度为O(n)。

稳定性

本文写的算法是稳定的,排序后相同的关键字不会改变其相对次序。


归并排序里的“分分合合”



点个在看你最好看