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

简介:

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

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项目聚集地”。

相关文章
|
2月前
|
存储 算法 编译器
米哈游面试算法题:有效的括号
米哈游面试算法题:有效的括号
53 0
|
2月前
|
开发框架 算法 搜索推荐
C# .NET面试系列九:常见的算法
#### 1. 求质数 ```c# // 判断一个数是否为质数的方法 public static bool IsPrime(int number) { if (number < 2) { return false; } for (int i = 2; i <= Math.Sqrt(number); i++) { if (number % i == 0) { return false; } } return true; } class Progr
90 1
|
2月前
|
负载均衡 算法 应用服务中间件
面试题:Nginx有哪些负载均衡算法?Nginx位于七层网络结构中的哪一层?
字节跳动面试题:Nginx有哪些负载均衡算法?Nginx位于七层网络结构中的哪一层?
84 0
|
16天前
|
算法 Java
Java面试题:解释垃圾回收中的标记-清除、复制、标记-压缩算法的工作原理
Java面试题:解释垃圾回收中的标记-清除、复制、标记-压缩算法的工作原理
22 1
|
1月前
|
消息中间件 算法 Java
抖音面试:说说延迟任务的调度算法?
Netty 框架是以性能著称的框架,因此在它的框架中使用了大量提升性能的机制,例如 Netty 用于实现延迟队列的时间轮调度算法就是一个典型的例子。使用时间轮调度算法可以实现海量任务新增和取消任务的时间度为 O(1),那么什么是时间轮调度算法呢?接下来我们一起来看。 ## 1.延迟任务实现 在 Netty 中,我们需要使用 HashedWheelTimer 类来实现延迟任务,例如以下代码: ```java public class DelayTaskExample { public static void main(String[] args) { System.ou
33 5
抖音面试:说说延迟任务的调度算法?
|
16天前
|
算法 Java 程序员
Java面试题:解释Java的垃圾回收机制,包括常见的垃圾回收算法。介绍一下Java的垃圾回收算法中的标记-压缩算法。
Java面试题:解释Java的垃圾回收机制,包括常见的垃圾回收算法。介绍一下Java的垃圾回收算法中的标记-压缩算法。
19 0
|
16天前
|
算法 Java 开发者
Java面试题:Java内存探秘与多线程并发实战,Java内存模型及分区:理解Java堆、栈、方法区等内存区域的作用,垃圾收集机制:掌握常见的垃圾收集算法及其优缺点
Java面试题:Java内存探秘与多线程并发实战,Java内存模型及分区:理解Java堆、栈、方法区等内存区域的作用,垃圾收集机制:掌握常见的垃圾收集算法及其优缺点
18 0
|
16天前
|
存储 算法 Java
Java面试题:解释JVM的内存结构,并描述堆、栈、方法区在内存结构中的角色和作用,Java中的多线程是如何实现的,Java垃圾回收机制的基本原理,并讨论常见的垃圾回收算法
Java面试题:解释JVM的内存结构,并描述堆、栈、方法区在内存结构中的角色和作用,Java中的多线程是如何实现的,Java垃圾回收机制的基本原理,并讨论常见的垃圾回收算法
15 0
|
1月前
|
存储 算法 Java
面试高频算法题汇总「图文解析 + 教学视频 + 范例代码」之 二分 + 哈希表 + 堆 + 优先队列 合集
面试高频算法题汇总「图文解析 + 教学视频 + 范例代码」之 二分 + 哈希表 + 堆 + 优先队列 合集
|
1月前
|
数据采集 算法 数据挖掘
LeetCode 题目 80:删除排序数组中的重复项 II【算法面试高频题】
LeetCode 题目 80:删除排序数组中的重复项 II【算法面试高频题】