归并排序
归并排序基于归并这个简单的操作,
归并:即将两个有序的数组归并成一个更大的有序数组
归并地实现:
一种直截了当的方法是将两个不同的有序数组归并到第三个数组中,两个数组中的元素应该都实现了Comparable接口。
实现的方法:创建一个适当大小的数组然后将两个输入数组的元素一个个从小到大放入这个数组中。
原地归并的抽象方法:** 可以先将前半部分排序,再将后半部分排序,然后在数组中移动元素而不需要使用额外的空间**。
merge(a, lo, mid, hi) : 它会将子数组a[lo…mid]和a[mid+1…hi]归并成一个有序的数组并将结果存放再a[lo…hi]中。 它将涉及的所有元素复制到一个辅助数组中,再把归并的结果放回原数组中。
算法概述:
要将一个数组排序,可以先(递归地)将它分成两半分别排序,然后将结果归并起来。
自顶向下的归并排序: 分治思想的体现。如果它能将两个子数组排序,它就能通过归并两个子数组来将整个数组排序。
算法复杂度:
它能够保证将任意长度为N的数组排序所需时间和NlogN成正比;它的主要缺点则是它需要的额外空间和N成正比。
归并排序是一种渐进最优的基于比较排序的算法。
分治的思想
就是将一个复杂问题分解成为两个或更多的相同或相似的子问题,再把子问题分成更小的子问题…直到最后子问题可以简单的直接求解
原问题的解即子问题的解的合并
小知识点:
二叉树的一个基本的组合学性质就是高度为h的树最多只可能由2h个叶子结点,拥有2h个结点的树是完美平衡的,或称为完全树
归并算法的改进
1、对小规模子数组使用插入排序
用不同的方法处理小规模问题能改进大多数递归算法的性能,因为递归会使小规模问题中方法的调用过于频繁,所以改进对它们的处理方法就能改进整个算法。
对排序来说,插入排序或选择排序 在小数组上可能比归并排序更快
使用插入排序处理小规模的子数组(比如长度小于15)一般可以将归并排序的运行时间缩短10%~15%
2、测试数组是否已经有序
我们可以添加一个判断条件,如果a[mid]小于等于a[mid+1] 我们就认为数组已经是有序的并跳过merge()方法。这个改动不影响排序的递归调用,但是任意有序的子数组算法的运行时间变成线性的了。
3、不将元素复制到辅助数组
我们可以节省将数组元素复制到用于归并的辅助数组所用的时间(但空间不行)。要做到这点我们要调用两种排序方法,一种将数据从输入数组排序到辅助数组,一种将数据从辅助数组排序到输入数组。
实现方式:我们要在递归调用的每个层次交换输入数组和辅助数组的角色
小总结
归并是一个 将两个有序的数组归并成为一个更大的有序数组 的操作
归并排序是基于归并操作之上的排序 采用了分治的思想 ,
要像为当前的数组进行排序 首先采用递归 将当前数组分成两个子数组 将两个子数组进行排序后 再采用归并操作 于是乎当前数组也就是一个有序数组
代码展示
1、自顶向下的归并排序 代码分析:
merge方法先将所有元素复制到aux[]中,然后再归并回a[]中。方法在归并时(第二个for循环)进行了4个条件判断:左半边用尽(取右半边的元素)、右半边用尽(取左半边的元素)、右半边的当前元素小于左半边的当前元素(取右半边的元素)以及右半边的当前元素大于等于左半边的元素(取左半边的元素)
public class Merge {
private static Comparable[] aux; //归并所需的辅助数组
public static void sort(Comparable[] a){
aux = new Comparable[a.length]; //一次性分配空间
sort(a, 0, a.length-1);
}
public static void sort(Comparable[] a, int lo, int hi){
if(hi <= lo) return;
int mid = lo + (hi - lo)/2;
sort(a, lo, mid); //将左半边排序
sort(a, mid+1, hi); //将右半边排序
merge(a, lo, mid, hi); //归并结果
}
public static void merge(Comparable[] a, int lo, int mid, int hi){
//将a[lo..mid]和a[mid+1..hi]归并
int i = lo, j = mid+1;
for(int k = lo; k <= hi; k++){ //将a[lo..hi]复制到aux[lo..hi]
aux[k] = a[k];
}
for(int k = lo; k <= hi; k++){ //归并回到a[lo..hi]
if (i > mid) a[k] = aux[j++];
else if (j > hi) a[k] = aux[i++];
else if (less(aux[j], aux[i])) a[k] = aux[j++];
else a[k] = aux[i++];
}
}
public static boolean less(Comparable v, Comparable w){
return v.compareTo(w) < 0;
}
public static void exch(Comparable[] a, int i, int j){
Comparable t = a[i];
a[i] = a[j];
a[j] = t;
}
public static boolean isSorted(Comparable[] a){
for(int i = 1; i < a.length; i++){
if(less(a[i],a[i-1])) return false;
}
return true;
}
public static void show(Comparable[] a){
for(int i = 0; i < a.length; i++){
StdOut.print(a[i] + " ");
}
StdOut.println();
}
public static void main(String[] args) {
String[] a = {"S","O","R","T","E","X","A","M","P","L","E"};
sort(a);
assert isSorted(a);
show(a);
}
}
自底向上的归并排序
背景分析:我们将一个大问题分割成小问题分别解决然后用所有小问题的答案来解决整个大问题。尽管我们考虑的问题是归并两个大数组,实际上我们归并的数组大多数都非常小。
实现归并排序的另一种方法是归并那些微型数组,然后再成对归并得到的子数组,如此这般,知道我们将整个数组归并在一起。
首先我们进行的是两两归并(把每个元素想象成一个大小为1的数组),然后是四四归并,然后是八八的归并,一直下去。在每一轮的归并中,最后一次归并的第二个子数组可能比第一个子数组更小(但对merge()方法不是问题) 如果不是的话所有的归并中两个数组大小应该一样,而在下一轮中子数组的大小会翻倍。
自底向上的归并排序会多次遍历整个数组,根据子数组大小进行两两归并。子数组的大小sz的初始量为1,每次加倍。最后一个子数组的大小只有在数组大小是sz的偶数倍的时候才会等于sz(否则它会比sz小)。
自底向上的归并排序比较适合用链表组织的数据。将链表先按大小为1的子链表进行排序,然后是大小为2的子链表,然后是大小为4的子链表。这种方式只需要重新组织链表链接就能将链表原地排序(不需要创建任何新的链表结点)
/**
* 自底向上的归并排序
*/
public class MergeBU {
private static Comparable[] aux; //归并所需的辅助数组
public static void sort(Comparable[] a){
//进行lgN次两两归并
int N = a.length;
aux = new Comparable[a.length];
for(int sz = 1; sz < N; sz = sz + sz) //sz子数组大小
for(int lo = 0; lo < N - sz; lo += sz+sz) //lo子数组索引
merge(a, lo, lo+sz-1, Math.min(lo+sz+sz-1, N-1));
}
public static void merge(Comparable[] a, int lo, int mid, int hi){
//将a[lo..mid]和a[mid+1..hi]归并
int i = lo, j = mid+1;
for(int k = lo; k <= hi; k++){ //将a[lo..hi]复制到aux[lo..hi]
aux[k] = a[k];
}
for(int k = lo; k <= hi; k++){ //归并回到a[lo..hi]
if (i > mid) a[k] = aux[j++];
else if (j > hi) a[k] = aux[i++];
else if (less(aux[j], aux[i])) a[k] = aux[j++];
else a[k] = aux[i++];
}
}
public static boolean less(Comparable v, Comparable w){
return v.compareTo(w) < 0;
}
public static void exch(Comparable[] a, int i, int j){
Comparable t = a[i];
a[i] = a[j];
a[j] = t;
}
public static boolean isSorted(Comparable[] a){
for(int i = 1; i < a.length; i++){
if(less(a[i],a[i-1])) return false;
}
return true;
}
public static void show(Comparable[] a){
for(int i = 0; i < a.length; i++){
StdOut.print(a[i] + " ");
}
StdOut.println();
}
public static void main(String[] args) {
String[] a = {"S","O","R","T","E","X","A","M","P","L","E"};
sort(a);
assert isSorted(a);
show(a);
}
}