【面试八股总结】排序算法(一):冒泡、选择、插入、快排、希尔、归并

本文详细介绍了冒泡排序、选择排序、插入排序、快速排序、希尔排序和归并排序的基本原理与步骤,以及它们的优化思路。通过实例展示了这些排序算法的工作流程和分治法的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考资料 :阿秀、1.0 十大经典排序算法 | 菜鸟教程 (runoob.com)

一、冒泡排序

        冒泡排序就是把小的元素往前交换或者把大的元素往后交换,比较相邻的两个元素,交换也发生在这两个元素之间。具体步骤:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

优化思路:

        假如从开始的第一对到结尾的最后一对,相邻的元素之间都没有发生交换的操作,这意味着右边的元素总是大于等于左边的元素,此时的数组已经是有序的了,我们无需再对剩余的元素重复比较下去了。

二、选择排序

        选择排序是给每个位置选择当前最小元素,循环遍历n-1次整个数组,每次遍历将最小值交换到遍历起始位置。具体步骤:

  1. 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
  2. 从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾
  3. 以此类推,直到所有元素均排序完毕

三、插入排序

        插入排序是在一个已经有序的小序列基础上,一次插入一个元素。刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。具体步骤:

  1. 从第一个元素开始,该元素可以认为已经被排序

  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描

  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置

  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置

  5. 将新元素插入到该位置后

  6. 重复步骤2~5

四、快速排序

        从数组中选择一个元素作为中轴元素吧,然后把数组中所有小于中轴元素的元素放在其左边,所有大于或等于中轴元素的元素放在其右边,显然,此时中轴元素所处的位置的是有序的。也就是说,我们无需再移动中轴元素的位置。

        从中轴元素开始把大的数组切割成两个小的数组(两个数组都不包含中轴元素),接着通过递归的方式,让中轴元素左边的数组和右边的数组也重复同样的操作,直到数组的大小为1,此时每个元素都处于有序的位置。具体步骤:

  1. 选取第一个数为基准
  2. 将比基准小的数交换到前面,比基准大的数交换到后面
  3. 对左右区间重复第二步,直到各区间只有一个数

五、希尔排序

        希尔排序是插入排序的一种变种,交换不相邻的元素以对数组的局部进行排序。希尔排序的思想是采用插入排序的方法,先让数组中任意间隔为 h 的元素有序,刚开始 h 的大小可以是 h = n / 2,让 h 一直缩小,当 h = 1 时,也就是此时数组中任意间隔为1的元素有序,此时的数组就是有序的了。

举个🌰:

1)h = n / 2 ,进行分组

2)每个组内进行排序 

3)缩小 h = n / 4,进行排序

4)直到 h = 1

六、归并排序  

        归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列:即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

        通过递归的方式将数组一直分割,直到数组的大小为 1,此时只有一个元素,那么该数组就是有序的了,之后再把两个大小为1的数组合并成一个大小为2的数组,再把两个大小为2的合并成4的 … 直到全部小的数组合并起来。具体步骤:

  1. 把长度为n的输入序列分成两个长度为n/2的子序列;
  2. 对这两个子序列分别采用归并排序;
  3. 将两个排序好的子序列合并成一个最终的排序序列。

举个🌰:

图片来源:【算法】排序算法之归并排序 - 知乎 (zhihu.com)

### 各种排序算法的时间复杂度空间复杂度比较 #### 时间复杂度分析 - **冒泡排序**:其时间复杂度为 \( O(N^2) \)[^4]。由于每次都需要两层嵌套循环来交换相邻元素的位置,因此效率较低。 - **选择排序**:同样具有 \( O(N^2) \) 的时间复杂度。尽管它的操作次数可能少于冒泡排序,但它仍然需要多次扫描整个未排序部分。 - **插入排序**:最坏情况下也是 \( O(N^2) \),但在数据接近有序的情况下表现优异,能够达到近似线性的性能[^1]。 - **希尔排序**:改进自插入排序,通过分组减少移动距离,从而降低时间复杂度至大约 \( O(N^{1.3}) \)[^4]。 - **快速排序**:理论上可以实现 \( O(N\log N) \) 的平均时间复杂度,但由于分区不平衡可能导致退化到 \( O(N^2) \)。不过实际应用中因其良好的适应性随机化策略而非常高效。 - **归并排序**:稳定且始终维持 \( O(N\log N) \) 的时间复杂度。即使在处理小型子问题时切换成其他更优的方法也不会改变整体渐进行为。 - **堆排序**:保持致的 \( O(N\log N) \) 性能水平,适合大规模数据集上的原地排序需求。 - **计数排序**:如果输入范围有限,则可获得线性级别 \( O(N + k) \) 的理想效果;这里k代表最大值减去最小值得差加。 - **基数排序**:对于固定长度的关键字序列来说,总耗时大致等于关键字数量乘以其位数\( M \times N\) ,即 \( O(M*N)\)[^2]。 #### 空间复杂度考量 - 多数简单排序冒泡选择以及插入都只需要额外存储几个变量即可完成运算过程,故它们的空间复杂度均为 \( O(1) \)[^3]。 - 归并排序常规版本需开辟同等大小的新数组来进行合并操作,因而拥有较高的辅助存储开销——具体为空间复杂度 \( O(N) \)[^3] 。然而特殊优化版可通过所谓“手摇”技术把这指标降至恒定量级【O(1)]】但代价是增加了计算负担[^3]。 - 快速排序通常是就地执行的,所以它也具备较小的附加内存消耗特性,即 \( O(\log N) \) 或更低(取决于递归深度)。 - 计数排序则依赖于建立频率表结构,这使其所需临时缓冲区规模直接关联待整理数值跨度,最终表现为 \( O(k) \)。 - 基数排序因要创建桶队列暂存各轮次分类后的项目集合,所以总体上也需要较多的工作区域支持,典型情况下的空间复杂度约为 \( O(N) \)。 综上所述,不同场景下应依据实际情况选取最适合的排序方式以平衡速度与资源占用之间的关系。 ```python def bubble_sort(arr): n = len(arr) for i in range(n): swapped = False for j in range(0, n-i-1): if arr[j] > arr[j+1]: arr[j], arr[j+1] = arr[j+1], arr[j] swapped = True if not swapped: break ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值