《算法》第二章部分程序 part 3
▶ 书中第二章部分程序,加上自己补充的代码,包括各种优化的快排
package package01; import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.StdRandom; public class class01
{
private class01() {} public static void sort1(Comparable[] a) // 基本的快排
{
StdRandom.shuffle(a); // 数组 a 随机化
sortKernel1(a, 0, a.length - 1);
} private static void sortKernel1(Comparable[] a, int lo, int hi) // 快排递归内核
{
if (hi <= lo)
return;
int j = partition1(a, lo, hi);
sortKernel1(a, lo, j - 1); // 用不到 sortKernel1 的返回值
sortKernel1(a, j + 1, hi);
} private static int partition1(Comparable[] a, int lo, int hi) // 快排分组
{
int i = lo, j = hi + 1;
for (Comparable v = a[lo];; exch(a, i, j)) // a[lo] 作为分界值,每次循环前要调整 i 和 j,所以初值要过量
{ // 没有 break 退出时调整两个错位的值
for (i++; i < hi && less(a[i], v); i++); // 寻找大于 a[lo] 的元素
for (j--; j > lo && less(v, a[j]); j--); // 寻找小于 a[lo] 的元素
if (i >= j) // 指针交叉,说明已经排好了
break;
}
exch(a, lo, j); // 安置哨兵
return j; // 返回哨兵的位置
} public static Comparable select(Comparable[] a, int k) // 从数组中选出第 k 小的数字
{
StdRandom.shuffle(a);
int lo = 0, hi = a.length - 1;
for (; lo < hi;)
{
int i = partition1(a, lo, hi); // 用 a[0] 分段,这是函数 partition 返回安置位置的原因
if (i > k) // 左段过长
hi = i - 1;
else if (i < k) // 右段过长
lo = i + 1;
else
return a[i]; // 找到了指定的分位数
}
return a[lo]; // 指针交叉或 k 不合法?
} public static void sort2(Comparable[] a) // 优化版本,三路快排
{
StdRandom.shuffle(a);
sortKernel2(a, 0, a.length - 1);
} private static void sortKernel2(Comparable[] a, int lo, int hi) // 三路路快排的递归内核,没有 partition 程序
{
if (hi <= lo)
return;
int pLess = lo, pEqual = lo + 1, pGreater = hi; // 三个指针分别指向小于部分、等于部分、大于部分,且 pEqual 指向当前检查元素
for (Comparable v = a[lo]; pEqual <= pGreater;)
{
int cmp = a[pEqual].compareTo(v);
if (cmp < 0)
exch(a, pLess++, pEqual++); // 当前元素较小,放到左边
else if (cmp > 0)
exch(a, pEqual, pGreater--); // 当前元素较大,放到右边
else
pEqual++; // 当前元素等于哨兵,仅自增相等部分的指针
}
sortKernel2(a, lo, pLess - 1);
sortKernel2(a, pGreater + 1, hi);
} private static final int INSERTION_SORT_CUTOFF = 8; // 数组规模不大于 8 时使用插入排序 public static void sort3(Comparable[] a) // 优化版本,三采样 - 插入排序
{
sortKernel3(a, 0, a.length - 1); // 没有数组随机化
} private static void sortKernel3(Comparable[] a, int lo, int hi) // 递归内核
{
if (hi <= lo)
return;
if (hi - lo + 1 <= INSERTION_SORT_CUTOFF) // 规模较小,使用插入排序
{
insertionSort(a, lo, hi + 1); //Insertion.sort(a, lo, hi + 1);,教材源码使用 edu.princeton.cs.algs4.Insertion
return;
}
int j = partition3(a, lo, hi);
sortKernel3(a, lo, j - 1);
sortKernel3(a, j + 1, hi);
} private static int partition3(Comparable[] a, int lo, int hi) // 分组
{
exch(a, median3(a, lo, lo + (hi - lo + 1) / 2, hi), lo); // 开头、中间和结尾各找 1 元求中位数作为哨兵 int i = lo, j = hi + 1;
Comparable v = a[lo]; // 注意相比函数 partition1 少了一层循环 for (i++; less(a[i], v); i++)
{
if (i == hi) // 低指针到达右端,所有元素都比哨兵小
{
exch(a, lo, hi); // 哨兵放到最后,返回哨兵位置
return hi;
}
}
for (j--; less(v, a[j]); j--)
{
if (j == lo + 1) // 高指针到达左端,所有元素都比哨兵大
return lo; // 直接返回原哨兵位置
}
for (; i < j;) // 在低指针和高指针之间分组
{
exch(a, i, j);
for (i++; less(a[i], v); i++);
for (j--; less(v, a[j]); j--);
}
exch(a, lo, j);
return j;
} private static final int MEDIAN_OF_3_CUTOFF = 40; // 数组规模较小时不采用三采样 public static void sort4(Comparable[] a) // 优化版本,使用
{
sortKernel4(a, 0, a.length - 1);
} private static void sortKernel4(Comparable[] a, int lo, int hi) // 递归内核
{
int n = hi - lo + 1;
if (n <= INSERTION_SORT_CUTOFF) // 小规模数组用插入排序
{
insertionSort(a, lo, hi + 1);
return;
}
else if (n <= MEDIAN_OF_3_CUTOFF) // 中规模数组用三采样
exch(a, median3(a, lo, lo + n / 2, hi), lo);
else // 大规模数组使用 Tukey ninther 采样,相当于 9 点采样
{
int eps = n / 8, mid = lo + n / 2;
int ninther = median3(a,
median3(a, lo, lo + eps, lo + eps + eps),
median3(a, mid - eps, mid, mid + eps),
median3(a, hi - eps - eps, hi - eps, hi)
);
exch(a, ninther, lo);
} int i = lo, p = lo, j = hi+1, q = hi + 1; // Bentley-McIlroy 三向分组
for (Comparable v = a[lo]; ;)
{
for (i++; i < hi && less(a[i], v); i++);
for (j--; j > lo && less(v, a[j]); j--); if (i == j && eq(a[i], v)) // 指针交叉,单独处理相等的情况
exch(a, ++p, i);
if (i >= j)
break; exch(a, i, j);
if (eq(a[i], v))
exch(a, ++p, i);
if (eq(a[j], v))
exch(a, --q, j);
}
i = j + 1;
for (int k = lo; k <= p; k++)
exch(a, k, j--);
for (int k = hi; k >= q; k--)
exch(a, k, i++);
sortKernel4(a, lo, j);
sortKernel4(a, i, hi);
} private static void insertionSort(Comparable[] a, int lo, int hi) // 公用插入排序
{
for (int i = lo; i < hi; i++)
{
for (int j = i; j > lo && less(a[j], a[j - 1]); j--)
exch(a, j, j - 1);
}
} private static int median3(Comparable[] a, int i, int j, int k) // 计算 3 元素的中位数
{
return less(a[i], a[j]) ?
(less(a[j], a[k]) ? j : less(a[i], a[k]) ? k : i) :
(less(a[k], a[j]) ? j : less(a[k], a[i]) ? k : i);
} private static boolean less(Comparable v, Comparable w)
{
if (v == w) // 相同引用时避免内存访问
return false;
return v.compareTo(w) < 0;
} private static boolean eq(Comparable v, Comparable w)
{
if (v == w) // 相同引用时避免内存访问
return true;
return v.compareTo(w) == 0;
} private static void exch(Object[] a, int i, int j)
{
Object swap = a[i];
a[i] = a[j];
a[j] = swap;
} private static void show(Comparable[] a)
{
for (int i = 0; i < a.length; i++)
StdOut.println(a[i]);
} public static void main(String[] args)
{
In in = new In(args[0]);
String[] a = in.readAllStrings(); class01.sort1(a);
//class01.sort2(a);
//class01.sort3(a);
//class01.sort4(a);
show(a);
/*
for (int i = 0; i < a.length; i++) // 使用函数 select 进行排序,实际上是逐步挑出 a 中排第 1、第 2、第 3 …… 的元素
{
String ith = (String)class01.select(a, i);
StdOut.println(ith);
}
*/
}
}
《算法》第二章部分程序 part 3的更多相关文章
- 《算法》第二章部分程序 part 5
▶ 书中第二章部分程序,加上自己补充的代码,包括利用优先队列进行多路归并和堆排序 ● 利用优先队列进行多路归并 package package01; import edu.princeton.cs.a ...
- 《算法》第二章部分程序 part 4
▶ 书中第二章部分程序,加上自己补充的代码,包括优先队列和索引优先队列 ● 优先队列 package package01; import java.util.Comparator; import ja ...
- 《算法》第二章部分程序 part 2
▶ 书中第二章部分程序,加上自己补充的代码,包括若干种归并排序,以及利用归并排序计算数组逆序数 ● 归并排序 package package01; import java.util.Comparato ...
- 《算法》第二章部分程序 part 1
▶ 书中第二章部分程序,加上自己补充的代码,包括插入排序,选择排序,Shell 排序 ● 插入排序 package package01; import java.util.Comparator; im ...
- javascript数据结构和算法 第二章 (数组) 二
字符串表示的数组 join() 和 toString() 函数返回数组的字符串表示.这两个函数通过将数组中的元素用逗号分隔符切割,返回字符串数组表示. 这里有个样例: var names = [&qu ...
- 第二章--Win32程序运行原理 (部分概念及代码讲解)
学习<Windows程序设计>记录 概念贴士: 1. 每个进程都有赋予它自己的私有地址空间.当进程内的线程运行时,该线程仅仅能够访问属于它的进程的内存,而属于其他进程的内存被屏蔽了起来,不 ...
- java版数据结构与算法第二章数组
数组由一组具有相同类型的数据元素组成,并存储在一组连续存储单元中.一维数组是常量. 二维数组:若一维数组中的数据元素又是一堆数据结构,我们称之为二维数组.二维数组可以看成是n个列向量组成的线性表. 数 ...
- ASP.NET本质论第二章应用程序对象学习笔记1
1.请求的处理参数—上下文对象HttpContext 1) 针对每一次请求,ASP.NET将创建一个处理这次请求所使用的HttpContext对象实例,这个对象实例将用来在ASP.NET服务器的处理过 ...
- 【学习总结】java数据结构和算法-第二章-数据结构和算法概述
总目录链接 [学习总结]尚硅谷2019java数据结构和算法 github:javaDSA 目录 数据结构和算法的关系 几个实际编程中的问题 线性结构和非线性结构 数据结构和算法的关系 几个实际编程中 ...
随机推荐
- Linux/Centos下安装部署phantomjs 及使用
PhantomJS 是一个基于 WebKit 的服务器端 JavaScript API.它全面支持web而不需浏览器支持,其快速,原生支持各种Web标准: DOM 处理, CSS 选择器, JSON, ...
- ThinkPHP 3.1.2 CURD特性 -3
一.ThinkPHP 3 的CURD介绍 (了解) 二.ThinkPHP 3 读取数据 (重点) 对数据的读取 Read $m=new Model('User'); $m=M('User'); ...
- vue中的计算属性中的坑,
new Vue({ el: '#app', data: { msg:'121', val: '', }, computed:{ val:function(){ return 3; } }, }); 这 ...
- Linux ssh服务器配置
配置文件在/etc/sshd_config,注意只有root可rw,其他所有用户权限为---. 配置说明可参考man sshd_config. 如果更改了服务器端口号,并且启用了SELinux,需要执 ...
- jsp页面间对象传递方法
严格的来说不能叫做JSP页面间的对象传递,实际应该是页面间对象共享的方法: 1. 通过servletcontext,也就是application对象了,但这种情况要求在同一个web应用下, ...
- 使用R语言-操作data.frame
1 向一个data.frame指定列插入一列新数据 1.1 插入一列到指定位置 y<-1:4 data1 <-data.frame(x1=c(1,3,5,7), x2=c(2,4,6,8) ...
- 函数,lambda函数,递归函数,内置函数(map,filter),装饰器
1. 集合 主要作用: 去重 关系测试, 交集\差集\并集\反向(对称)差集 2. 元组 只读列表,只有count, index 2 个方法 作用:如果一些数据不想被人修改, 可以存成元组,比如身份证 ...
- java高并发编程(一)
读马士兵java高并发编程,引用他的代码,做个记录. 一.分析下面程序输出: /** * 分析一下这个程序的输出 * @author mashibing */ package yxxy.c_005; ...
- Jmeter(十五)Logic Controllers 之 while Controller
while Controller是控制循环的Controller,条件判断的Controller.先看看官方Demo. while Controller控制它的子对象,直到false为止.并且还提供了 ...
- [UE4]子控件Child Widget顶层容器选择
如果父级容器是Canvas,则可以直接设置尺寸.放到其他widget的时候也会保持设定好的尺寸(而不管父容器是什么类型).