《计算之魂》读书笔记 04
《计算之魂》读书笔记 04
- 1.4 关于排序的讨论
- 【1.4.3】针对特殊情况,我们是否还有更好的答案?
- 【附录】为什么排序算法的复杂度不可能小于 O(nlogn)O(nlogn)O(nlogn)?
- 【本节思考题】
- 总结
- 参考资料
1.4 关于排序的讨论
- 排序算法在计算机算法中占有重要位置
- 根据时间复杂度,排序算法可分为两类:
- 复杂度为 O(n2)\bm{O(n^2)}O(n2):大多较直观
- 复杂度为 O(nlogn)\bm{O(nlogn)}O(nlogn):执行效率高
- 要理解排序算法,就要先掌握 递归和分治
回顾:通过学习前两小节内容,我们认识了 5 种直观和高效的排序算法
【1.4.3】针对特殊情况,我们是否还有更好的答案?
使用一种排序算法难以兼顾多维需求,因此今天排序算法的改进大多是 混合排序算法
内省排序(Introsort):
快速排序 + 堆排序 + 插入排序
在大多标准函数库(STL)的排序函数中使用
结合三种算法的优点,最坏情况下的运行时间为 O(nlogn)O(nlogn)O(nlogn)
动态演示(from YouTube):
伪代码:
sort(A : array):# 递归深度限制depthLimit = 2xfloor(log(length(A)))introsort(A, depthLimit)introsort(A, depthLimit):n = length(A)if n <= 16:# 数据规模小时(元素数量低于某个阈值),切换到插入排序insertionSort(A)if depthLimit == 0:# 当递归深度超过一定限制时,切换到堆排序heapsort(A)else:# 规模适中,切换到快速排序,p 即 pivotp = partition(A) introsort(A[0 : p - 1], depthLimit - 1)introsort(A[p + 1 : n], depthLimit - 1)
这里,快速排序的 pivot 选取使用了 “三点中值法”,即每次选取的 pivot,是 首、末、中 这三个位置值的中位数,以避免最坏情况发生
实现示例:
import java.io.IOException;public class IntroSort {// the actual data that has to be sortedprivate int a[];// the number of elements in the dataprivate int n;// Constructor to initialize the size of the dataIntroSort(int n) {a = new int[n];this.n = 0;}// The utility function to insert the dataprivate void dataAppend(int temp) {a[n] = temp;n++;}private void swap(int i, int j) {int temp = a[i];a[i] = a[j];a[j] = temp;}// To maxHeap a subtree rooted with node i which is an index in a[]. heapN is size of heapprivate void maxHeap(int i, int heapN, int begin) {int temp = a[begin + i - 1];int child;while (i <= heapN / 2) {child = 2 * i;if (child < heapN&& a[begin + child - 1] < a[begin + child])child++;if (temp >= a[begin + child - 1])break;a[begin + i - 1] = a[begin + child - 1];i = child;}a[begin + i - 1] = temp;}// Function to build the heap (rearranging the array)private void heapify(int begin, int end, int heapN) {for (int i = (heapN) / 2; i >= 1; i--)maxHeap(i, heapN, begin);}// main function to do heapsortprivate void heapSort(int begin, int end) {int heapN = end - begin;// Build heap (rearrange array)this.heapify(begin, end, heapN);// One by one extract an element from heapfor (int i = heapN; i >= 1; i--) {// Move current root to endswap(begin, begin + i);// call maxHeap() on the reduced heapmaxHeap(1, i, begin);}}// function that implements insertion sortprivate void insertionSort(int left, int right) {for (int i = left; i <= right; i++) {int key = a[i];int j = i;// Move elements of arr[0..i-1]while (j > left && a[j - 1] > key) {a[j] = a[j - 1];j--;}a[j] = key;}}// Function for finding the median of the three elementsprivate int findPivot(int a1, int b1, int c1) {int max = Math.max(Math.max(a[a1], a[b1]), a[c1]);int min = Math.min(Math.min(a[a1], a[b1]), a[c1]);int median = max ^ min ^ a[a1] ^ a[b1] ^ a[c1];if (median == a[a1])return a1;if (median == a[b1])return b1;return c1;}// takes the last element as pivotprivate int partition(int low, int high) {int pivot = a[high];// Index of smaller elementint i = (low - 1);for (int j = low; j <= high - 1; j++) {if (a[j] <= pivot) {i++;swap(i, j);}}swap(i + 1, high);return (i + 1);}// The main function that implements Introsort// low --> Starting index,// high --> Ending index,// depthLimit --> recursion levelprivate void sortDataUtil(int begin, int end, int depthLimit) {if (end - begin > 16) {if (depthLimit == 0) {this.heapSort(begin, end);return;}depthLimit = depthLimit - 1;int pivot = findPivot(begin, begin + ((end - begin) / 2) + 1, end);swap(pivot, end);int p = partition(begin, end);sortDataUtil(begin, p - 1, depthLimit);sortDataUtil(p + 1, end, depthLimit);} else {insertionSort(begin, end);}}private void sortData() {int depthLimit = (int) (2 * Math.floor(Math.log(n) / Math.log(2)));this.sortDataUtil(0, n - 1, depthLimit);}private void printData() {for (int i = 0; i < n; i++)System.out.print(a[i] + " ");}public static void main(String args[]) throws IOException {int[] inp = {2, 10, 24, 2, 10, 11, 27, 4, 2, 4, 28, 16, 9, 8, 28, 10, 13, 24, 22, 28, 0, 13, 27, 13, 3, 23, 18, 22, 8, 8};int n = inp.length;IntroSort introsort = new IntroSort(n);for (int i = 0; i < n; i++) {introsort.dataAppend(inp[i]);}introsort.sortData();introsort.printData();} }
给 6000 个元素排序,堆排序、插入排序、快速排序、内省排序 效率对比(ms):
缺点:和堆排序、快速排序一样,内省排序不稳定
- 蒂姆排序(Timsort):
插入排序 + 归并排序
结合插入排序的直观和归并排序的效率,同时改进了归并排序中的顺序比较大小(少做无用功),在 Java 和 Android 操作系统内部使用广泛
最坏情况下的时间复杂度是:O(nlogn)O(nlogn)O(nlogn)
可以看作是以块(run)为单位的归并排序(块内部元素已排好序)
观察下面随机序列中相邻的数:
我们发现大多相邻的数并非大小交替,而是构成多个递增或递减子序列,蒂姆排序正是利用此特性减少操作大致流程:
- 找出序列中各个递增和递减的子序列(如太短,二分查找,插入排序)
- 把这些子序列放入堆栈中(临时存储)
- 按照规则合并这些块(先合并最短,批处理归并,跳跃式预测)
成块插入模拟图:(跳跃式 + 批处理)
实现示例:
public class TimSort {static int MIN_MERGE = 32;public static int minRunLength(int n) {assert n >= 0;// Becomes 1 if any 1 bits are shifted offint r = 0;while (n >= MIN_MERGE) {r |= (n & 1);n >>= 1;}return n + r; }public static void insertionSort(int[] arr, int left, int right) {for (int i = left + 1; i <= right; i++) {int temp = arr[i];int j = i - 1;while (j >= left && arr[j] > temp) {arr[j + 1] = arr[j];j--;}arr[j + 1] = temp;} }// Merge function merges the sorted runs public static void merge(int[] arr, int l, int m, int r) {// Original array is broken in two parts left and right arrayint len1 = m - l + 1, len2 = r - m;int[] left = new int[len1];int[] right = new int[len2];for (int x = 0; x < len1; x++) {left[x] = arr[l + x];}for (int x = 0; x < len2; x++) {right[x] = arr[m + 1 + x];}int i = 0;int j = 0;int k = l;// After comparing, we merge those two array in larger sub arraywhile (i < len1 && j < len2) {if (left[i] <= right[j]) {arr[k] = left[i];i++;} else {arr[k] = right[j];j++;}k++;}// Copy remaining elements of left, if anywhile (i < len1) {arr[k] = left[i];k++;i++;}// Copy remaining elements of right, if anywhile (j < len2) {arr[k] = right[j];k++;j++;} }public static void timSort(int[] arr, int n) {int minRun = minRunLength(MIN_MERGE);// Sort individual subarrays of size RUNfor (int i = 0; i < n; i += minRun) {insertionSort(arr, i, Math.min((i + MIN_MERGE - 1), (n - 1)));}// Merge from size RUN (or 32). It will merge to form size 64, 128, 256 and so onfor (int size = minRun; size < n; size = 2 * size) {// After every merge, we increase left by 2*sizefor (int left = 0; left < n;left += 2 * size) {// Find ending point of left sub array mid+1 is starting point of right sub arrayint mid = left + size - 1;int right = Math.min((left + 2 * size - 1), (n - 1));// Merge sub array arr[left.....mid] & arr[mid+1....right]if (mid < right)merge(arr, left, mid, right);}} }public static void printArray(int[] arr, int n) {for (int i = 0; i < n; i++) {System.out.print(arr[i] + " ");}System.out.print("\n"); }public static void main(String[] args) {int[] arr = {-2, 7, 15, -14, 0, 15, 0, 7, -7, -4, -13, 5, 8, -14, 12};int n = arr.length;System.out.println("Given Array is");printArray(arr, n);timSort(arr, n);System.out.println("After Sorting Array is");printArray(arr, n);} }
【特点】:稳定、便于多列列表排序、应用广泛
【附录】为什么排序算法的复杂度不可能小于 O(nlogn)O(nlogn)O(nlogn)?
对于任意一个序列,其元素有很多排列方式,其中:
- 最小的序列是将其中每一个元素从小到大排好序
假定有序列 a1,a2,...,ai,...,aj,...,aNa_{1},a_{2},...,\bm{a_{i}},...,\bm{a_{j}},...,a_{N}a1,a2,...,ai,...,aj,...,aN 和 a1,a2,...,aj,...,ai,...,aNa_{1},a_{2},...,\bm{a_{j}},...,\bm{a_{i}},...,a_{N}a1,a2,...,aj,...,ai,...,aN,除了在第 i 个和第 j 个位置上的元素彼此互换(i < j),其他元素都相同
- 如果 ai≤aja_{i} ≤ a_{j}ai≤aj,第一个序列就小于第二个序列
- 如果 aj≤aia_{j} ≤ a_{i}aj≤ai,则第二个序列小于第一个序列
假如我们做 k 次比较,最多能区分出 2k\bm{2^k}2k 种不同序列的大小
- 如果我们有 M 种序列,要区分出它们的大小,需要 logM 次比较
- 如果我们有 M 种序列,要区分出它们的大小,需要 logM 次比较
结论证明:(根据上面的推导)
- NNN 个元素组成的数组能排列出 N!N!N! 种序列
- 因此,要选出其中最小的一个,至少需要 logN!logN!logN! 次比较
- 使用斯特林公式:lnN!=NlnN−N+O(lnN)lnN!=NlnN−N+O(lnN)lnN!=NlnN−N+O(lnN)
- 可以得到复杂度:logN!=O(NlogN)logN!=O(NlogN)logN!=O(NlogN)
- 综上,任何排序算法的复杂度都不可能小于 O(nlogn)O(nlogn)O(nlogn)
【本节思考题】
- 已完成,详见上周笔记
总结
通过阅读 1.4.3,我们了解了在一些特殊情况下(出于多维要求),是需要寻找一些更好的排序算法的(混合排序算法)。同时,如果能结合不同排序算法的特点去不断 “强化” 少做无用功原则,就能慢慢摸索到一些算法的敲门,从而找到一点感觉了。
至此,第一章学习结束。我们主要阅读学习了计算机软硬件的发展史、大O表示法、复杂度及数量级概念、逆向思维、少做无用功原则、递归和分治思想、直观和高效的排序算法等内容。最后,感谢吴军老师的开源授权,以及DW社区的积极组织。
参考资料
- 《计算之魂》第一章
- 【Introspective sort】
- 【TimSort】
- 《STL源码剖析》
- 《计算之魂》读书笔记 03
《计算之魂》读书笔记 04相关推荐
- 推荐系统实践读书笔记-04利用用户标签数据
推荐系统实践读书笔记-04利用用户标签数据 推荐系统的目的是联系用户的兴趣和物品,这种联系需要依赖不同的媒介.GroupLens在一篇文章中表示目前流行的推荐系统基本上通过3种方式联系用户兴趣和物品. ...
- C++ Primer 读书笔记04
C++ Primer 读书笔记04 关联容器 pair 动态内存 静态内存 栈内存 智能指针 shared_ptr 直接管理内存 new 空悬指针 智能指针陷阱 unique_ptr weak_ptr ...
- 《计算广告》读书笔记——第一章 在线广告综述
在线广告, 也称为网络广告. 互联网广告, 指的是在线媒体上投放的广告. 形成了以人群为投放目标. 以产品为导向的技术型投放模式. 在线广告开启了大规模. 自动化地利用数据改善产品和提高收入的先河. ...
- mongoDB的读书笔记(04)_【Replica】(01)_Replica的一些基本概念
数据库分布已经是当下互联网的标准配置.原来单节点标准配置,一台web服务器,一台数据库服务器的1+1模式,可以应对一个小公司或者少量的访问量.而随着服务的提升,对于7×24×365的高可用性的要求的需 ...
- 《计算广告》读书笔记
刘鹏讲座---一文搞懂互联网广告的计算原理 DMP的用户数据从何而来 第一部分 广告市场与背景 什么是广告? 广告的根本目的是广告主通过媒体达到低成本的用户接触.----<当代广告学> 什 ...
- 8086中断系统——《x86汇编语言:从实模式到保护模式》读书笔记04
80X86中断系统 能够处理256个中断 用中断向量号0-255区别 可屏蔽中断还需要借助专用中断控制器Intel 8259A实现优先权管理 1.中断的分类 中断可以分为内部中断和外部中断. (1)内 ...
- 机器学习实战 - 读书笔记(04) - 朴素贝叶斯
核心公式 - 贝叶斯准则 \[p(c|x) = \frac{p(x|c)p(c)}{p(x)}\] p(c|x) 是在x发生的情况下,c发生的概率. p(x|c) 是在c发生的情况下,x发生的概率. ...
- 构件之法读书笔记04
我们前两周我们团队一起制作了一个大学生记账软件,但是我们没有对我们的软件进行测试,只要是这个功能能够顺利进行,我们就觉得OK. 其实,我认为我们的软件是有问题的,对于一些极限的操作能否完成,在各种环境 ...
- APUE读书笔记-04文件和目录(1)
转载于:https://blog.51cto.com/quietheart/762759
最新文章
- Python使用tpot获取最优模型、将最优模型应用于交叉验证数据集(5折)获取数据集下的最优表现,并将每一折(fold)的预测结果、概率、属于哪一折与测试集标签、结果、概率一并整合输出为结果文件
- 拉普拉斯平滑处理 Laplace Smoothing
- Gentoo - X11 forwarding request failed on channel 0
- 电子商务人们广泛使用计算机,电子商务基础——PPT课件
- pycharm acejumpchar插件
- CSU-1975 机器人搬重物(BFS)
- 单独像对相对定向元素解析
- [转载] 民兵葛二蛋——第27集
- JDK8新特性(十四)之日期时间API
- SLAM_怎么评价slam建图效果
- SQL Server 代码颜色
- 最新JAVA+Python+大数据资料分享
- 监测 Windows 应用行为
- 磷酸氢二钠作用及分子量
- 十八.国民技术MCU开发之UART模块LIN模式主从通信交互案例
- wlan协议—802.11n—802.11ac 5G和2.4G
- NYOJ118 修路方案
- Python下数值型与字符型类别变量独热编码(One-hot Encoding)实现
- 1682亿!!阿里工程师如何喝着茶创造双11奇迹?
- 2022.11.17排序题讲解
热门文章
- 关于Tomcat在启动时的socket bind failed 730048 错误
- 《Cocos Creator游戏实战》你画我猜中的画板功能
- SystemInit()时钟系统初始化函数解析
- STM32 SPI读写FLASH
- 英国一名28岁女子晋升曾祖母
- 《每日论文》ImageNet Classification with Deep Convolutional Neural Networks
- Like What You Like: Knowledge Distill via Neuron Selectivity Transfer 论文翻译
- java操作word(一)
- 35岁的程序员:第30章,表白
- Leetcode-1598. 文件夹操作日志搜集器