排序算法

源码地址(gitee)

常见排序算法

  • 插入排序:

    • 直接插入排序
    • 希尔排序
  • 选择排序:

    • 直接选择排序
    • 堆排序
  • 交换排序:

    • 冒泡排序
    • 快速排序
  • 归并排序

直接插入排序

  • 核心思想:将一个数插入一段有序区间。

希尔排序

  • 核心思想:

    • 先分组预排序(使用直接插入排序),使整个数组接近有序(小的数在前,大的数在后)
    • 再直接插入排序
  • 可以将较大的数快速地移动到后面,适用于大量数据
  • gap == 1就相当于直接插入排序。
  • gap越大,预排越快,预排序越不接近有序。
  • gap越小,预排越慢,预排序越接近有序。
    排序算法_第1张图片

直接选择排序

  • 遍历数组,选出最大的数,与最后一个数交换。
  • 然后依次遍历选择次大的数,与倒数第二个数交换。
  • 如此循环。

堆排序

  • 建堆时间是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直接选到最大值或者最小值。

左右指针法快排

排序算法_第2张图片

  • 一前一后两个指针,选择一个关键数key(一般是开头位或者末位)
  • 如果选择开头位为关键数key,则让end向后先走,寻找比key小的数,找到小的数后停下。
  • 然后pre向前走,寻找比key大的数,找到后停止。
  • 然后交换pre的值和end的值,交换完后,end继续向后移动。
  • 一直循环,直到pre和end相遇为止,相遇时的值一定是比key小的,所以交换key和pre的值
  • 这样,key的数就会被移动到整个数组的中间值位置,它左边的都比它小,右边的都比它大
  • 最后使用递归。

挖坑法快排

排序算法_第3张图片

  • 另外定义一个变量sum,用来保存第一个pre,pre的位置则空出来了。
  • 然后end先向前移动,寻找比sum小的数,找到后将end与pre交换,现在end的位置为空。
  • 然后pre向前移动,寻找比sum大的数,找到将pre与end交换,现在pre的位置为空。
  • 然后再是end向前移动………………
  • 这样循环直到pre和end相遇为止,相遇时的位置为空,再将sum的值放到相遇的位置上。
  • 这样这个数的左边都是比它小的数,右边都是比它大的数。
  • 最后使用递归即可。

前后指针法快排

排序算法_第4张图片

  • 起始位置,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 份有序文件
    • 然后再归并,最后归并成一个有序的大文件。

算法复杂度及稳定性

  • 数组中相同的值,排完序后相对位置不变,就是稳定的,否则就是不稳定。
    排序算法_第5张图片

你可能感兴趣的:(c++数据结构与算法)