本篇文章仅介绍顺序表(即数组)的N种常见的排序算法,以后遇到有意思的算法还会持续补充。其他数据结构的排序不在本文讨论范围内。本文所有算法由C#代码实现,将32位有符号整数int类型(取值范围[-2^31,2^31-1])数组按从小到大排序。
排序算法分类大致如下(不全,有时间就慢慢补充吧)
内部排序:排序期间所有元素均在内存里。
外部排序:排序期间元素无法全部存放在内存中,必须在排序的过程中根据要求不断地在内存,外存中移动。
在正式讨论排序算法前,我们先思考三个问题:
问题一:如何交换两个数的值?
方法一:通过临时变量。另外通过Stack和Queue就不说了。
方法二:通过算数运算。相比方法一,不用开辟临时变量的内存,但是多了几步算数运算的过程。通过算数运算分两种:一种是加减,一种是乘除。其中加减运算通用性较高,其次是乘除运算,因为乘除无法对0进行操作,且比加减运算更容易超出数据类型的最大值限制。下面以加减运算为例,展示代码及过程。如果要换为乘除运算,将“+”替换为“*”,“-”替换为“/”即可。
方法三:通过异或运算。先回顾下C#里的四个逻辑运算符。
- &:位与,都1才1
- |:位或,有1就1
- ^:位异或,不一样为1
- ~:位非,取反
以上是四个逻辑运算符的二进制解释,最简单的方式是下面韦恩图的解释。
- &:与,取两者的交集
- |:或,取两者的并集
- ^:异或,取两者都没有的部分
- ~:非,取反
问题二:如何随机打乱一个数组?
这个问题还是很有意义的,比如在游戏里玩家抽奖前,正面随机顺序展示若干张抽奖卡。
问题三:什么是排序的稳定性?
稳定与不稳定的区别就是相同值的元素排序后是否保留排序前的先后顺序,保留则为稳定。假如只有两个糖果分别分给考试成绩前两名的同学,他们的区别就是下面这样。。。
本文还会用到如下两个方法:
第一种排序:冒泡排序
初学编程的人可能接触到的第一个排序算法就是冒泡排序。所谓的“泡”指的是每一次外层循环所移动到数组后面的一个最大的值。冒泡排序对数组的重新排列是从末尾开始,即按从后往前的方向,将数组元素从大到小排列。下面展示了一种未优化的冒泡排序算法。
上面的排序还有许多可优化的点,下面结合动图展示一种优化后的冒泡排序算法。这里只展示相对通用的优化方式,如果有相对具体的排序数据模型,就可以深度定制优化方案。本文其他排序算法均展示相对通用、易于理解的写法。
冒泡排序的时间复杂度由比较和移动(即交换)的次数决定,下面展示优化的效果
第二种排序:直接插入排序
直接插入排序的核心就是维护一个开头开始的按从小到大排好的局部数组(局部数组初始元素为0号索引),每往后遍历一个新元素,就考虑将之插入到局部数组中,循环往复,使之充满整个数组。
第三种排序:简单选择排序
简单选择排序的核心也是维护一个开头开始的按从小到大排好的局部数组(不过这个局部数组没有初始元素),每次遍历后半部分数组,找出最小的红色数加到局部数组的最后面。从图中两个5可以看出,简单选择排序是不稳定排序。
第四种排序:快速排序
施工中。。。