排序算法总结

十种常见排序算法可以分为两大类:

  1. 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
  2. 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
    在这里插入图片描述
    算法复杂度:
    在这里插入图片描述

1. 比较类排序

1.1. 交换排序

1.1.1. 冒泡排序(交换邻接元素)

平均时间复杂度最差时间复杂度最好时间复杂度空间复杂度稳定性
O(n2)O(n2)O(n)O(1)稳定
  1. 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  3. 针对所有的元素重复以上的步骤,除了最后一个;
  4. 重复步骤1~3,直到排序完成。
def bubbleSort(arr):
    n = len(arr)
    # 遍历所有数组元素
    for i in range(n):
        # Last i elements are already in place
        for j in range(0, n - i - 1):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]

arr = [64, 34, 25, 12, 22, 11, 90]
bubbleSort(arr)
print("排序后的数组:", arr)

1.1.2. 快速排序(交换left/right指针指向元素)

平均时间复杂度最差时间复杂度最好时间复杂度空间复杂度稳定性
O(nlog2n))O(n2)O(nlog2n)O(nlog2n)不稳定

note: 交换左右指针指向元素时无需移动左右指针
在这里插入图片描述

def quick_sort(L):
    return q_sort(L, 0, len(L) - 1)

def q_sort(L, left, right):
    if left < right:
        pivot = Partition(L, left, right)
        q_sort(L, left, pivot - 1)
        q_sort(L, pivot + 1, right)
    return L

def Partition(L, left, right):
    pivotkey = L[left]
    while left < right:
        while left < right and L[right] >= pivotkey:
            right -= 1
        L[left] = L[right]
        while left < right and L[left] <= pivotkey:
            left += 1
        L[right] = L[left]
    L[left] = pivotkey
    return left

L = [5, 9, 1, 11, 6, 7, 2, 4]
print(quick_sort(L))

1.2. 插入排序

1.2.1. 简单插入排序

一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
**适用:**少量数据的排序,
**时间复杂度:**O(n^2)
稳定

  1. 从第一个元素开始,该元素可以认为已经被排序;
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  3. 如果该元素(已排序)大于新元素,将该元素(已排序)移到下一位置;
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  5. 将新元素插入到该位置后;
  6. 重复步骤2~5。
def SimpleInsertSort(arr):
    len_a = len(arr)
    if len_a <= 1:
        return arr
    for i in range(1, len_a):
        j = i
        temp = arr[i]
        while j > 0 and temp < arr[j - 1]:
            arr[j] = arr[j - 1]
            j -= 1
        arr[j] = temp  # 把temp插到空位
    return arr

arr = [3, 1, 2, 4, 6, 7, 8, 9, 5]
arr = SimpleInsertSort(arr)
print(arr) # [1, 2, 3, 4, 5, 6, 7, 8, 9]

1.2.2. 希尔排序(简单插入排序算法的高效改进版本)

  1. 排序数组A=[8 9 1 7 2 3 5 4 6 0]
  2. 初始增量为 gap = 10/2 = 5,整个数组分成了 5 组【 8 , 3 】,【 9 , 5 】,【 1 , 4 】,【 7 , 6 】,【 2 , 0 】。对这分开的 5 组分别使用简单插入排序,得到 3 5 1 6 0 8 9 4 7 2
  3. 缩小增量 gap = 5/2 = 2,整个数组分成了 2 组【 3 , 1 , 0 , 9 , 7 】,【 5 , 6 , 8 , 4 , 2 】。对这分开的 2 组分别使用插入排序,得到 0 2 1 4 3 5 7 6 9 8
  4. 再缩小增量 gap = 2/2 = 1,整个数组分成了 1 组【 0, 2 , 1 , 4 , 3 , 5 , 7 , 6 , 9 , 8 】。进行插入排序,得到 0 1 2 3 4 5 6 7 8 9
def ShellInsertSort(arr):
    len_a = len(arr)
    if len_a <= 1:
        return arr
    d = len_a // 2
    while d >= 1:
        for i in range(d, len_a):
            j = i - d
            temp = arr[i]  
            while (j >= 0 and arr[j] > temp):  # 从后向前,找比其小的数的位置
                arr[j + d] = arr[j]  # 向后挪动
                j -= d
            if j != i - d:
                arr[j + d] = temp
        d = d // 2
    return arr


arr = [50, 16, 30, 10, 60, 90, 2, 80, 70]
arr = ShellInsertSort(arr)
print(arr) # [2, 10, 16, 30, 50, 60, 70, 80, 90]

1.3. 选择排序

从无序区中选择最大/最小元素,放入有序区末尾

1.3.1. 简单选择排序

平均时间复杂度最差时间复杂度最好时间复杂度空间复杂度稳定性
O(n2)O(n2)O(n2)O(1)稳定

n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。实现从小到大排序的具体算法描述如下:

  1. 初始状态:有序区为空,无序区为R[1…n];
  2. 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R[i…n]。该趟排序从当前无序区R[i…n]中选出最小值 R[k],将它与无序区的第1个记录R[i]交换,使R[1…i-1]和R[i…n]分别变为记录个数增加1个的新有序区R[1…i]和记录个数减少1个的新无序区R[i+1…n];
  3. n-1趟结束,数组实现从小到大排列。
    python代码实现
