面试前你必须知道的三个排序算法

简介:

今天分享的是三种排序算法,在面试、实际编程中经常会碰到和使用到的,我会带领大家从分析排序算法技巧上以及代码实现上全面理解这一知识点的掌握。

6a552207cb14bdbf2a9a77281300afbe19f1df88

一、如何分析一个「排序算法」

1. 执行效率

① 最好、最坏、平均时间复杂度

在分析算法的好坏时,要分别说出最好、最坏、平均时间复杂度的同时,也要说出最好、最坏时间复杂度对应排序的原始数据是什么样的。

② 复杂度系数、常数、低阶

时间复杂度反应的是数据规模 n 很大的时候的一个增长趋势,它表示的时候会忽略系数、常数、低阶 ,小规模数据除外。

③ 比较次数和移动次数

基于比较的排序算法,在分析算法效率时,我们要考虑到元素的比较和元素的移动。

2. 内存消耗

算法的内存消耗可以通过空间复杂度来衡量,排序算法也不例外。我们引用一个名词叫做「原地排序」,就是指特定空间复杂度是 O(1) 的排序算法。

3. 稳定性

如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变,就叫做「稳定排序」。

二、冒泡排序

1、算法思想

每次冒泡对相邻的两个元素进行比较,看是否满足大小关系,不满足就进行互换,一次冒泡会让至少一个元素移动到它应该在的位置。有 n 个数据,需要重复 n 次。

559868f100a88f0c3d2c3974f0a26bf6c02617cf

2、算法优化

当某次冒泡过程已经没有数据交换时,说明已经达到完全有序,不用再执行后续的冒泡操作。

3、代码实现


1// 冒泡排序,a 表示数组,n 表示数组大小
2public void bubbleSort(int[] a, int n) {
3 if (n <= 1) return;
4
5 for (int i = 0; i < n; ++i) {
6 // 提前退出冒泡循环的标志位
7 boolean flag = false;
8 for (int j = 0; j < n - i - 1; ++j) {
9 if (a[j] > a[j+1]) { // 交换
10 int tmp = a[j];
11 a[j] = a[j+1];
12 a[j+1] = tmp;
13 flag = true; // 表示有数据交换
14 }
15 }
16 if (!flag) break; // 没有数据交换,提前退出
17 }
18}

4、问题思考

①是否为原地排序

冒泡的过程只涉及相邻数据的交换操作,只需要常量级的临时空间,所以它的空间复杂度为 O(1),是一个原地排序算法。

②是否为稳定排序

在冒泡排序中,只有交换才可以改变两个元素的前后顺序。为了保证冒泡排序算法的稳定性,当有相邻的两个元素大小相等的时候,我们不做交换,相同大小的数据在排序前后不会改变顺序,所以冒泡排序是稳定的排序算法。

③最好、最坏以及平均时间复杂度

最好的情况是数据已经排好序,我们只进行一次冒泡排序就可以了,最好时间复杂度为 O(n) 。最坏的情况是,要排序的数据刚好是倒序排列的,我们只进行 n 此冒泡操作,所以最坏的时间复杂度为 O(n²),平均时间复杂度为 O(n²)。

三、插入排序(Insertion Sort)

1、算法思想

