《计算之魂》读书笔记 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)为单位的归并排序(块内部元素已排好序)

    • 观察下面随机序列中相邻的数:


      我们发现大多相邻的数并非大小交替,而是构成多个递增或递减子序列,蒂姆排序正是利用此特性减少操作

    • 大致流程

      1. 找出序列中各个递增和递减的子序列(如太短,二分查找,插入排序)
      2. 把这些子序列放入堆栈中(临时存储)
      3. 按照规则合并这些块(先合并最短,批处理归并,跳跃式预测)
    • 成块插入模拟图:(跳跃式 + 批处理)

    • 实现示例

      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 次比较
  • 结论证明:(根据上面的推导)

    • 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相关推荐

  1. 推荐系统实践读书笔记-04利用用户标签数据

    推荐系统实践读书笔记-04利用用户标签数据 推荐系统的目的是联系用户的兴趣和物品,这种联系需要依赖不同的媒介.GroupLens在一篇文章中表示目前流行的推荐系统基本上通过3种方式联系用户兴趣和物品. ...

  2. C++ Primer 读书笔记04

    C++ Primer 读书笔记04 关联容器 pair 动态内存 静态内存 栈内存 智能指针 shared_ptr 直接管理内存 new 空悬指针 智能指针陷阱 unique_ptr weak_ptr ...

  3. 《计算广告》读书笔记——第一章 在线广告综述

    在线广告, 也称为网络广告. 互联网广告, 指的是在线媒体上投放的广告. 形成了以人群为投放目标. 以产品为导向的技术型投放模式. 在线广告开启了大规模. 自动化地利用数据改善产品和提高收入的先河. ...

  4. mongoDB的读书笔记(04)_【Replica】(01)_Replica的一些基本概念

    数据库分布已经是当下互联网的标准配置.原来单节点标准配置,一台web服务器,一台数据库服务器的1+1模式,可以应对一个小公司或者少量的访问量.而随着服务的提升,对于7×24×365的高可用性的要求的需 ...

  5. 《计算广告》读书笔记

    刘鹏讲座---一文搞懂互联网广告的计算原理 DMP的用户数据从何而来 第一部分 广告市场与背景 什么是广告? 广告的根本目的是广告主通过媒体达到低成本的用户接触.----<当代广告学> 什 ...

  6. 8086中断系统——《x86汇编语言:从实模式到保护模式》读书笔记04

    80X86中断系统 能够处理256个中断 用中断向量号0-255区别 可屏蔽中断还需要借助专用中断控制器Intel 8259A实现优先权管理 1.中断的分类 中断可以分为内部中断和外部中断. (1)内 ...

  7. 机器学习实战 - 读书笔记(04) - 朴素贝叶斯

    核心公式 - 贝叶斯准则 \[p(c|x) = \frac{p(x|c)p(c)}{p(x)}\] p(c|x) 是在x发生的情况下,c发生的概率. p(x|c) 是在c发生的情况下,x发生的概率. ...

  8. 构件之法读书笔记04

    我们前两周我们团队一起制作了一个大学生记账软件,但是我们没有对我们的软件进行测试,只要是这个功能能够顺利进行,我们就觉得OK. 其实,我认为我们的软件是有问题的,对于一些极限的操作能否完成,在各种环境 ...

  9. APUE读书笔记-04文件和目录(1)

    转载于:https://blog.51cto.com/quietheart/762759

最新文章

  1. Python使用tpot获取最优模型、将最优模型应用于交叉验证数据集(5折)获取数据集下的最优表现,并将每一折(fold)的预测结果、概率、属于哪一折与测试集标签、结果、概率一并整合输出为结果文件
  2. 拉普拉斯平滑处理 Laplace Smoothing
  3. Gentoo - X11 forwarding request failed on channel 0
  4. 电子商务人们广泛使用计算机,电子商务基础——PPT课件
  5. pycharm acejumpchar插件
  6. CSU-1975 机器人搬重物(BFS)
  7. 单独像对相对定向元素解析
  8. [转载] 民兵葛二蛋——第27集
  9. JDK8新特性(十四)之日期时间API
  10. SLAM_怎么评价slam建图效果
  11. SQL Server 代码颜色
  12. 最新JAVA+Python+大数据资料分享
  13. 监测 Windows 应用行为
  14. 磷酸氢二钠作用及分子量
  15. 十八.国民技术MCU开发之UART模块LIN模式主从通信交互案例
  16. wlan协议—802.11n—802.11ac 5G和2.4G
  17. NYOJ118 修路方案
  18. Python下数值型与字符型类别变量独热编码(One-hot Encoding)实现
  19. 1682亿!!阿里工程师如何喝着茶创造双11奇迹?
  20. 2022.11.17排序题讲解

热门文章

  1. 关于Tomcat在启动时的socket bind failed 730048 错误
  2. 《Cocos Creator游戏实战》你画我猜中的画板功能
  3. SystemInit()时钟系统初始化函数解析
  4. STM32 SPI读写FLASH
  5. 英国一名28岁女子晋升曾祖母
  6. 《每日论文》ImageNet Classification with Deep Convolutional Neural Networks
  7. Like What You Like: Knowledge Distill via Neuron Selectivity Transfer 论文翻译
  8. java操作word(一)
  9. 35岁的程序员:第30章,表白
  10. Leetcode-1598. 文件夹操作日志搜集器