算法之路漫漫,还只是个初学者,仅做笔记,有很多不好的地方提前致歉。
代码都已上传到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。
用实际证明:
代码实现
/**
* 假设将算法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性能反倒没原版好。
代码实现
/**
* 快速归并。
*
* <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节所述的对归并排序的三项改进:加快小数组的排序速度,检测数组是否已经有序以及通过在递归中交换参数来避免数组复制。
评估
数组长度数量级越大改进之后的算法性能越明显。
代码实现
-
加快小数组的排序速度
设置一个界限
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
方法时交换两个数组的传参位置来实现两个数组的地位交换
但其实输出结果会发现其实就是对同一串数组进行排序,在每次
merge
方法中输出src数组和dst数组,实际上 每一次merge
中的src都是上一次sort
中修改后,即半边排序完后的dstprivate 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