基础算法-> 排序

99 阅读5分钟

参考: 十大经典排序算法

概述

排序是按照某种顺序排列序列元素的一种算法。输出是输入序列的重新排序。

排序是计算机科学中的重要算法,排序有时可以显著降低问题的复杂度,可以使用排序作为减少查找复杂度的一种技术。

排序的分类

基于参数分类

  • 比较次数
  • 交换次数
  • 内存使用
  • 递归
  • 稳定性
  • 适应性

其他分类

  • 内部排序 排序时仅使用主存储器的排序算法称为内部排序(internal sort)
  • 外部排序 排序时需要使用磁盘等外部存储器的排序算法属于外部排序(external sort)

总结几个经典的排序算法

复杂度比较

方法平均时间最坏时间最好时间空间稳定性
插入O(n2)O(n^2)O(n2)O(n^2)O(n)O(n)O(1)O(1)稳定
希尔O(n1.3)O(n^{1.3})O(n2)O(n^2)O(n)O(n)O(1)O(1)不稳定
选择O(n2)O(n^2)O(n2)O(n^2)O(n2)O(n^2)O(1)O(1)不稳定
O(nlog2n)O(n\log_2 {n})O(nlog2n)O(n\log_2 {n})O(nlog2n)O(n\log_2 {n})O(1)O(1)不稳定
冒泡O(n2)O(n^2)O(n2)O(n^2)O(n)O(n)O(1)O(1)稳定
快速O(nlog2n)O(n\log_2 {n})O(n2)O(n^2)O(nlog2n)O(n\log_2 {n})O(nlog2n)O(n\log_2 {n})不稳定
归并O(nlog2n)O(n\log_2 {n})O(nlog2n)O(n\log_2 {n})O(nlog2n)O(n\log_2 {n})O(n)O(n)稳定
计数O(n+k)O(n+k)O(n+k)O(n+k)O(n+k)O(n+k)O(n+k)O(n+k)稳定
O(n+k)O(n+k)O(n2)O(n^2)O(n)O(n)O(n+k)O(n+k)稳定
基数O(nk)O(n*k)O(nk)O(n*k)O(nk)O(n*k)O(n+k)O(n+k)稳定

排序算法均以升序排列为例

插入排序(Selection Sort)

插入排序

计算过程

  1. 将第一待排序序列第一个元素看做一个有序序列,将第二个元素到最后元素作为未排序序列
  2. 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置(如果与有序队列的某个元素相等,则放到此元素后面)

代码

def insertion_sort(arr):
    """
    插入排序
    :param arr 待排序的数组
    """
    for i in range(1, len(arr)):
        preIndex = i - 1
        current = arr[i]
        while preIndex >= 0 and arr[preIndex] > current:
            arr[preIndex + 1] = arr[preIndex]
            preIndex -= 1
        arr[preIndex + 1] = current
    return arr

选择排序(Selection Sort)

选择排序

计算过程

  1. 在序列中找到最小的元素,交换到序列的起始位置
  2. 将需要比较起始位置后移
  3. 重复1~2步,知道比较完

代码

def selection_sort(arr):
    """
    选择排序
    :param arr 待排序的数组
    """
    for i in range(len(arr) - 1):
        minIndex = i
        for j in range(i + 1, len(arr))
            # 交换顺序
            if arr[j] < arr[minIndex]:
                minIndex = j
        if i != minIndex:
            # 交换
            arr[i], arr[minIndex] = arr[minIndex], arr[i]
    return arr

冒泡排序(Bubble Sort)

冒泡排序

计算过程

  1. 比较相邻的元素,如果第一个比第二个大,就交换顺序
  2. 对每一个相邻的元素重复上一步,这样执行完一遍,最后一个元素为最大
  3. 递减每次需要比较的元素
  4. 重复1~3步,直到递减为0

复杂度分析

最快情况最慢情况
数据为正序数据为反序

代码

def bubble_sort(arr):
    """
    冒泡排序
    :param arr 待排序的数组
    """
    # 用i来递减每次需要比价的元素
    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[i]
    return arr

希尔排序(Shell Sort)

希尔排序 希尔排序是基于插入排序改进的:

  • 插入排序比较低效,因为每次插入排序只能移动一位

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

计算过程

  1. 选择一个增量序列t1,t2,......,tkt_1, t_2, ......, t_k,其中ti>tj,tk=1t_i > t_j, t_k = 1
  2. 按增量序列个数k,对序列进行k趟排序
  3. 每趟排序,根据对应的增量tit_i,将待排序分割成若干长度为m的子序列,分别对各子表进行直接插入排序。仅增量因子为1时,整个序列作为一个表来处理,表长度即为整个序列的长度。

希尔排序的思想是使数组中任意间隔为h的元素都是有序的。这样的数组被称为h有序数组。换句话说,一个h有序的数组就是h个互相独立的有序数组编织在一起组成的一个数组。

代码

两种实现

def shell_sort(arr):
    """
    希尔排序
    :param arr 待排序的数组
    """
    n = len(arr)
    # 初始步长
    gap = n / 2
    while gap > 0:
        for i in range(gap, n):
            # 每个步长进行插入排序
            temp = arr[i]
            j = i
            # 插入排序
            while j >= 0 and arr[j - gap] > temp:
                arr[j] = arr[j - gap]
                j -= gap
            arr[j] = temp
        gap = gap / 2
    return arr       

快速排序(Quick Sort)

快速排序

快速排序是对冒泡排序的一种改进,本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。

计算过程

  1. 从数列中挑出一个元素,称为基准(pivot)
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序

代码

精简版本(占用多余空间)

def quick_sort(arr):
    """
    快速排序
    :param arr 待排序的数组
    """
    if len(arr) <= 1:
        return arr
    left, right, mid = [], [], []
    pivot = random.choice(arr)
    for item in arr:
        if item == pivot:
            mid.append(item)
        elif item < pivot:
            left.append(item)
        else:
            right.append(item)
    
    # 递归
    return quick_sort(left) + mid + quick_sort(right)

原地排序版本

def quick_sort(arr, start, end):
    if start >= end:
        return arr
    mid_value = arr[first]
    low = start
    high = end
    while low < high:
        while low < high and arr[high] >= mid_value:
            high -= 1
        arr[low] = arr[high]
        while low < high and arr[low] < mid_value:
            low += 1
        arr[high] = arr[low]
    arr[low] = mid_value
    quick_sort(arr, start, low - 1)
    quick_sort(arr, low + 1, high)