《算法(第四版)》2.2部分习题

这篇博客详细解答了《算法(第四版)》2.2部分的多项习题,包括算法比较封装、归并排序的改进、快速归并等。博主分享了代码实现并上传到git,提供了部分习题的评估和分析,同时讨论了如何在不同情况下优化归并排序,如检测数组是否有序、避免数组复制等。
摘要由CSDN通过智能技术生成

算法之路漫漫,还只是个初学者,仅做笔记,有很多不好的地方提前致歉。
代码都已上传到git/github,其中包括在力扣上的一些题解(目前较少)以及笔记。

git:https://gitee.com/yayako/algorithm.git

github:https://github.com/yayakoBlessing/algorithm.git

官方库jar包已上传至百度云

链接:https://pan.baidu.com/s/182L-CZHq–NimCPii95zAg
提取码:xgxb

前面的练习题官方网站上基本都有解答
https://algs4.cs.princeton.edu/22mergesort/

算法比较 封装

/**
 * 算法比较(基于不同算法对同一数组的排序的用时比较)
 *
 * @param s 名称
 * @param baseSorts 算法类
 */
public static void timeRandomInput(String[] s, BaseSort[] baseSorts) {
   
  List<Comparable[]> a = new ArrayList<>(T);
  Comparable[] b;
  double[] total, pre = new double[s.length];
  int n;

  // 生成从100~100000数量级长度的测试数组
  for (int i = 2; i <= 5; i++) {
   
    n = (int) Math.pow(10, i);
    System.out.println();
    System.out.println("ArraySize = " + n);
    // 每个数量级的排序都要分别进行T次,所以需要初始化T个相同长度的不同数组
    for (int j = 0; j < T; j++) {
   
      b = ArrayGenerate.random(n);
      a.add(b);
    }
    total = SortCompare.compare(baseSorts, a, n);
    for (int k = 0; k < baseSorts.length; k++) {
   
      System.out.println(s[k] + ":" + total[k] + "\tnow/pre = " + total[k] / pre[k]);
      pre[k] = total[k];
    }
    a.clear();
  }
}

/**
 * 综合比较
 *
 * @param baseSorts 需要进行比较的各算法类型
 * @param a 排序对象数组
 * @param n 数组长度
 * @return 用时数组
 */
public static double[] compare(BaseSort[] baseSorts, List<Comparable[]> a, int n) {
   
  Comparable[] b;
  int j;
  double[] total = new double[baseSorts.length];
  for (int i = 0; i < T; i++) {
   
    b = new Comparable[n];
    j = 0;
    for (BaseSort baseSort : baseSorts) {
   
      System.arraycopy(a.get(i), 0, b, 0, n);
      total[j++] = SortCompare.time(baseSort, b);
    }
  }
  return total;
}


/** 构造随机数组 */
public static Comparable[] random(int n) {
   
  Comparable[] a = new Comparable[n];
  for (int i = 0; i < n; i++) a[i] = Math.random() * 100;
  return a;
}

2.2.8

题目

假设将算法2.4修改为:只要a[mid]<=a[mid+1]就不调用merge()方法,请证明用归并排序处理一个已经有序的数组所需的比较次数是线性级别的。

解答

理论上,根据命题F的证明过程有,当N为2的幂时,比较次数将满足递归T(N)= 2 T(N / 2)+1,其中T(1)= 0。

用实际证明:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sLAtYP0c-1581570783953)(../images/image-20200212142320734.png)]

代码实现

/**
 * 假设将算法2.4修改为:只要a[mid]<=a[mid+1]就不调用merge()方法,请证明用归并排序处理一个已经有序的数组所需的比较次数是线性级别的。
 *
 * @author cyy
 */
public class ex8 {
   
  private static Comparable[] aux;
  private static int ctime;

  public static void main(String[] args) {
   
    for (int n = 100; n <= 100000; n *= 10) {
   
      ctime = 0;
      sort(ArrayGenerate.inorder(n));
      System.out.println("\nArraySize = " + n + "\n比较次数:" + ctime);
    }
  }

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

  private static void sort(Comparable[] a, int l, int r) {
   
    ctime++;
    if (l >= r) return;
    int mid = (l + r) / 2;
    sort(a, l, mid);
    sort(a, mid + 1, r);
    if (Common.less(a[mid], a[mid + 1])) return;
    merge(a, l, mid, r);
  }

  private static void merge(Comparable[] a, int l, int mid, int r) {
   
    int i = l, j = mid + 1;
    System.arraycopy(a, l, aux, l, r - l + 1);
    ctime++;
    for (int k = l; k <= r; k++) {
   
      if (i > mid) a[k] = aux[j++];
      else if (j > r) a[k] = aux[i++];
      else if (Common.less(aux[j], aux[i])) a[k] = aux[j++];
      else a[k] = aux[i++];
    }
  }
}

2.2.10

题目

*快速归并。*实现一个merge()方法,按降序将a[]的后半部分复制到aux[],然后将其归并回a[]中。这样就可以去掉内循环中检测某半边是否用尽的代码。注意:这样的排序产生的结果是不稳定的(请见2.5.1.8节)。

评估

