排序算法
前言
排序算法是使一组无序的序列按照某种从小到大或从大到小的顺序输出的过程,主要分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存,一般说的常用排序均是内部排序。
常用排序算法
常用排序算法分类
常用排序算法根据排序的原理方法一般分为:
- 交换排序
交换排序包括冒泡排序和快速排序 - 选择排序
选择排序包括简单选择排序和堆排序 - 插入排序
插入排序包括直接插入排序和希尔排序 - 归并排序
- 基数排序
常用排序算法时间空间复杂度及稳定性总结
排序方法 | 平均时间复杂度 | 最坏情况 | 最好情况 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n²) | O(n²) | O(n) | O(1) | 稳定 |
快速排序 | O(nlog2n) | O(n²) | O(nlog2n) | O(nlog2n) | 不稳定 |
简单选择排序 | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 |
堆排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(1) | 不稳定 |
直接插入排序 | O(n²) | O(n²) | O(n) | O(1) | 稳定 |
希尔排序 | O(nlog2n) | O(nlog2n) | —— | O(1) | 不稳定 |
归并排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(n) | 稳定 |
基数排序 | O(d(n+r)) | O(d(n+r)) | O(d(n+r)) | O(n+r) | 稳定 |
冒泡排序
冒泡排序
思路
令数组长度为n
1.比较相邻的前后2个数据,如果前面数据大于后面的数据,就将二个数据交换
2.对数组的第0个数据到n-1个数据进行一次遍历后,最大的一个数据就“沉”到数组第n-1个位置
3.令n=n-1,如果n不为0就重复前面二步,否则排序完成
代码实现
1 | // 交换两个数 |
冒泡排序改进1
思路
对冒泡排序常见的改进方法是加入一标志性变量isExitExchange,用于标志某一趟排序过程中是否有数据交换,如果进行某一趟排序时并没有进行数据交换,则说明数据已经按要求排列好,可立即结束排序,避免不必要的比较过程
代码实现
1 | /* 改进的冒泡排序1 |
冒泡排序改进2
思路
进一步的,可以增加记录某一趟遍历最后一次交换的位置,下一次从头开始遍历的时候就可以从记录的位置索引为长度开始跳跃着交换,而不需要每次长度只递减1
代码实现
1 | /*改进的冒泡排序2 |
冒泡排序结果打印
before sorted print:
loop[0]: 1 6 8 9 4 5 6 25 19 3 0 2
sorted process print:
loop[0]: 1 6 8 4 5 6 9 19 3 0 2 25
loop[1]: 1 6 4 5 6 8 9 3 0 2 19 25
loop[2]: 1 4 5 6 6 8 3 0 2 9 19 25
loop[3]: 1 4 5 6 6 3 0 2 8 9 19 25
loop[4]: 1 4 5 6 3 0 2 6 8 9 19 25
loop[5]: 1 4 5 3 0 2 6 6 8 9 19 25
loop[6]: 1 4 3 0 2 5 6 6 8 9 19 25
loop[7]: 1 3 0 2 4 5 6 6 8 9 19 25
loop[8]: 1 0 2 3 4 5 6 6 8 9 19 25
loop[9]: 0 1 2 3 4 5 6 6 8 9 19 25
loop[10]: 0 1 2 3 4 5 6 6 8 9 19 25
loop[11]: 0 1 2 3 4 5 6 6 8 9 19 25
after sorted print:
loop[0]: 0 1 2 3 4 5 6 6 8 9 19 25
快速排序
思路
思想是分治 和递归
- 确定基准值(一般是序列第一个位置、最后一个位置或中间位置)
- 从序列后面开始向前找元素,若小于基准值则交换之,否则继续前向步进,直到找到一个满足条件的值
- 从序列前面开始向后找元素,若大于基准值则交换之(元素与上一次的交换操作的结果进行交换),否则继续向后步进,直到找到一个满足条件的值;
- 重复2~3操作,直到基准值左边的元素均不大于基准值,基准值右边的元素均不小于基准值,分为了左半部分和右半部分
- 分别对左半部分和右半部分重复1~4的操作
代码实现
1 | /*进行经过1~4步操作,并返回基准值的位置*/ |
快速排序结果打印
before sorted print:
loop[0]: 1 6 8 9 4 5 6 25 19 3 0 2
sorted process print:
loop[1]: 0 1 8 9 4 5 6 25 19 3 6
loop[8]: 0 1 2 6 4 5 6 3 8 19 25
loop[2]: 0 1 2 6 4 5 6
loop[7]: 0 1 2 3 4 5 6
loop[3]: 0 1 2 3 4 5
loop[4]: 0 1 2 3 4 5
loop[5]: 0 1 2 3 4 5
loop[10]: 0 1 2 3 4 5 6 6 8 9 19
after sorted print:
loop[0]: 0 1 2 3 4 5 6 6 8 9 19 25
简单选择排序
简单选择排序
思路
直接选择排序和直接插入排序类似,都将数据分为有序区和无序区,所不同的是直接播放排序是将无序区的第一个元素直接插入到有序区以形成一个更大的有序区,而直接选择排序是从无序区选一个最小的元素直接放到有序区的最后
- 初始时,数组全为无序区为a[0~n-1]
- 在无序区a[i
n-1]中选取一个最小的元素,将其与a[i]交换。交换之后a[0i]就形成了一个有序区 - i++并重复第二步直到i—>n-1排序完成
代码实现
1 | //获取长度为len的序列a[] 从索引index开始到len-1范围序列的最小值的索引 |
简单选择排序2
思路
通过依次比较和交换来完成选择最小值
####代码实现
1 | //内层循环从i+1 ~ len中依次与i进行比较,选择最小值,放到索引为i的位置(i = 0 ~ len(外层循环)) |
简单选择排序3(二元选择排序)
思路
通过每次遍历同时选择最小值和最大值,减少遍历次数为n/2
代码实现
1 | //二元选择排序,每次从i ~ len-i中选择最大值和最小值,然后分别放入i和len-i-1的位置(i = 0 ~ len/2) |
简单选择排序结果打印
before sorted print:
loop[0]: 1 6 8 9 4 5 6 25 19 3 0 2
sorted process print:
loop[0]: 0 6 8 9 4 5 6 25 19 3 1 2
loop[1]: 0 1 8 9 4 5 6 25 19 3 6 2
loop[2]: 0 1 2 9 4 5 6 25 19 3 6 8
loop[3]: 0 1 2 3 4 5 6 25 19 9 6 8
loop[4]: 0 1 2 3 4 5 6 25 19 9 6 8
loop[5]: 0 1 2 3 4 5 6 25 19 9 6 8
loop[6]: 0 1 2 3 4 5 6 25 19 9 6 8
loop[7]: 0 1 2 3 4 5 6 6 19 9 25 8
loop[8]: 0 1 2 3 4 5 6 6 8 9 25 19
loop[9]: 0 1 2 3 4 5 6 6 8 9 25 19
loop[10]: 0 1 2 3 4 5 6 6 8 9 19 25
loop[11]: 0 1 2 3 4 5 6 6 8 9 19 25
after sorted print:
loop[0]: 0 1 2 3 4 5 6 6 8 9 19 25
堆排序
二叉堆是完全二叉树或者是近似完全二叉树。二叉堆满足二个特性:
1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值
2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)
当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆
当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆
思路
最小堆排序:(从大到小顺序)
1.建立最小堆
2.交换堆顶(最小值)和末尾位置
3.向下重新调整最小堆(不包括末尾位置)
4.循环2~3步骤(末尾位置不断变小)
代码实现
1 | // 向下调整最小堆:当前位置index, 左右孩子节点分别为2*index+1和2*index+2 |
堆排序结果打印
before sorted print:
loop[0]: 1 6 8 9 4 5 6 25 19 3 0 2
sorted process print:
build min heap result: loop[0]: 0 1 2 9 3 5 6 25 19 6 4 8
loop[11]: 1 3 2 9 4 5 6 25 19 6 8 0
loop[10]: 2 3 5 9 4 8 6 25 19 6 1 0
loop[9]: 3 4 5 9 6 8 6 25 19 2 1 0
loop[8]: 4 6 5 9 19 8 6 25 3 2 1 0
loop[7]: 5 6 6 9 19 8 25 4 3 2 1 0
loop[6]: 6 9 6 25 19 8 5 4 3 2 1 0
loop[5]: 6 9 8 25 19 6 5 4 3 2 1 0
loop[4]: 8 9 19 25 6 6 5 4 3 2 1 0
loop[3]: 9 25 19 8 6 6 5 4 3 2 1 0
loop[2]: 19 25 9 8 6 6 5 4 3 2 1 0
loop[1]: 25 19 9 8 6 6 5 4 3 2 1 0
after sorted print:
loop[0]: 25 19 9 8 6 6 5 4 3 2 1 0
直接插入排序
思路
将一个记录插入到一个已经排序好的序列中,从而得到一个新的,记录数+1的有序序列
即先将有序序列第一个元素当成有序序列,然后从第二个元素开始依次逐个进行插入,直到整个序列有序为止
- 初始时,a[0]自成1个有序区,无序区为a[1~n-1]。令i=1
- 将a[i]并入当前的有序区a[0
i-1]中形成a[0i]的有序区间。 - i++并重复第二步直到i—>n-1排序完成。
代码实现
1 | /*直接的插入排序 |
直接插入排序结果打印
before sorted print:
loop[0]: 1 6 8 9 4 5 6 25 19 3 0 2
sorted process print:
loop[1]: 1 6 8 9 4 5 6 25 19 3 0 2
loop[2]: 1 6 8 9 4 5 6 25 19 3 0 2
loop[3]: 1 6 8 9 4 5 6 25 19 3 0 2
loop[4]: 1 4 6 8 9 5 6 25 19 3 0 2
loop[5]: 1 4 5 6 8 9 6 25 19 3 0 2
loop[6]: 1 4 5 6 6 8 9 25 19 3 0 2
loop[7]: 1 4 5 6 6 8 9 25 19 3 0 2
loop[8]: 1 4 5 6 6 8 9 19 25 3 0 2
loop[9]: 1 3 4 5 6 6 8 9 19 25 0 2
loop[10]: 0 1 3 4 5 6 6 8 9 19 25 2
loop[11]: 0 1 2 3 4 5 6 6 8 9 19 25
after sorted print:
loop[0]: 0 1 2 3 4 5 6 6 8 9 19 25
希尔排序
思路
先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的
代码实现
1 | /* |
希尔排序结果打印
before sorted print:
loop[0]: 1 6 8 9 4 5 6 25 19 3 0 2
sorted process print:
loop[3]: 1 6 8 3 0 2 6 25 19 9 4 5
loop[1]: 1 0 2 3 4 5 6 6 8 9 25 19
loop[0]: 0 1 2 3 4 5 6 6 8 9 19 25
after sorted print:
loop[0]: 0 1 2 3 4 5 6 6 8 9 19 25
归并排序
思路
基本思想是分治
先递归序列拆成多个有序序列,直到每个序列有一个元素,然后再合并有序序列
代码实现
1 | //通用的合并两个有序序列a[len_a]和b[len_b]到最终有序序列res[]中 |
归并排序结果打印
before sorted print:
loop[0]: 1 6 8 9 4 5 6 25 19 3 0 2
sorted process print:
loop[0]: 1 6
loop[0]: 1 6 8
loop[0]: 4 9
loop[0]: 4 5 9
loop[0]: 1 4 5 6 8 9
loop[0]: 6 25
loop[0]: 6 19 25
loop[0]: 0 3
loop[0]: 0 2 3
loop[0]: 0 2 3 6 19 25
loop[0]: 0 1 2 3 4 5 6 6 8 9 19 25
after sorted print:
loop[0]: 0 1 2 3 4 5 6 6 8 9 19 25
线性排序
计数排序
思路
- 找出待排序的数组中最大和最小的元素
- 统计数组中每个值为i的元素出现的次数,存入数组CountArray的第i项
- 对所有的计数累加(从CountArray中的第一个元素开始,每一项和前一项相加)
- 反向填充目标数组:将每个元素i放在新数组的第CountArray(i)项,每放一个元素就将CountArray(i)减去1
代码实现
1 | int getMaxValue(int a[], int len) |
计数排序运行结果打印
before sorted print:
loop[0]: 1 6 8 9 4 5 6 25 19 3 0 2
sorted process print:
loop[1]: 1 6 8 9 4 5 6 25 19 3 0 2
loop[7]: 1 6 8 9 4 5 6 25 19 3 0 2
loop[8]: 1 6 8 9 4 5 6 25 19 3 0 2
loop[9]: 1 6 8 9 4 5 6 25 19 3 0 2
loop[4]: 1 6 8 9 4 5 6 25 19 3 0 2
loop[5]: 1 6 8 9 4 5 6 25 19 3 0 2
loop[6]: 1 6 8 9 4 5 6 25 19 3 0 2
loop[11]: 1 6 8 9 4 5 6 25 19 3 0 2
loop[10]: 1 6 8 9 4 5 6 25 19 3 0 2
loop[3]: 1 6 8 9 4 5 6 25 19 3 0 2
loop[0]: 1 6 8 9 4 5 6 25 19 3 0 2
loop[2]: 1 6 8 9 4 5 6 25 19 3 0 2
after sorted print:
loop[0]: 0 1 2 3 4 5 6 6 8 9 19 25
桶排序
同计数排序一样,桶排序也对待排序序列作了假设,桶排序假设序列由一个随机过程产生,该过程将元素均匀而独立地分布在区间[0,1)上。基本思想是:把区间[0,1)划分成n个相同大小的子区间,称为桶。将n个记录分布到各个桶中去。如果有多于一个记录分到同一个桶中,需要进行桶内排序(可以使用快排)。最后依次把各个桶中的记录列出来记得到有序序列
基数排序
在计数排序中,当k很大时,时间和空间的开销都会增大(可以想一下对序列{12344,33333,6666}用计数排序,此时不但浪费很多空间,而且时间方面还不如比较排序)。于是可以把待排序记录分解成个位(第一位)、十位(第二位)….然后分别以第一位、第二位…对整个序列进行计数排序。这样的话分解出来的每一位不超过9,即用计数排序序列中最大值是9了