排序(一)
Ⅰ 前言
排序对于任何一个程序员来说,都不会陌生。一般新手学的第一个算法,就是排序,而且大都是冒泡排序。大部分编程语言中,也提供了排序函数。排序在算法中是非常重要的,我将会用三篇文章,讲清楚经典的几个排序算法。
排序算法太多了,很多可能你连名字都没听说过,比如猴子排序、睡眠排序、面条排序等等,这三篇文章着重讲最经典和最常用的几个:冒泡排序、插入排序、选择排序、归并排序、快速排序、基数排序、计数排序、桶排序。我按照时间复杂度把它们分成了三类,对应三篇文章。
| 排序算法 | 时间复杂度 | 是否基于比较 |
|---|---|---|
| 冒泡、插入、选择 | O(n2) | 是 |
| 快排、归并 | O(nlog2n) | 是 |
| 桶、计数、基数 | O(n) | 否 |
Ⅱ 如何分析一个“排序算法”
学习排序算法,我们除了要学习它的算法原理、代码实现之外,更重要的是要学会如何评价、分析一个排序算法。那么,分析一个排序算法要从哪几个方面入手呢?
A. 排序算法的执行效率
对于排序算法执行效率的分析,我们一般会从以下几个方面来衡量:
1.最好情况、最坏情况、平均情况时间复杂度
我们在分析排序算法的时间复杂度时,要分别给出最好情况、最坏情况、平均情况下的时间复杂度。除此之外,你还要说出最好、最坏的时间复杂度对应的要排序的原始数据是什么样的。
如果对时间空间复杂度的计算有迷惑,可以看我下面这篇文章👇
2.时间复杂度的系数、常数、低阶
我们知道,时间复杂度反应的是数据规模 n 很大的时候的一个增长趋势,所以它表示的时候会忽略系数、常数、低阶。但是实际的软件开发中,我们排序的可能是 10 个、 100 个、 1000 个这样规模很小的数据,所以,在对同一阶时间复杂度的排序算法性能对比的时候,我们就要把系数、常数、低阶也考虑进来。
3.比较次数和交换(或移动)次数
前两篇文章中,我讲解的都是基于比较的排序算法。基于比较的排序算法的执行过程,会涉及到两种操作,一种是元素比较大小,另一种是元素交换或移动。所以,如果我们在分析排序算法的执行效率的时候,应该把比较次数和交换(或移动)次数也考虑进去。
B. 排序算法的内存消耗
算法的内存消耗可以通过空间复杂度来衡量,排序算法也不列外。不过,针对排序算法的空间复杂度,我们还引入了一个新的概念,原地排序(Sorted in place)。原地排序算法,就是特指空间复杂度是 O(1) 的排序算法,这篇文章中要讲的四个算法都是原地排序算法。
C. 排序算法的稳定性
针对排序算法,我们还有一个重要的度量指标,稳定性。这个概念是说,如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。
比如一组数据 1, 13, 2, 7, 0, 4, 7,按照大小排序之后就是 0, 1, 2, 4, 7, 7, 13。
这组数据里有两个 7。经过某种排序算法排序后,如果两个 7 的前后顺序没有变,那我们就把这种排序算法叫作稳定的排序算法,如果前后的顺序发生变化,那对应的排序算法就叫作不稳定的排序算法。
这个的意义在哪里呢,既然都是 7 。实际上,在练习的时候我们排序都用的是数字,但是真正应用上,我们要排的可能就是一组对应,根据其键值来排,那么稳定性就很重要了。
比如说,我们现在要给电商交易系统中的“订单”排序。订单有两个属性,一个是下单时间,一个是订单金额。如果我们现在有十万条订单数据,我们希望按照金额从小到大对订单数据进行排序,对于金额相同的订单,我们希望按照下单时间从早到晚排序。对于这样一个需求我们应该怎么做呢?
很容易想到的一个方法是:先按照金额对订单数据进行排序,然后,再遍历排序之后的订单数据,对于每个金额相同的小区间再按照下单时间排序。这种排序思路很容易理解,但是实现起来会很麻烦。
借助稳定排序算法,这个问题可以非常简洁地解决。思路是这样的:我们先按照下单时间给订单排序,排序完成之后,我们用稳定排序算法,按照订单金额重新排序。两遍排序之后,我们得到的订单数据就是按照金额从小到大排序,金额相同的订单按照下单时间从早到晚排序的。
对排序算法的分析有个大概认识之后,我们来看具体的算法。
Ⅲ 冒泡排序(Bubble Sort)
1. 详解
冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。
我用一组数据来展示一遍冒泡排序的整个过程。比如我们要排序 {2, 1, 8, 45, 6}
第一次冒泡:

可以看出,经过一次冒泡操作后, 45 这个元素已经存储在正确的位置上。要想完成所有数据的排序,我们只要进行 5 次这样的冒泡操作就可以了。

实际上,这个冒泡的过程还可以优化。当某次冒泡操作已经没有数据交换时,说明已经达到完全有序,不用再继续执行后续的冒泡操作了。
我用Java简单实现一下这个算法。
package com.tyz.about_sort.core;
public class BubbleSort {
public BubbleSort() {
}
public static void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
boolean isChanged = false; //判断此轮是否有数据交换
for (int j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
isChanged = true;
}
}
if (!isChanged) {
break;
}
for (int k = 0; k < arr.length; k++) {
System.out.print(arr[k] + " ");
}
System.out.println();
}
}
}
测试代码如下,我还是用做例子的一组数据👇
package com.tyz.about_sort.test;
import com.tyz.about_sort.core.BubbleSort;
public class Test {
public static void main(String[] args) {
int[] arr = {
2, 1, 8, 45

最低0.47元/天 解锁文章
1951

被折叠的 条评论
为什么被折叠?



