Algorithms-2.2 Mergesort 归并排序

Mergesort

1 归并排序 Mergesort

在这里插入图片描述

1.1 原地归并在这里插入图片描述

  • 抽象的“原位归并”
  • 需要一个辅助数组记录数据,i和j比较,小的放回原数组,index右移1位,两个子数组继续比较;如果两边数组值一样,取左边数组的
package Chapter02;

public class Merge {
    private static void merge(Comparable[] a,Comparable[] aux,int lo,int mid,int hi){
        assert Riqi.isSorted(a,lo,mid); //precondition: a[lo...mid]   sorted
        assert Riqi.isSorted(a,mid+1,hi); //precondition: a[mid+1...hi]   sorted

        //copy
        for (int k = lo; k <= hi; k++) {
            aux[k] = a[k];

        //merge
        int i = lo, j = mid+1;
            for (int m = lo; m <= hi ; m++) {
                if      (i > mid)                     a[m] = aux[j++];//如果i走到边界,说明左边子数组已经全部移上去了,就把j移上去
                else if (j > hi)                      a[m] = aux[i++];//如果j走到边界,说明右边子数组已经全部移上去了,就把i移上去
                else if (Riqi.less(aux[j],aux[i]))    a[m] = aux[j++];//将j移到原数组,然后j自增
                else                                  a[m] = aux[i++];//将i移到原数组,然后i自增
            }


        assert Riqi.isSorted(a,lo,hi); //precondition: a[lo...hi]   sorted

        }
    }
}

assert

  • assert后跟boolean:在最后表明做了什么,在前面表明要做什么
  • assert默认是禁用的

1.2 自顶向下的归并排序

package Chapter02;

import java.util.Arrays;

public class Merge {
    private static void merge(Comparable[] a,Comparable[] aux,int lo,int mid,int hi){
        assert Riqi.isSorted(a,lo,mid); //precondition: a[lo...mid]   sorted
        assert Riqi.isSorted(a,mid+1,hi); //precondition: a[mid+1...hi]   sorted

        //copy
        for (int k = lo; k <= hi; k++) {
            aux[k] = a[k];
        }

        //merge
        int i = lo, j = mid+1;
        for (int m = lo; m <= hi ; m++) {
            if      (i > mid)                     a[m] = aux[j++];//如果i走到边界,说明左边子数组已经全部移上去了,就把j移上去
            else if (j > hi)                      a[m] = aux[i++];//如果j走到边界,说明右边子数组已经全部移上去了,就把i移上去
            else if (Riqi.less(aux[j],aux[i]))    a[m] = aux[j++];//将j移到原数组,然后j自增
            else                                  a[m] = aux[i++];//将i移到原数组,然后i自增
            }


        assert Riqi.isSorted(a,lo,hi); //precondition: a[lo...hi]   sorted

    }

    //不要将辅助数组在这里创建,会多出许多额外的小数组的花费
    private static void sort(Comparable[] a,Comparable[] aux,int lo,int hi){
        if (hi <= lo) return;//先检查下标,如果检查不通过,后续不运行   false则继续运行后面代码
        int mid = lo + (hi - lo) / 2;
        //反复套娃,直到最小的数组长度变为2
        sort(a,aux,lo,mid); //左半边排序
        sort(a,aux,mid+1,hi);//右半边排序
        merge(a,aux,lo,mid,hi); //归并结果
    }

    private static void sort(Comparable[] a){
        Comparable[] aux = new Comparable[a.length];
        sort(a,aux,0,a.length-1);
    }

    public static void main(String[] args) {
        Comparable[] a = {32,23,1,134,5,23,856,54,90};
        sort(a);
        System.out.println(Arrays.toString(a)); // [1, 5, 23, 23, 32, 54, 90, 134, 856]

    }
}
  • 如果执行了return语句,那么后面的语句将会不执行
    在这里插入图片描述
  • 把原数组不断对半分,直到变成两两一组