def SimpleChooseSort(arr):
    for j in range(len(arr)-1):
        minIndex = j
        for i in range(j+1, len(arr), 1):
            if arr[i] < arr[minIndex]:
                minIndex = i
        arr[j], arr[minIndex] = arr[minIndex], arr[j]

arr = [3,1,2,4,6,7,8,9,5]
SimpleChooseSort(arr)
print(arr)

1.3.2. 堆排序

  1. 将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆(升序选择大顶堆,降序选择小顶堆);
  2. 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
  3. 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
    具体过程
from collections import deque

def swap_param(L, i, j):
    L[i], L[j] = L[j], L[i]
    return L

def heap_adjust(L, start, end):
    temp = L[start]
    i = start
    j = 2 * i
    while j <= end:
        if (j < end) and (L[j] < L[j + 1]):
            j += 1
        if temp < L[j]:
            L[i] = L[j]
            i = j
            j = 2 * i
        else:
            break
    L[i] = temp

def heap_sort(L):
    L_length = len(L) - 1
    first_sort_count = L_length // 2
    for i in range(first_sort_count):
        heap_adjust(L, first_sort_count - i, L_length)
    for i in range(L_length - 1):
        L = swap_param(L, 1, L_length - i)
        heap_adjust(L, 1, L_length - i - 1)
    return [L[i] for i in range(1, len(L))]
    
def main():
    L = deque([50, 16, 30, 10, 60,  90,  2, 80, 70])
    L.appendleft(0)
    print(heap_sort(L))

main()

1.4. 归并排序

1.4.1. 二路归并排序

算法思路:

  1. 把长度为n的输入序列分成两个长度为n/2的子序列;
  2. 对这两个子序列分别采用归并排序;
  3. 将两个排序好的子序列合并成一个最终的排序序列。
    在这里插入图片描述
# 归并排序(分)
def mergeSort(arr):
    if len(arr) < 2:
        return arr
    mid = len(arr) // 2
    left = mergeSort(arr[:mid])
    right = mergeSort(arr[mid:])
    return merge(left, right)


# 归并排序(治)
def merge(left, right):
    if not len(left) or not len(right):
        return left or right
    result = []
    i, j = 0, 0

    while (len(result) < len(left) + len(right)):
        if left[i] < right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
        if i == len(left) or j == len(right):
            result.extend(right[j:] or left[i:])
            break
    return result


arr = [50, 16, 30, 10, 60, 90, 2, 80, 70]
arr = mergeSort(arr)
print(arr)

1.4.2. 多路归并排序

2. 非比较类排序

2.1. 计数排序

待排序数组A,算法描述:

  1. O(n)的时间扫描一下整个序列 A,获取最小值 min 和最大值 max
  2. 开辟一块新的空间创建新的数组 B,长度为 ( max - min + 1)
  3. 数组 B 中 index (index = A中元素 - min)的元素记录的值是 A 中某元素出现的次数
  4. 最后输出目标整数序列,具体的逻辑是遍历数组 B,输出相应元素(输出元素值 = B中某元素 + min)以及对应的个数
    例如:待排序数组A=[5 3 4 7 2 4 3 4 7]
    得到min = 2, max = 7, len(B) = max - min + 1 = 6, B[0] = 1, B[1] = 2, B[2] = 3, B[3] = 1, B[4] = 0, B[5] = 2.
    遍历数组B, 输出排序后的A = [2,3,3,4,4,4,5,7,7]

2.2. 桶排序

基本思想:假设有一组长度为N的待排关键字序列K[1…n]。首先将这个序列划分成M个的子区间(桶) 。然后基于某种映射函数 ,将待排序列的关键字k映射到第i个桶中(即桶数组B的下标 i) ,那么该关键字k就作为B[i]中的元素(每个桶B[i]都是一组大小为N/M的序列)。接着对每个桶B[i]中的所有元素进行比较排序(可以使用快排)。然后依次枚举输出B[0]…B[M]中的全部内容即是一个有序序列。
例如:
假如待排序列K= {49,38,35,97,76,73,27,49}。这些数据全部在1 - 100之间。因此我们定制10个桶,然后确定映射函数f(k) = k/10。则第一个关键字49将定位到第4个桶中(49/10 = 4)。依次将所有关键字全部堆入桶中,并在每个非空的桶中进行快速排序。

2.3. 基数排序

基本思想:将数组中的所有数按位进行分类,由于每一位数的大小都在0 - 9之间,因此创建下标为0 - 9的十个数组,根据需要对数进行存储。
例如:对于一个数组:[8,9,6,11,23,1,9,18,10,213,33,7,87,91,180,35,52,716,106]
(1)选择个位数相同的元素,并成为一个数组
此时个位为i的数,分别别存储在下标为i的数组中,对数据开始进行收集,将数组重新整理,数组变为:10  180  11  1  91  52  23  213  33  35  6  716  106  7  87  8  18  9  9
(2)选择十位数为i的数的元素,并成为一个数组
此时十位为i的数,分别别存储在下标为i的数组中,对数据开始收集,将数组重新整理,数组变为:1  6  106  7  8  9  9  10  11  213  716  18  23  33  35  52  180  87  91
(3)选择百位数为i的数的元素,并成为一个数组
此时百位为i的数,分别别存储在下标为i的数组中,对数据开始收集,将数组重新整理,数组变为:1 6 7 8 9 9 10 11 18 23 33 35 52 87 91 106 180 213 716

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值