排序算法

排序是程序开发中非常常见的操作,对一组任意的数据元素经过排序操作后,就可以把他们变成一组一定规则排序的有序序列
排序算法属于算法中的一种,而且是覆盖范围极小的一种,彻底掌握排序算法对程序开发是有很大的帮助的
对与排序算法的好坏衡量,主要是从时间复杂度、空间复杂度、稳定性
时间复杂度、空间复杂度前面已经讲过,这里主要看看稳定性的定义
稳定性指的是假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变
即在原序列中,r[i] = r[j],且 r[i] 在 r[j] 之前,而在排序后的序列中,r[i] 仍在 r[j] 之前,则称这种排序算法是稳定的;否则称为不稳定的

常见排序算法

冒泡排序

一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来
思路如下:

attach/dd841197b36b937247058f6e59f53b0a_MD5.webp

def bubbleSort(arr):
    for i in range(1, len(arr)):
        for j in range(0, len(arr)-i):
            if arr[j] > arr[j+1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
    return arr

选择排序

选择排序是一种简单直观的排序算法,它也是一种交换排序算法
无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好
唯一的好处是不占用额外的内存存储空间
思路如下:

插入排序

插入排序是一种简单直观的排序算法
它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入
解决思路如下:

归并排序

归并排序是建立在归并操作上的一种有效的排序算法
该算法是采用分治法的一个非常典型的应用
将已有序的子序列合并,得到完全有序的序列,即先使每个子序列有序,再使子序列段间有序

递归法解决思路如下:

  1. 将序列每相邻两个数字进行归并操作,形成ceil(n/2)个序列,排序后每个序列包含两/一个元素
  2. 若此时序列数不是1个则将上述序列再次归并,形成ceil(n/4)个序列,每个序列包含四/三个元素
  3. 重复步骤2,直到所有元素排序完毕,即序列数为1

attach/74dc35e0d3b5b89e7a0faf305f4a540e_MD5.gif

快速排序

快速排序是对冒泡排序算法的一种改进,基本思想是通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据比另一部分的所有数据要小
再按这种方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,使整个数据变成有序序列
解决思路如下:

def quicksort(arr: list,left=None,right=None):
    left = 0 if left is None else left
    right = len(arr)-1 if right is None else right
    if left<right:
        pivot = left
        index = pivot+1
        i = index
        while i<= right:
	        # 如果数据小于基准数据,便交换其到index位置处
	        # 即1~index-1小于基准
            if arr[i]<arr[pivot]:
                arr[i],arr[index] = arr[index],arr[i]
                index += 1
            i+=1
        arr[pivot],arr[index-1] = arr[index-1],arr[pivot] # 交换基准元素到中间位置
        quicksort(arr,left,index-2)
        quicksort(arr,index,right)
    return arr

希尔排序

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。

算法步骤

attach/72af4843580df19cec011c60a6d71933_MD5.gif

def shellSort(arr):
    import math
    gap=1
    while(gap < len(arr)/3):
        gap = gap*3+1
    while gap > 0:
        for i in range(gap,len(arr)):
            temp = arr[i]
            j = i-gap
            while j >=0 and arr[j] > temp:
                arr[j+gap]=arr[j]
                j-=gap
            arr[j+gap] = temp
        gap = math.floor(gap/3)
    return arr

计数排序

计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

  1. 找出待排序的数组中最大和最小的元素;
  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
  3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。

attach/c720fc7813b289149b74b17b78a8413f_MD5.gif

桶排序

桶排序又叫箱排序,是计数排序的升级版,它的工作原理是将数组分到有限数量的桶子里,然后对每个桶子再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序),最后将各个桶中的数据有序的合并起来。

计数排序是桶排序的一种特殊情况,可以把计数排序当成每个桶里只有一个元素的情况。网络中很多博文写的桶排序实际上都是计数排序,并非标准的桶排序,要注意辨别。

算法描述

  1. 找出待排序数组中的最大值max、最小值min
  2. 我们使用 动态数组ArrayList 作为桶,桶里放的元素也用 ArrayList 存储。桶的数量为(max-min)/arr.length+1
  3. 遍历数组 arr,计算每个元素 arr[i] 放的桶
  4. 每个桶各自排序
  5. 遍历桶数组,把排序好的元素放进输出数组
    attach/1d78fbfcebf062d97253ad8ccf63e318_MD5.png

基数排序

基数排序(Radix Sort)是桶排序的扩展,它的基本思想是:将整数按位数切割成不同的数字,然后按每个位数分别比较。
排序过程:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
算法过程:

  1. 取得数组中的最大数,并取得位数;
  2. arr为原始数组,从最低位开始取每个位组成radix数组;
  3. 对radix进行计数排序(利用计数排序适用于小范围数的特点);

attach/1d21df0cabb2c40807ac5d5767f22d60_MD5.gif

三、区别

除了上述的排序算法之外,还存在其他的排序算法,例如希尔排序、堆排序等等......
区别如下图所示: attach/ee0ccff6bd61d0e3ce8e64184128cc08_MD5.png

参考文献