源码地址(gitee)
- 堆排序源码地址:https://gitee.com/BJFyfwl/Lin...
- 其他排序方法源码地址:https://gitee.com/BJFyfwl/Lin...
常见排序算法
插入排序:
- 直接插入排序
- 希尔排序
选择排序:
- 直接选择排序
- 堆排序
交换排序:
- 冒泡排序
- 快速排序
- 归并排序
直接插入排序
- 核心思想:将一个数插入一段有序区间。
希尔排序
核心思想:
- 先分组预排序(使用直接插入排序),使整个数组接近有序(小的数在前,大的数在后)
- 再直接插入排序
- 可以将较大的数快速地移动到后面,适用于大量数据
- gap == 1就相当于直接插入排序。
- gap越大,预排越快,预排序越不接近有序。
- gap越小,预排越慢,预排序越接近有序。
直接选择排序
- 遍历数组,选出最大的数,与最后一个数交换。
- 然后依次遍历选择次大的数,与倒数第二个数交换。
- 如此循环。
堆排序
- 建堆时间是O(N)
- 排序时间是O(logN)
- 堆只存在两种类型:大根堆和小根堆。
- 大根堆:根节点是最大的(即孩子节点 < 父亲节点)
- 小根对:根节点是最小的(即孩子节点 > 父亲节点)
- 堆在逻辑结构上是完全二叉树,在物理结构上还是数组形式。所以实现堆时,仍然以数组来存储。
堆的下标规律
- 设父节点下标为 x,左边子节点下标为 y,右边节点下标为 z。
- y = x*2 + 1;
- z = x*2 + 2;
- x = (x - 1)/ 2 或者 (y-1)/ 2;
堆排序问题一:
- 假设数组数据很大(有10亿个数,大概4G内存),他们存在文件中,取最大的前k个数
- 解决方法:建立一个 k 个数的小根堆,然后将文件中的数依次取出与根节点比较,如果比根节点大,则替换根节点,然后向下调整,如此循环,最后这个小根堆中的数据就是最大的前k个数。
堆排序问题二:
- 堆排序:将一个数组升序排列
- 解决方法:将数组建立小根堆,依次取根节点的数据,然后将根节点数据与末尾数据交换,再删除末尾数据(即删除最小的那个数了),然后再向下调整,如此循环即可。
快速排序
- 时间复杂度 O(N * logN)
- 空间复杂度O(logN)
- 稳定性:不稳定
如何取关键数key
- 先取数组的第一个数、最后一个数和中间位置的数,然后取这三个数中不是最大也不是最小的那个数作为key。
- 这种方法可以避免key直接选到最大值或者最小值。
左右指针法快排
- 一前一后两个指针,选择一个关键数key(一般是开头位或者末位)
- 如果选择开头位为关键数key,则让end向后先走,寻找比key小的数,找到小的数后停下。
- 然后pre向前走,寻找比key大的数,找到后停止。
- 然后交换pre的值和end的值,交换完后,end继续向后移动。
- 一直循环,直到pre和end相遇为止,相遇时的值一定是比key小的,所以交换key和pre的值
- 这样,key的数就会被移动到整个数组的中间值位置,它左边的都比它小,右边的都比它大
- 最后使用递归。
挖坑法快排
- 另外定义一个变量sum,用来保存第一个pre,pre的位置则空出来了。
- 然后end先向前移动,寻找比sum小的数,找到后将end与pre交换,现在end的位置为空。
- 然后pre向前移动,寻找比sum大的数,找到将pre与end交换,现在pre的位置为空。
- 然后再是end向前移动………………
- 这样循环直到pre和end相遇为止,相遇时的位置为空,再将sum的值放到相遇的位置上。
- 这样这个数的左边都是比它小的数,右边都是比它大的数。
- 最后使用递归即可。
前后指针法快排
- 起始位置,pre在第一位,end在第二位。
- 然后end向后移动,寻找比key小的值,找到后,pre++,然后交换pre和end的值。
- 然后继续end向后移动,直到end访问完为止
- 最后在交换pre和key的值
- 这样key这个数的左边都是比它小的数,右边都是比它大的数。
- 最后递归即可。
递归式快排的小区间优化
- 当数据量很大时,在最后几层递归时,会出现大量递归(出现大量递归时可能会栈溢出),但是每个递归处理的数据较小。
- 所以为了避免最后几层的递归,在数据量较小时,直接使用插入排序
- 这样大数据处理的最后几层递归,则变为插入排序,可以减少大量递归,降低处理时间。
非递归快排
- 使用循环代替递归。
- 为了避免栈溢出,自己创建一个栈(malloc创建的空间是在堆中的),使用循环来进行排序,其实质是自己模拟栈的出栈和压栈。
- 然后将单趟排序隔离出来,压栈时将pre和end的下标压栈,出栈将pre和end传给单趟排序,并且将栈中的pre和end消除,
- 循环如此即可排序。
计数排序
- 核心思路:根据所给数组的最大值与小值,开辟等大小的数组,并且初始化为0,遍历数组,将该整数对应下标的新数组的位置加1。(相当于一种映射)
- 例如:给定一个数组 {5,6,7,8,9,6}
- 最大值为9,最小值为5,开辟新数组 arr 大小为 5。
- 遍历数组,第一个数为 5 ,对应下标为 0,即arr[0]++,
- 如此遍历完整个数组即可。
适用范围
- 只适合数据范围比较集中的数组,如果数据集中效率还是很高的。
- 并且只适合整数,浮点数和字符串等都不适合。
归并排序
- 核心思想:将数组切分成两份,如果左边有序,右边也有序,那么两份归并,整个数组都是有序的了。
归并的海量数据处理(外排序)
例如:一个4G的文件,文件中是一些整数,将这些整数排序,系统提供内存为512M
- 将文件分成 8 等分,然后分别将小文件的数据读取到内存中进行排序(不要用归并,因为归并有O(N)的空间复杂度)
- 这样 8 份小文件中的数据都是有序的了。
- 然后再将 8 分小文件两两进行归并,合称 4 份有序文件
- 然后再归并,最后归并成一个有序的大文件。
算法复杂度及稳定性
- 数组中相同的值,排完序后相对位置不变,就是稳定的,否则就是不稳定。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。