1.3 复杂度

在这里插入图片描述

  • 在数据量巨大的情况下,归并排序与插入排序相比具有优越性
  • 合并时最多需要比较N次(比一次,放回原数组一个元素):如果左右没有一边提前排完,就需要N次比较
  • 数组访问次数:merge所需次数是6N(从a拷贝到aux算2次数组访问) —> 2N次用来复制,2N次用来将排好序的元素移动回去,另外最多比较2N次
    在这里插入图片描述
1.3.1 图示法
  • 假设N=2n
    在这里插入图片描述
  • 2n需要不断除以2,共除log2N次,即log2N层
  • 每层归并都需要比较N次,将所有层相加即为总比较次数
1.3.2 代数法

在这里插入图片描述

1.3.2 数学归纳法

在这里插入图片描述

  • 归纳假设
  • log2(2N) = log2(N) + log2(2) = log2(N) + 1

1.4 内存占用

在这里插入图片描述

  • 辅助数组需要占内存一半的空间,所以不能实现真正的原地排序
  • 希尔排序、插入排序等是真正的原地排序

1.5 归并排序的改进

1.5.1 引入插入排序
  • 对于小的数组采用归并排序,不停递归、调用方法使性能损失
  • 可以对小于某个数值的小数组采用插入排序
    在这里插入图片描述
private static final int CUTOFF = 7; //设置判断是否使用插入排序的初始值

    //不要将辅助数组在这里创建,会多出许多额外的小数组的花费
    private static void sort(Comparable[] a,Comparable[] aux,int lo,int hi){
        //if (hi <= lo) return;//先检查下标,如果检查不通过,后续不运行   false则继续运行后面代码
        //小数组使用插入排序
        if(hi <= lo + CUTOFF - 1){  //末项 - 首项 <= 6   数组长度小于7
            Insertion.sort(a, lo, hi);
            return;}

        int mid = lo + (hi - lo) / 2;
        //反复套娃,直到最小的数组长度变为2
        sort(a,aux,lo,mid); //左半边排序
        sort(a,aux,mid+1,hi);//右半边排序
        merge(a,aux,lo,mid,hi); //归并结果
    }
1.5.2 子数组有序则直接复制

在这里插入图片描述

  • 检测待归并的两个子数组是否已经有序,如果有序,则可跳过此轮归并
  • 只需检测前一半最大的数是否小于后一半最小的数
    private static void sort(Comparable[] a,Comparable[] aux,int lo,int hi){
        if (hi <= lo) return;//先检查下标,如果检查不通过,后续不运行   false则继续运行后面代码
        int mid = lo + (hi - lo) / 2;
        //反复套娃,直到最小的数组长度变为2
        sort(a,aux,lo,mid); //左半边排序
        sort(a,aux,mid+1,hi);//右半边排序
        if (!Riqi.less(a[mid+1],a[mid])) return;//如果数组已经有序则直接复制,不再merge    使用!非可以将等于包含进来
        merge(a,aux,lo,mid,hi); //归并结果
    }
1.5.3 节省拷贝到辅助数组的时间
  • 通过再递归中交换参数来避免每次归并时都要复制数组到辅助数组
  • 每轮递归时,转换原数组和辅助数组的角色
    在这里插入图片描述
package Chapter02;

import java.util.Arrays;

public class Merge {
    private static void merge(Comparable[] a,Comparable[] aux,int lo,int mid,int hi){
        assert Riqi.isSorted(a,lo,mid); //precondition: a[lo...mid]   sorted
        assert Riqi.isSorted(a,mid+1,hi); //precondition: a[mid+1...hi]   sorted

        int i = lo, j = mid+1;
        //将a数组归并到aux数组
        for (int m = lo; m <= hi ; m++) {
            if      (i > mid)                     aux[m] = a[j++];//如果i走到边界,说明左边子数组已经全部移上去了,就把j移上去
            else if (j > hi)                      aux[m] = a[i++];//如果j走到边界,说明右边子数组已经全部移上去了,就把i移上去
            else if (Riqi.less(a[j],a[i]))        aux[m] = a[j++];//将j移到原数组,然后j自增
            else                                  aux[m] = a[i++];//将i移到原数组,然后i自增
        }

        assert Riqi.isSorted(a,lo,hi); //precondition: a[lo...hi]   sorted

    }

