返回首页返回书籍《Hello 算法》
目录

目录 · CONTENTS

  1. 01序
  2. 02前言
  3. 03关于本书
  4. 04如何使用本书
  5. 05小结
  6. 06初识算法
  7. 07算法无处不在
  8. 08算法是什么
  9. 09小结
  10. 10复杂度分析
  11. 11算法效率评估
  12. 12迭代与递归
  13. 13时间复杂度
  14. 14空间复杂度
  15. 15小结
  16. 16练习
  17. 17数据结构
  18. 18数据结构分类
  19. 19基本数据类型
  20. 20数字编码
  21. 21字符编码
  22. 22小结
  23. 23练习
  24. 24数组与链表
  25. 25数组
  26. 26链表
  27. 27列表
  28. 28内存与缓存
  29. 29小结
  30. 30练习
  31. 31栈与队列
  32. 32栈
  33. 33队列
  34. 34双向队列
  35. 35小结
  36. 36练习
  37. 37哈希表
  38. 38哈希表
  39. 39哈希冲突
  40. 40哈希算法
  41. 41小结
  42. 42练习
  43. 43树
  44. 44二叉树
  45. 45二叉树遍历
  46. 46二叉树数组表示
  47. 47二叉搜索树
  48. 48AVL 树
  49. 49小结
  50. 50练习
  51. 51堆
  52. 52堆
  53. 53建堆操作
  54. 54Top-k 问题
  55. 55小结
  56. 56练习
  57. 57图
  58. 58图
  59. 59图的基础操作
  60. 60图的遍历
  61. 61小结
  62. 62练习
  63. 63搜索
  64. 64二分查找
  65. 65二分查找插入点
  66. 66二分查找边界
  67. 67哈希优化策略
  68. 68重识搜索算法
  69. 69小结
  70. 70练习
  71. 71排序
  72. 72排序算法
  73. 73选择排序
  74. 74冒泡排序
  75. 75插入排序
  76. 76快速排序
  77. 77归并排序
  78. 78堆排序
  79. 79桶排序
  80. 80计数排序
  81. 81基数排序
  82. 82小结
  83. 83练习
  84. 84分治
  85. 85分治算法
  86. 86分治搜索策略
  87. 87构建二叉树问题
  88. 88汉诺塔问题
  89. 89小结
  90. 90练习
  91. 91回溯
  92. 92回溯算法
  93. 93全排列问题
  94. 94子集和问题
  95. 95n 皇后问题
  96. 96小结
  97. 97练习
  98. 98动态规划
  99. 99初探动态规划
  100. 100动态规划问题特性
  101. 101动态规划解题思路
  102. 1020-1 背包问题
  103. 103完全背包问题
  104. 104编辑距离问题
  105. 105小结
  106. 106练习
  107. 107贪心
  108. 108贪心算法
  109. 109分数背包问题
  110. 110最大容量问题
  111. 111最大切分乘积问题
  112. 112小结
  113. 113练习
  114. 114附录
  115. 115编程环境安装
  116. 116一起参与创作
  117. 117术语表
  118. 118参考文献
  119. 119纸质书
第 83 / 119 节

第 83 节 · SECTION 83

练习

知识巩固

选择排序和冒泡排序的前几轮

给定数组 [4, 2, 5, 1, 3],以下都按从小到大排序。

  1. 按选择排序模拟前两轮,写出每轮结束后的数组,并指出此时哪些位置已经确定。
  2. 按冒泡排序模拟第一轮,写出数组状态和交换次数,并指出哪个位置已经确定。

参考答案

  1. 前两轮为:

    轮次 数组状态 说明
    1 [1, 2, 5, 4, 3] 最小元素 1 与首位交换
    2 [1, 2, 5, 4, 3] 数值 2 已位于索引 1,无须交换

    此时前两个位置已经确定,后续只需在 [5, 4, 3] 中继续选择最小元素。

  2. 依次比较相邻元素:4 与 2 交换、4 与 5 不换、5 与 1 交换、5 与 3 交换, 得 [2, 4, 1, 3, 5] ,共 3 次交换;最大元素 5 已移动到数组末尾,因此最后一个位置已经确定。

相同元素的先后次序会变吗

数组 $[2_a, 2_b, 1]$ 中,$2_a$ 和 $2_b$ 的数值相同,但用下标标出了原有次序。

  1. 写出选择排序第一轮结束后的数组。$2_a$ 和 $2_b$ 的相对次序是否改变?
  2. 写出冒泡排序第一轮结束后的数组。$2_a$ 和 $2_b$ 的相对次序是否改变?
  3. 根据前两问,说明两种排序在保持相等元素原有次序方面有什么不同。

参考答案

  1. 选择排序第一轮选出最小元素 1,并与首位 $2_a$ 交换,得 $[1, 2_b, 2_a]$ 。$2_a$ 被换到了 $2_b$ 之后,两者的相对次序已经改变。

  2. 冒泡排序先比较 $2_a$ 和 $2_b$,因为两者相等,不交换;再比较 $2_b$ 和 1 并交换, 第一轮结束后为 $[2_a, 1, 2_b]$ 。$2_a$ 仍在 $2_b$ 之前,相对次序没有改变。

  3. 这个例子中,选择排序会改变相等元素的原有次序;冒泡排序只在左边元素大于右边元素时 交换相邻元素。相等元素之间不交换,因此能保持它们原有的先后次序。

比较计数排序和基数排序

学校要排序许多固定为 8 位的学号。请回答:

  1. 从最低位开始的基数排序需要处理几轮?
  2. 如果把学号直接当作整数使用计数排序,为什么会准备大量没有用到的计数位置?
  3. 根据前两问,如果在计数排序和基数排序之间选择一种处理大量固定为 8 位的学号,你会选择哪一种?为什么?

参考答案

  1. 学号有 8 个数位,因此从最低位到最高位共处理 8 轮;每轮只按 0~9 分组。

  2. 直接计数需要为所有可能的 8 位取值预留位置,但实际学生只使用其中很少一部分, 大多数计数位置会一直为 0。

  3. 应选择基数排序。它利用了“位数固定、每位只有 10 种取值”的特点,只需重复 8 轮稳定分组。 计数排序若把整个 8 位学号当作整数下标,则需要为大量实际不会出现的数值预留计数位置。

编程练习

用归并排序排列数组

给定一个整数数组 nums,请自行实现归并排序,把其中的元素按非递减顺序排列并返回。不要调用语言自带的排序函数。

解题提示

  1. 区间长度不超过 1 时,它已经有序
  2. 从中点把区间分成两半,并分别递归排序
  3. 用两个指针合并两个有序半区,再把结果写回原数组

LeetCode

用计数排序排列整数数组

给定整数数组 nums 和非负整数 $K$,数组中的每个元素都在 $0$ 到 $K$ 之间。

请实现计数排序,把结果按非递减顺序写回 nums 并返回 nums。 不要使用元素之间的大小比较来决定顺序,也不要调用语言自带的排序函数。

解题提示

  1. 因为每个元素都在 0 到 K 之间,可以直接用元素值作为计数数组的索引
  2. 第一遍扫描 nums,令对应位置的计数加 1
  3. 再从 0 到 K 扫描计数数组;数值 x 出现多少次,就向 nums 中连续写入多少个 x