本文仅供个人记录和复习,不用于其他用途
O(N²)复杂度
有冒泡排序、选择排序、插入排序三种。
冒泡排序
- 设i=0,那么比较i和i+1的大小,大的放后面
- ++i,重复上述过程
- 当完成一轮排序后,设i=1,进行第二轮排序
- 若数列长度为n,当进行完n-1次排序后,所得数列便是由小到大排布
选择排序
- 设数列长度为n,从0~n-1中选出最小的数,放到索引0处
- 从1~n-1中选出最小的数,放到索引1处
- 重复上述过程,直到数列结束
插入排序
- 设i=0,比较i与i+1,若i小于i+1,那么二者交换位置
- ++i,将i+1与0~i进行比较,当某个数小于i+1时,将i+1插在它的后面,否则将i+1放在第一位
- 重复上述过程,直到数列结束
O(N*logN)复杂度
有归并排序、快速排序、堆排序、希尔排序四种
归并排序
- 将数列划分为长度为1的多个小块
- 相邻的两个块进行合并,合并时按照由小到大的顺序
- 当前块的长度都为2,合并相邻两个块,按照由小到大排序
- 重复上述过程,直到剩下一个块
快速排序
- 将数列最左边的数作为基准数flag,i和j的初始值为数列左右两边的位置索引
- —j,从右往左进行查找,直到找到一个小于基准数的数
- ++i,从左往右进行查找,直到找到一个大于基准数的数
- 交换a[i]和a[j]的位置
- 交换a[i]和a[left],并将a[i]作为新的基准数
|
|
堆排序
堆排序首先要知道如何生成最小堆,具体的我会在二叉树一章讲解。
- 将数列调整为最小堆
- 将顶部结点删除,并记录到数组中
- 将末尾结点移至顶部,并调整为最小堆
- 重复上述过程,直至堆为空
|
|
希尔排序
进行插入排序时,每次都是和前面的一个数进行比较,步长为1。希尔排序的步长是不断变化的,并且步长都在逐渐减小,到1时便结束。
简单来说,希尔排序将数列分为若干个子列,然后对这些子列分别进行插入排序,随后依次缩小步长进行排序。一般来说,希尔排序的步长每次都是取数列长度的一半,当然也有其他更高效的办法。
|
|
O(N)复杂度
此类算法不在于进行数之间的比较,而是运用巧妙的思想,并且算法思路都是建立在捅排序的基础上。
计数排序
- 建立一个数组,数组长度即为数列中的最大值加一
- 对数列进行遍历,设当前的数值为i,那么
++a[i]
- 遍历结束后,数组中就记录了数列中各个数的出现次数
基数排序
- 若所排序的数为十进制(所有数的位数应该相同,不同的在前面补零),例如101、011、014等
- 建立0~9等十个桶
- 按照数值的个位数,将所有的数放进对应的桶中
- 按顺序倒出所有的数,组成新的数列
- 按照数值的十位数,将所有的数放进对应的桶中
- 按顺序倒出所有的数,组成新的数列
- 重复上述过程,直到最大数值的每一位都遍历完成
如果最大数为3位数,总共有5个数,那么就需要进行3*5次操作,效率比与之前的算法要高很多。
总结
稳定的排序算法:冒泡排序、插入排序、归并排序、计数排序、基数排序
不稳定排序算法:选择排序、快速排序、希尔排序、堆排序
排序算法无优劣之分,看似快速的算法其实运用的环境比较少。一般来说,假如对时间要求不高,我们可以使用插入排序。如果要求了时间,那么便可以使用归并排序。
例题
几乎有序数组
问:已知一个几乎有序的数组,请问用什么方法排序比较好?(注:几乎有序是指当数组排序完毕时,每个元素移动的距离不超过k,而k相对于数组长度来说很小)
首先来看看简单的算法:冒泡排序、选择排序、插入排序。首先,冒泡排序和选择排序与数列的原始排列无关,而对于插入排序而言,原始序列越有序,所花费的时间越少。
至于速度更快的算法,其中归并排序与原始序列无关,其他的也不够稳定,所以我们必须要对算法稍作改进。那么,我便介绍一下改进的堆排序:
- 取0~k-1,建立一个小根堆
- 弹出栈顶并记录,随后加入下一个数k,调整为小根堆
- 重复上述过程
显然,出栈顺序就是排列完毕的数组,由于题目说明了每个元素的移动距离不超过k。那么,如果按照从小到大来排列的话,最小数必在0~k-1中。如此一来,便能够更快得到排序好的数组。
判断重复数
问:判断数组中是否有重复的数,必须保证额外空间复杂度为O(1)。
哈希表可以在遍历数组的过程中,统计所有数出现的次数。不过既然要求了空间复杂度,那么就不能用哈希表了。这里我们还是使用堆排序,不过也得做一些修改,那就是将递归实现的堆排序转变为非递归实现的堆排序。
合并有序数组
问:将两个有序数组合并为一个数组,且第一个数组空间正好可以容纳两个数组的元素。
举个例子:
数组A:1 3 5
数组B:2 4 6
我们将A最大的数和B最大的数进行比较(显然数组A的大小为6),然后将数组B的6拷贝到A[5]
。随后再将A最大的数和B最大的数进行比较,然后将数组A的5拷贝到A[4]
。重复上述过程,直到数组A全部更新完毕,就会得到两个数组合并之后的结果。
这个方法的主要思想就是从后往前拷贝,这样就不用担心拷贝时替换了重要的数据。
荷兰国旗问题
问:对只包含0、1、2的数组进行排序,要求只能使用交换和原地排序,不能用计数排序。
其实这个问题与快速排序很像,我们只需要在数组的两边设置一个0区域和一个2区域,随后遍历整个数组,遇到1就跳到下个数,遇到0和2就放到两边对应的位置。
二维数组查找
问:给定一个排序完毕二维数组,查找数组中是否包含k。
二维数组展开后就是一个矩阵,我们从右上角开始找,如果当前数大于要找的数,那么向左移动一格;如果当前数小于要找到的数,那么向下移动一格。
最短排序数组
问:给定一个数组,查找数组中需要排列的最短子数组长度。
- 从左到右遍历数组,记录当前数的最大值。当某个数小于最大值时,记录这个位置。
- 从右往左遍历数组,记录当前数的最小值。当某个数大于最小值时,记录这个位置。
例如:现在有数列1、5、4、3、2、6、7。从左往右不断的更新最大值,那么4、3、2显然小于5,而6、7大于5,所以记录2的位置。从右往左不断的更新最小值,那么3、4、5显然大于2,而1小于2,所以记录5的位置。
那么5和2中间的5、4、3、2,就是需要排序的最短子数组长度。
最大差值
问:给定一个数组,排序完毕后,返回相邻两数的最大差值。
先找出数组的最小值min和最大值max,然后根据数组的元素个数n,将区间[min, max)分成n个桶,最大值额外放在一个桶中。分完之后,第一个桶中绝对有最小值,最后一个桶中只有最大值。
由于有n+1个桶,只有n的元素,那么必然会在中间出现空桶。事实上,空桶两边的数的差值是最大的,同一个桶中的差值绝对不会大于这个数,所以不用考虑同一个桶的相邻数,只用考虑桶间的相邻数。我们取每一个桶的最小值,减去上一个非空桶的最大值,记录其中的最大差值即可。