    private static final int CUTOFF = 7; //设置判断是否使用插入排序的初始值

    //不要将辅助数组在这里创建,会多出许多额外的小数组的花费
    private static void sort(Comparable[] a,Comparable[] aux,int lo,int hi){
 
        if (hi <= lo) return;
        int mid = lo + (hi - lo) / 2;
        sort(aux,a,lo,mid); //左半边排序
        sort(aux,a,mid+1,hi);//右半边排序
        merge(a,aux,lo,mid,hi); //归并  转换a和aux的角色
    }

    private static void sort(Comparable[] a){
        Comparable[] aux = a.clone();
        sort(a,aux,0,a.length-1);
    }

    public static void main(String[] args) {
        Comparable[] a = {32,23,1,134,5,23,856,54,90};
        sort(a);
        System.out.println(Arrays.toString(a)); // [1, 5, 23, 23, 32, 54, 90, 134, 856]

    }
}

2 自底向上的归并排序 bottom-up mergesort

在这里插入图片描述

  • 能够遍历整个序列且不需要递归
package Chapter02;

import javax.swing.*;

public class MergeBU {
    //归并所需的辅助数组
    private static Comparable[] aux;

    private static void merge(Comparable[] a,int lo,int mid,int hi){
        assert Riqi.isSorted(a,lo,mid); //precondition: a[lo...mid]   sorted
        assert Riqi.isSorted(a,mid+1,hi); //precondition: a[mid+1...hi]   sorted

        //copy
        for (int k = lo; k <= hi; k++) {
            aux[k] = a[k];
        }

        //merge
        int i = lo, j = mid+1;
        for (int m = lo; m <= hi ; m++) {
            if      (i > mid)                     a[m] = aux[j++];//如果i走到边界,说明左边子数组已经全部移上去了,就把j移上去
            else if (j > hi)                      a[m] = aux[i++];//如果j走到边界,说明右边子数组已经全部移上去了,就把i移上去
            else if (Riqi.less(aux[j],aux[i]))    a[m] = aux[j++];//将j移到原数组,然后j自增
            else                                  a[m] = aux[i++];//将i移到原数组,然后i自增
        }

        assert Riqi.isSorted(a,lo,hi); //precondition: a[lo...hi]   sorted

    }

    public static void sort(Comparable[] a){
        //进行lgN次两两归并
        int N = a.length;
        Comparable[] aux = new Comparable[N];
        for (int sz = 1; sz < N; sz = sz+sz) {//外循环:子数组长度每轮翻倍
            //内循环:每个子数组归并
            for (int lo = 0; lo < N-sz; lo += sz+sz) { //sz为子数组大小  lo = lo+sz+sz 跳到下一个子数组起始位置
                merge(a,lo,lo+sz-1,Math.min(lo+sz+sz-1,N-1)); //最末子数组也许长度不够sz,长度不够则选数组最后一个元素
            }
        }
    }
}

在这里插入图片描述

  • 时间复杂度为logN,而每一轮需要进行N次比较,因此总复杂度为NlogN

3 排序算法的复杂度 sorting complexity

在这里插入图片描述

  • lower bound就是要找一个最小的比较次数;optimal algorithm是lower bound与upper bound相等时
    在这里插入图片描述
    在这里插入图片描述
  • 全排列这棵树至少有N! 个叶子结点,因为N个不同的主键(元素)会有N! 种不同的排列
  • 二叉树的组合学性质是高度为h的树最多只可能有2h个叶子结点,此时该树为完全树(每一层每个点都有两个叉)
    在这里插入图片描述
  • 因此树的高度大于等于log2(N!),根据Stirling公式,正比于NlgN,即排序算法复杂度的下限
    在这里插入图片描述
    在这里插入图片描述
  • 归并排序时间复杂度最优,但空间复杂度不是最优
  • 以上证明都基于所有元素不同且完全打乱的前提
    在这里插入图片描述