我们将元素分为两个区间,未排序区间和已排序区间。我们要做的就是在未排序区间取出元素与已排序区间元素进行比较插入到适当位置,以此类推,直到未排序区间元素为空为止(顺序为从后向前比较

073d2fe3bee59e8aba3726e4babaf66c09c5c6a2

2、代码实现


1// 插入排序,a 表示数组,n 表示数组大小(从小到大进行排序)
2public void insertionSort(int[] a, int n) {
3 //如果数组大小为 1 直接返回
4 if (n <= 1) return;
5 //否则进行插入排序
6 for (int i = 1; i < n; ++i) {
7 int value = a[i];
8 int j = i - 1;
9 // 查找插入的位置
10 for (; j >= 0; --j) {
11 if (a[j] > value) {
12 a[j+1] = a[j]; // 数据移动
13 } else {
14 break;
15 }
16 }
17 a[j+1] = value; // 插入数据
18 }
19}

3、问题思考

① 是否为原地排序?

答:插入排序的运算并不需要额外的存储空间,所以空间复杂度是 O(1),是一个原地排序算法。

② 是否为稳定排序?

答:在插入排序中,对于值相同的元素,我们会将后边出现的元素插入到前边出现的元素的后边,所以插入排序是稳定排序。

③ 最好、最坏、平均时间复杂度?

答:

最好的情况就是数据元素已经排好序,最好的时间复杂度为 O(1) ,

如果数组是倒序的,每次插入都相当于在数组的第一个位置插入新的数据,需要移动大量的数据,最坏的时间复杂度是 O(n²)。

我们在数组中插入数据的平均时间复杂度为 O(n),对于插入排序来说我们每次就相当于数组插入一个新的数据,循环执行n次插入数据,所以平均时间复杂度为 O(n²)。

四、选择排序

1、算法思想

和插入排序有点相似,将在未排序期间寻找到最小的数据,并将其放到已排好区间的元素的尾部。

2、问题思考

① 是否为原地排序

因为,数组中的两个元素需要相互交换,需要用一个变量来存储交换值,选择排序的空间复杂度为O(1),所以,是一种原地排序算法。

② 是否为稳定排序

选择排序每次都要找到剩余未排序元素的最小值,并和前边的元素交换位置,这样破坏了稳定性。所以说,选择排序是一种不稳定的排序算法。

③ 最好、最坏以及平均时间复杂度

选择排序的最好情况就是已经是一组有序数据,最好的时间复杂度为 O(1),最坏的情况就是 O(n²)。平均时间复杂度就是 O(n²)。

3、代码实现


1// 选择排序,a表示数组,n表示数组大小
2 public static void selectionSort(int[] a, int n) {
3 if (n <= 1) return;
4 for (int i = 0; i < n; ++i) {
5 // 查找最小值
6 int minIndex = i;
7 int minValue = a[i];
8 for (int j = i; j < n; ++j) {
9 if (a[j] < minValue) {
10 minValue = a[j];
11 for (int i = 0; i < n - 1; ++i) {
12 // 查找最小值
13 int minIndex = i;
14 for (int j = i + 1; j < n; ++j) {
15 if (a[j] < a[minIndex]) {
16 minIndex = j;
17 }
18 }
19 if (minIndex == i)
20 continue;
21 // 交换
22 int tmp = a[i];
23 a[i] = a[minIndex];
24 a[minIndex] = tmp;
25 }
26 }

五、实际应用中为什么插入排序应用最为广泛

冒泡排序不管怎么优化,元素交换的次数是一个固定值,是原始数据的逆序度。插入排序是同样的,不管怎么优化,元素移动的次数也等于原始数据的逆序度。

从代码实现上来看,冒泡排序的数据交换要比插入排序的数据移动要复杂,冒泡排序需要 3 个赋值操作,而插入排序只需要 1 个。


1冒泡排序中数据的交换操作:
2if (a[j] > a[j+1]) { // 交换
3 int tmp = a[j];
4 a[j] = a[j+1];
5 a[j+1] = tmp;
6 flag = true;
7}
8
9插入排序中数据的移动操作:
10if (a[j] > value) {
11 a[j+1] = a[j]; // 数据移动
12} else {
13 break;
14}

有兴趣的小伙伴可以编几个数据自己测试一下。

虽然冒泡排序和插入排序在在时间复杂度上是一样的,都是 O(n²),我们希望把性能优化做到极致,首选插入排序。

六、重点掌握

今天重点掌握的内容是三种排序的「分析方法」,不必要死记硬背。另一个方面就是实际应用中用到最多就是「插入排序」。


原文发布时间为:2018-11-14

本文作者:不甘平凡的码农

本文来自云栖社区合作伙伴“Web项目聚集地”,了解相关信息可以关注“Web项目聚集地”。

相关文章
|
负载均衡 NoSQL 算法
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
这篇文章是关于Java面试中Redis相关问题的笔记,包括Redis事务实现、集群方案、主从复制原理、CAP和BASE理论以及负载均衡算法和类型。
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
[go 面试] 雪花算法与分布式ID生成
[go 面试] 雪花算法与分布式ID生成
|
11月前
|
算法
面试场景题:如何设计一个抢红包随机算法
本文详细解析了抢红包随机算法的设计与实现,涵盖三种解法:随机分配法、二倍均值法和线段切割法。随机分配法通过逐次随机分配金额确保总额不变,但易导致两极分化;二倍均值法优化了金额分布,使每次抢到的金额更均衡;线段切割法则将总金额视为线段,通过随机切割点生成子金额,手气最佳金额可能更高。代码示例清晰,结果对比直观,为面试中类似算法题提供了全面思路。
1766 16
|
算法 Java 数据库
美团面试:百亿级分片,如何设计基因算法?
40岁老架构师尼恩分享分库分表的基因算法设计,涵盖分片键选择、水平拆分策略及基因法优化查询效率等内容,助力面试者应对大厂技术面试,提高架构设计能力。
美团面试:百亿级分片,如何设计基因算法?
|
算法 Java 数据库
美团面试:百亿级分片,如何设计基因算法?
40岁老架构师尼恩在读者群中分享了关于分库分表的基因算法设计,旨在帮助大家应对一线互联网企业的面试题。文章详细介绍了分库分表的背景、分片键的设计目标和建议,以及基因法的具体应用和优缺点。通过系统化的梳理,帮助读者提升架构、设计和开发水平,顺利通过面试。
美团面试:百亿级分片,如何设计基因算法?
|
算法 Java 数据中心
探讨面试常见问题雪花算法、时钟回拨问题,java中优雅的实现方式
【10月更文挑战第2天】在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。
2315 2
|
机器学习/深度学习 JavaScript 算法
面试中的网红虚拟DOM,你知多少呢?深入解读diff算法
该文章深入探讨了虚拟DOM的概念及其diff算法,解释了虚拟DOM如何最小化实际DOM的更新,以此提升web应用的性能,并详细分析了diff算法的实现机制。
|
消息中间件 存储 算法
这些年背过的面试题——实战算法篇
本文是技术人面试系列实战算法篇,面试中关于实战算法都需要了解哪些内容?一文带你详细了解,欢迎收藏!
|
消息中间件 算法 Java
抖音面试:说说延迟任务的调度算法?
Netty 框架是以性能著称的框架,因此在它的框架中使用了大量提升性能的机制,例如 Netty 用于实现延迟队列的时间轮调度算法就是一个典型的例子。使用时间轮调度算法可以实现海量任务新增和取消任务的时间度为 O(1),那么什么是时间轮调度算法呢?接下来我们一起来看。 ## 1.延迟任务实现 在 Netty 中,我们需要使用 HashedWheelTimer 类来实现延迟任务,例如以下代码: ```java public class DelayTaskExample { public static void main(String[] args) { System.ou
207 5
抖音面试:说说延迟任务的调度算法?
|
JavaScript 算法 索引
【Vue面试题二十三】、你了解vue的diff算法吗?说说看
这篇文章深入分析了Vue中的diff算法,解释了其在新旧虚拟DOM节点比较中的工作机制,包括同层节点比较、循环向中间收拢的策略,并通过实例演示了diff算法的执行过程,同时提供了源码层面的解析,说明了当数据变化时,如何通过Watcher触发patch函数来更新DOM。
【Vue面试题二十三】、你了解vue的diff算法吗?说说看