这一篇要总结的是归并排序,这也是七大排序的最后一种排序算法。
首先来看一下归并排序(Merge Sort) 的基本原理。它的原理是假设初始序列有n个元素,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2个长度为2或1的有序子序列;再两两归并,…… ,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法就称为归并排序。
1、归并排序的示意图
下面用示意图来说明归并排序的过程:
图一:
图二:
2、归并排序的代码
MergeSort.java
public class MergeSort {
public static void main(String[] args) {
int[] list = {50, 10, 90, 30, 70};
System.out.println("************归并排序************");
System.out.println("排序前:");
display(list);
System.out.println("");
System.out.println("排序后:");
mergeSort(list, 0, list.length - 1);
display(list);
}
/**
* 归并排序算法
*
* @param list 待排序的列表
* @param tempList 临时列表
* @param head 列表开始位置
* @param rear 列表结束位置
*/
public static void mergeSort(int[] list, int head, int rear) {
if (head < rear) {
// 取分割位置
int middle = (head + rear) / 2;
// 递归划分列表的左序列
mergeSort(list, head, middle);
// 递归划分列表的右序列
mergeSort(list, middle + 1, rear);
// 列表的合并操作
merge(list, head, middle, rear);
}
}
/**
* 合并操作(列表的两两合并)
*
* @param list
* @param tempList
* @param head
* @param middle
* @param rear
*/
public static void merge(int[] list, int head, int middle, int rear) {
int[] temp = new int[high-low+1];
int i= low;
int j = mid+1;
int k=0;
// 把较小的数先移到新数组中
while(i<=mid && j<=high){
if(a[i]<a[j]){
temp[k++] = a[i++];
}else{
temp[k++] = a[j++];
}
}
// 把左边剩余的数移入数组
while(i<=mid){
temp[k++] = a[i++];
}
// 把右边边剩余的数移入数组
while(j<=high){
temp[k++] = a[j++];
}
// 把新数组中的数覆盖nums数组
for(int x=0;x<temp.length;x++){
a[x+low] = temp[x];
}
}
/**
* 遍历打印
*/
public static void display(int[] list) {
System.out.println("********展示开始********");
if (list != null && list.length > 0) {
for (int num :
list) {
System.out.print(num + " ");
}
System.out.println("");
}
System.out.println("********展示结束********");
}
}
运行结果:
在排序算法中快速排序的效率是非常高的,但是还有种排序算法的效率可以与之媲美,那就是归并排序;
归并排序和快速排序有那么点异曲同工之妙,快速排序:是先把数组粗略的排序成两个子数组,然后递归再粗略分两个子数组,直到子数组里面只有一个元素,那么就自然排好序了,可以总结为先排序再递归;归并排序:先什么都不管,把数组分为两个子数组,一直递归把数组划分为两个子数组,直到数组里只有一个元素,这时候才开始排序,让两个数组间排好序,依次按照递归的返回来把两个数组进行排好序,到最后就可以把整个数组排好序;
算法分析
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
基本思路:
先递归的把数组划分为两个子数组,一直递归到数组中只有一个元素,然后再调用函数把两个子数组排好序,因为该函数在递归划分数组时会被压入栈,所以这个函数真正的作用是对两个有序的子数组进行排序;
基本步骤:
1、判断参数的有效性,也就是递归的出口;
2、首先什么都不管,直接把数组平分成两个子数组;
3、递归调用划分数组函数,最后划分到数组中只有一个元素,这也意味着数组是有序的了;
4、然后调用排序函数,把两个有序的数组合并成一个有序的数组;
5、排序函数的步骤,让两个数组的元素进行比较,把大的/小的元素存放到临时数组中,如果有一个数组的元素被取光了,那就直接把另一数组的元素放到临时数组中,然后把临时数组中的元素都复制到实际的数组中;
时间复杂度
归并的时间复杂度分析:主要是考虑两个函数的时间花销,一、数组划分函数mergeSort();二、有序数组归并函数_mergeSort();
_mergeSort()函数的时间复杂度为O(n),因为代码中有2个长度为n的循环(非嵌套),所以时间复杂度则为O(n);
简单的分析下元素长度为n的归并排序所消耗的时间 T[n]:调用mergeSort()函数划分两部分,那每一小部分排序好所花时间则为 T[n/2],而最后把这两部分有序的数组合并成一个有序的数组_mergeSort()函数所花的时间为 O(n);
公式:T[n] = 2T[n/2] + O(n);
公式就不仔细推导了,可以参考下排序算法之 冒泡排序和快速排序 里面时间复杂度的推导;
所以得出的结果为:T[n] = O( nlogn )
因为不管元素在什么情况下都要做这些步骤,所以花销的时间是不变的,所以该算法的最优时间复杂度和最差时间复杂度及平均时间复杂度都是一样的为:O( nlogn );好像有人说最差的时间复杂度不是O(nlogn),我不知道怎么算出来的,知道的麻烦告知下,谢谢;
空间复杂度
归并的空间复杂度就是那个临时的数组和递归时压入栈的数据占用的空间:n + logn;所以空间复杂度为: O(n)
以时间换空间
我看到网上很多blog分享空间复杂度只有O(1)的归并排序法;因为传统的归并排序所消耗的空间主要是在归并函数(把两个有序的函数合并成一个有序的函数),所以如果要让时间复杂度为 O(1) ,那么也只能在归并函数中做文章了。代码就不列出来了,其主要思想就是借助于快速排序(其实就是相当于归并函数被快速排序函数替换了);这样的方法虽然可以减少内存的消耗,但是却会在时间上带来损失,因为这样时间复杂度却变成了 O(n^2) 了;所以这种方法并不是一个两全其美的idea;
总结
归并排序虽然比较稳定,在时间上也是非常有效的(最差时间复杂度和最优时间复杂度都为 O(nlogn) ),但是这种算法很消耗空间,一般来说在内部排序不会用这种方法,而是用快速排序;外部排序才会考虑到使用这种方法。