4 比较器 comparators

在这里插入图片描述

  • 比较器接口:对同一数据的不同排序
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
package Chapter02;

import java.util.Comparator;

public class Student {

    //原码见 algs4/Selection
    public static void sort(Object[] a, Comparator comparator){
        int N = a.length;
        for (int i = 0; i < N; i++) {
            for (int j = i; j >0 && Riqi.less(comparator,a[j],a[j-1]); j--) {
                Selection.exch(a,j,j-1);
            }
        }
    }

    //按照不同的键对同一组数据进行排序
    public static final Comparator<Student> BY_NAME = new ByName();
    public static final Comparator<Student> BY_SECTION = new BySection();
    private final String name;//这里的对象要赋值,否则带final报错
    private final int section;


    private static class ByName implements Comparator<Student>{
        public int compare(Student v,Student w){
            return v.name.compareTo(w.name);
        }
    }

    private static class BySection implements Comparator<Student>{
        public int compare(Student v,Student w){
            return v.section - w.section; //这里不会溢出
        }
    }

}

在这里插入图片描述
在这里插入图片描述

  • 根据极坐标排序
  • 用几何方法非常复杂,可以用counterclockwise
    在这里插入图片描述
  • 源码见algs4 --> Point2D
    在这里插入图片描述

5 稳定性 stability

在这里插入图片描述

  • 不是所有排序都能在按section排列的情况下,name仍然按顺序排,这就是stability
  • A stable sort preserves the relative order of items with equal keys (一个稳定的排列是指拥有相同关键字的记录保留其相对顺序)
  • 插入排序和归并排序是稳定的,选择排序和希尔排序不是
    在这里插入图片描述
  • 插入排序是稳定的,因为拥有相同值的两个元素不会交换,即不会越过对方
    在这里插入图片描述
    在这里插入图片描述
  • 长距离交换使得相同值被越过
    在这里插入图片描述
    在这里插入图片描述
  • 归并排序遇到相同值总取左边的,不会打乱顺序
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
算法导论》Hardcover版的引言(Introduction to Algorithms - Hardcover Edition)是一本经典的计算机科学教材。该版本不仅在内容上与平装版相同,还具有精美的硬皮封面,能够更好地保护书籍,并增添一份高质感和专业感。 首先,这本书是由Thomas H. Cormen等四位作者共同编写。他们是计算机科学领域的权威人物,在算法研究和教育方面具有丰富的经验。这本书的目的是为计算机科学专业的学生和从业人员提供系统而全面的算法知识,帮助他们深入理解和应用算法。 《算法导论》Hardcover版首先介绍了算法设计和分析的基础知识,包括分治法、贪婪算法、动态规划和回溯法等。接着,书中详细阐述了各种经典算法,如排序、图算法、字符串匹配、高级数据结构等。此外,书中还介绍了算法的优化技巧和应用领域,例如算法的并行化和近似算法。 与平装版相比,Hardcover版的封面更加美观,书页由高品质纸张制成,更加耐用。这使得读者在长时间研究和使用这本书时,能够更好地保存它的完整性和精美外观。此外,Hardcover版也更加适合作为礼品或收藏品,体现了读者对该书的重视和对算法学习的热爱。 总之,《算法导论》Hardcover版是一本内容丰富、思想深刻的算法教材,通过系统化的介绍和实例,帮助读者深入理解和应用各种算法。同时,Hardcover版的精美外观和耐用性也增强了读者在日常使用和收藏方面的满意度。无论是学习算法的新手还是资深专家,都能从这本书中获得极大的收益。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值