当数组规模越大时,改版后的merge性能反倒没原版好。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mi8EBDxa-1581570861277)(../images/image-20200211223111677.png)]

代码实现

/**
 * 快速归并。
 *
 * <p>实现一个merge()方法,按降序将a[]的后半部分复制到aux[],然后将其归并回a[]中。 这样就可以去掉内循环中检测某半边是否用尽的代码。
 * 注意:这样的排序产生的结果是不稳定的(请见2.5.1.8节)。
 *
 * @author cyy
 */
public class ex10 {
   
  private static Comparable[] aux;

  public static void main(String[] args) {
   
    Comparable[] a = {
   1, 5, 9, 11, 2, 4, 12, 13};
    aux = new Comparable[a.length];
    merge(a, 0, (a.length - 1) / 2, a.length - 1);
    Common.print(a);
  }

  private static void merge(Comparable[] a, int l, int mid, int r) {
   
    for (int i = l; i <= mid; i++) aux[i] = a[i];
    for (int i = mid + 1; i <= r; i++) aux[i] = a[r + mid - i + 1];
    int i = l, j = r;
    for (int k = l; k <= r; k++) {
   
      if (Common.less(aux[i], aux[j])) a[k] = aux[i++];
      else a[k] = aux[j--];
    }
  }
}

2.2.11

题目

*改进。*实现2.2.2节所述的对归并排序的三项改进:加快小数组的排序速度,检测数组是否已经有序以及通过在递归中交换参数来避免数组复制。

评估

数组长度数量级越大改进之后的算法性能越明显。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cy7Lcrxi-1581570783956)(../images/image-20200211220828526.png)]

代码实现

  • 加快小数组的排序速度

    设置一个界限 LIMIT,当要对小于这个 LIMIT的数组进行排序时,直接调用插入排序

    private static final int LIMIT = 15; // 界限
    
    private void sort(Comparable[] a, int l, int r) {
         
      if (r - l + 1 < LIMIT) {
          // 对小规模子数组使用插入排序
        insertion(a, l, r);
        return;
      }
      int mid = (l + r) / 2;
      sort(a, l, mid);
      sort(a, mid + 1, r);
      merge(a, l, mid, r);
    }
    
    private void insertion(Comparable[] a, int l, int r) {
         
      for (int i = l + 1; i <= r; i++)
        for (int j = i; j > l && Common.less(a[j], a[j - 1]); j--) Common.exch(a, j - 1, j);
    }
    
  • 检测数组是否已经有序

    就是题2.2.8中的方法,当测试数组满足 a[mid]<=a[mid+1]时,直接return,不调用 merge方法

    private void sort(Comparable[] a, int l, int r) {
         
      if (l >= r) return;
      int mid = (l + r) / 2;
      sort(a, l, mid);
      sort(a, mid + 1, r);
      if (Common.less(a[mid], a[mid + 1])) return; // 测试数组已经有序跳过merge方法
      merge(a, l, mid, r);
    }
    
  • 通过在递归中交换参数来避免数组复制

    这个很神奇,就这几个字理解起来优点绕,但是理解后会发现其实就是这几个字。

    通过在递归中交换两个数组来避免数组复制

    • 原版中

      一个是主数组对象a,一个是辅助数组对象aux。

      我们是先将a中要比较的元素复制到aux中,再通过对aux中的元素进行比较重新存入a中。

    • 改进后

      这个改进的思路则是通过交换两个数组的地位。

      一个是主数组对象dst,一个是辅助数组对象src。

      merge方法中每次都是把src数组中的元素进行比较再存入dst数组中,但是在递归调用 sort方法时交换两个数组的传参位置来实现两个数组的地位交换
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V80i8EB0-1581570783957)(C:\Users\90598\AppData\Roaming\Typora\typora-user-images\image-20200207151125731.png)]

      但其实输出结果会发现其实就是对同一串数组进行排序,在每次 merge方法中输出src数组和dst数组,实际上 每一次 merge中的src都是上一次 sort中修改后,即半边排序完后的dst

      private void sort(Comparable[] src, Comparable[] dst, int l, int r) {
             
        if (l >= r) return;
        int mid = (l + r) / 2;
        sort(dst, src, l, mid);
        sort(dst, src, mid + 1, r);
        merge(src, dst, l, mid, r);
      }
      
      private void merge(Comparable[] src, Comparable[] dst, int l, int mid, int r) {
             
        int i = l, j = mid + 1;
        // System.arraycopy(a, l, aux, l, r - l + 1); 避免数组复制
        for (int k = l; k <= r; k++) {
             
          if (i > mid) dst[k] = src[j++];
          else if (j > r) dst[k] = src[i++];
          else if (Common.less(src[i], src[j])) dst[k] = src[i++];
          else dst[k] = src[j++];
        }
      }
      
  • 三合一
/**
 * 改进。
 *
 * <p>实现2.2.2节所述的对归并排序的三项改进:加快小数组的排序速度,检测数组是否已经有序以及通过在递归中交换参数来避免数组复制。
 *
 * @author cyy
 */
public class ex11 implements BaseSort {
   
  private static final int LIMIT = 15;

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

  private void sort(Comparable[] src, Comparable[] dst, int l, int r) {
   
    if (r - l 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值