大家好啊,好久没写博客了,主要是前段时间比较忙,很抱歉很久都没更新了。是时候更新了~
上次讲了快排,这次讲一讲归并排序。首先在正片前多说两句,归并排序用的还是蛮多的,在JAVA API中,归并排序一度是默认的排序方式,当然实际上JAVA里是分了很多种情况考虑的,以Array的sort()为例,当数组长度小于某个值时,其实使用的是插入排序。其他的还有一些判断,在写完排序算法后,我会分析一下JAVA API的排序算法。
归并排序定义
相信学习算法的大家,对“分而治之”这一思想是有充分理解的,其实归并排序也是如此,也是先通过大化小,最后再把小的组合成大的,完成排序。示意图如下:
诸位看完,是不是已经有了算法实现的大概思路?哈哈,需要注意的是,每次要做合并(merge)的两个数组,都是排好序的,我们需要从左侧开始依次比较,填入新的数组中。
代码实现,写了好久,注意看注释~
//首先请先看merge那段代码,再回头看这一段
public void sort(int[] arr,int left,int right){
//递归基:如果左右相等,则返回
if (left==right) return ;
//分割点为(left+right)/2
//那么为何不直接这么写呢?因为int是有限的,相加可能越界,写成下面的写法会安全一些
int division = left +(right-left)/2;
//递归左边的
sort(arr,left,division);
//递归右边的
sort(arr,division+1,right);
//合并,merge!
merge(arr,left,division+1,right);
}
//虽然这一段在下面,但先来看这一段吧,这一段是合并的代码
private void merge(int[] arr,int left,int right,int bound){
//先解释一下入参,int[] arr,要合并的数组
// int left,左指针的位置,标志左数组的起点
// int right,左指针的位置,标志左数组的起点,同时也是左数组的边界
// int bound右边界的位置
int[] tempArr = new int[bound-left+1];//我们需要一个临时数组来存放
int index = 0;//记录临时数组插入位置的指针
int mid = right-1;//左右数组的分割点
int start =left;//记录左数组的起点
//当左指针小于分割点,右指针小于边界时
//此时说明我们还未比较完
while (left<=mid && right<=bound){
//当左数组当前值小于等于右数组当前值,我们把左数组的当前值加入临时数组
//注意,这个等号是稳定性的关键!
if (arr[left]<=arr[right])
tempArr[index++] = arr[left++];
//反之加入右数组的当前值
else
tempArr[index++] = arr[right++];
}
//左右数组往往是不等长的,当一个数组已经加完了之后,我们把另一个数组余下的数全部加入临时数组
//再次强调,能这么做是因为左右数组是分别已经排好序了的
while(left<=mid) tempArr[index++] = arr[left++];
while (right<=bound) tempArr[index++] = arr[right++];
//最后把临时数组的值给原数组
for (int i = 0;i<tempArr.length;i++){
arr[start++] = tempArr[i];
}
}
算法稳定性
归并排序是一种稳定的排序。
时间复杂度
对长度为n的文件,需进行 趟二路归并,每趟归并的时间为O(n),故其时间复杂度无论是在最好情况下还是在最坏情况下均是O(nlgn)。
空间复杂度
需要一个辅助向量来暂存两有序子文件归并的结果,故其辅助空间复杂度为O(n),显然它不是就地排序。
总而言之,
归并排序比较占用内存,但却是一种效率高且稳定的算法。