排序方法:插入、冒泡、选择、希尔、归并、堆排序,时空复杂度,原理图解,python 实现

各类排序算法时空复杂度、稳定性对比

序号排序算法平均时间复杂度最坏时间复杂度空间复杂度是否稳定
1插入排序O(n2)O(n2)O(1)
2冒泡排序O(n2)O(n2)O(1)
3选择排序O(n2)O(n2)O(1)不是
4希尔排序O(nlogn)O(ns)O(1)不是
5归并排序O(nlogn)O(nlogn)O(n)
6快速排序O(nlogn)O(n2)O(logn)不是
7堆排序O(nlogn)-O(1)不是

1. 插入排序

原理:不断选择数字,插入到部分已经排好序的数组中。

  • 时间复杂度最好的情况是已经排好序
  • 时间复杂度最好 O ( n ) O(n) O(n),平均 O ( n 2 ) O(n^2) O(n2),最坏 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度 O ( 1 ) O(1) O(1)
1.1 直接插入排序
# 直接插入排序
def insert_sort(nums):
    for i in range(1, len(nums)):
        cur_num = nums[i]
        j = i
        # 对已经排好的数字,从后往前,边挪边比较
        while j - 1 >= 0 and nums[j - 1] > cur_num:
            nums[j] = nums[j - 1]
            j -= 1
        nums[j] = cur_num
    return nums
1.2 折半插入排序

和直接插入排序方法类似,不过是以折半查找方式来寻找插入位置

# 折半插入排序
def half_insert_sort(nums):
    for i in range(1, len(nums)):
        cur_num = nums[i]
        # 折半查找,找到要插入位置(left)
        left, right = 0, i - 1
        while left <= right:
            mid = int((left + right) / 2)
            if nums[mid] > cur_num:
                right = mid - 1
            else:
                left = mid + 1
        for j in range(i, left, -1):  # 挪
            nums[j] = nums[j - 1]
        nums[left] = cur_num
    return nums

2. 冒泡排序

原理:将大的数字往后冒(交换相邻两个数字的位置)。

  • 时间复杂度最好的情况是已经排好序
  • 时间复杂度最好 O ( n ) O(n) O(n),平均 O ( n 2 ) O(n^2) O(n2),最坏 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度 O ( 1 ) O(1) O(1)
def bubble_sort(nums):
    # 大的往后冒
    for i in range(len(nums), 0, -1):  # 最后一个不用管
        for j in range(i - 1):
            # 将大的交换到后面去
            if nums[j] > nums[j + 1]:
                nums[j], nums[j + 1] = nums[j + 1], nums[j]
    return nums

3. 选择排序

原理:每趟从剩余数字中找到最小的数字,放到指定位置。

  • 时间复杂度最好的情况是已经排好序
  • 时间复杂度最好 O ( n ) O(n) O(n),平均 O ( n 2 ) O(n^2) O(n2),最坏 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度 O ( 1 ) O(1) O(1)
def select_sort(nums):
    """每次把剩余最小的放在前面"""
    for i in range(len(nums)):
        min_index = i
        # 找剩余最小的下标
        for j in range(i, len(nums)):
            if nums[j] < nums[min_index]:
                min_index = j
        # 交换
        temp = nums[i]
        nums[i] = nums[min_index]
        nums[min_index] = temp
    return nums

4. 希尔排序

原理:间隔步长step的数字归为一组,组内进行插入排序,step由大变小,直到最后一趟步长为1,排序完成。

  • 当增量序列为 K = 2 x K = 2^x K=2x时,时间复杂度为 O ( n 2 ) O(n^2) O(n2)
  • 当增量序列为 K = ( 3 ∗ x + 1 ) K = (3 * x + 1) K=(3x+1)时,时间复杂度为 O ( n 3 / 2 ) O(n^3/2) O(n3/2)
  • 空间复杂度为 O ( 1 ) O(1) O(1)
def shell_sort(nums):
    """间隔步长step的数字为一组,组内进行插入排序,step由大到小"""
    length = len(nums)
    step = int(length / 2)
    while step > 0:
        for i in range(step, length):
            # 每组内部采用插入排序
            j = i
            temp = nums[i]
            while j - step >= 0 and nums[j - step] > temp:
                nums[j] = nums[j - step]
                j -= step
            nums[j] = temp
        step = int(step / 2)
    return nums

5. 归并排序

原理:对于给定的一组记录,利用递归与分治技术将数据序列划分成为越来越小的半子表,在对半子表排序,最后再用递归方法将排好序的半子表合并成为越来越大的有序序列

  • 时间复杂度最优、平均和最差都是 O ( n l o g n ) O(nlogn) O(nlogn)
  • 空间复杂度为: O ( n ) O(n) O(n)

例子
请添加图片描述

# 合并左右两个子数组
def merge(arr, left, mid, right):
    """相当于有两个递增的子数组,合并成一个递增的数组"""
    n1 = mid - left + 1
    n2 = right - mid
    # 创建临时数组
    L = [0] * (n1)
    R = [0] * (n2)
    # 将左右子数组拷贝数据到临时数组 L[] 和 R[]
    for i in range(0, n1):
        L[i] = arr[left + i]
    for j in range(0, n2):
        R[j] = arr[mid + 1 + j]

    # 归并临时数组到 arr[l..r]
    i = 0  # 初始化第一个子数组的索引
    j = 0  # 初始化第二个子数组的索引
    k = left  # 初始归并子数组的索引
    while i < n1 and j < n2:
        if L[i] <= R[j]:
            arr[k] = L[i]
            i += 1
        else:
            arr[k] = R[j]
            j += 1
        k += 1
    # 拷贝 L[] 的保留元素
    while i < n1:
        arr[k] = L[i]
        i += 1
        k += 1
    # 拷贝 R[] 的保留元素
    while j < n2:
        arr[k] = R[j]
        j += 1
        k += 1


# 递归归并排序
def merge_sort(nums, left, right):
    """"""
    if left < right:
        mid = int((left + (right - 1)) / 2)
        merge_sort(nums, left, mid)
        merge_sort(nums, mid + 1, right)
        merge(nums, left, mid, right)

6. 快速排序

原理:每一趟将第一个数字作为基准,先从后往前遍历,比基准大的放到前面,比基准小的放到后面。

  • 最优情况是每次划分正好平分数组
  • 最差情况是每次划分落到最边缘,退化为冒泡排序
  • 时间复杂度最优 O ( n l o g n ) O(nlogn) O(nlogn),平均 O ( n l o g n ) O(nlogn) O(nlogn),最坏 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度最优 O ( l o g n ) O(logn) O(logn),平均 O ( l o g n ) O(logn) O(logn),最差 O ( n ) O(n) O(n)

例子:
请添加图片描述

def quick_sort(nums, low, high):
    """最优情况是每次划分正好平分数组,最差情况是每次划分落到最边缘,退化为冒泡排序"""
    if low >= high:
        return
    i, j = low, high
    pivot = nums[low]  # 以第一个元素作为基准
    while i < j:
        while i < j and nums[j] > pivot:
            j -= 1
        nums[i] = nums[j]
        while i < j and nums[i] <= pivot:
            i += 1
        nums[j] = nums[i]
    nums[i] = pivot
    quick_sort(nums, low, i - 1)  # 对低位进行递归快排
    quick_sort(nums, i + 1, high)  # 对高位进行递归快排

7. 堆排序

原理:堆看成是二叉树结构,大顶堆要求父节点元素要大于等于其子节点。那么每次弹出大顶堆的根节点,就能从大到小进行排序。

  • 时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
    • 初始化堆: O ( n ) O(n) O(n)
    • 更改堆元素后重建堆时间: O ( n l o g n ) O(nlogn) O(nlogn)
  • 空间复杂度 O ( 1 ) O(1) O(1)

例子
请添加图片描述

def heapify(nums, n, i):
    largest = i  # 最大值下标暂时记录为 i
    left = 2 * i + 1
    right = 2 * i + 2
    if left < n and nums[largest] < nums[left]:
        largest = left
    if right < n and nums[largest] < nums[right]:
        largest = right
    if i != largest:
        nums[i], nums[largest] = nums[largest], nums[i]
        # 子结点递归交换
        heapify(nums, n, largest)


def heap_sort(nums):
    n = len(nums)
    # 构造大顶堆
    for i in range(n - 1, -1, -1):
        heapify(nums, n, i)
    # 从堆顶一个一个弹出
    for j in range(n - 1, -1, -1):
        nums[0], nums[j] = nums[j], nums[0]
        heapify(nums, j, 0)

完整测试代码

# !usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Author: ywm_up
@File: sort_methods.py
@Time: 2021/8/15 10:55
"""


# 1. 插入排序
def insert_sort(nums):
    for i in range(1, len(nums)):
        cur_num = nums[i]
        j = i
        # 对已经拍好的数字,从后往前,边挪边比较
        while j - 1 >= 0 and nums[j - 1] > cur_num:
            nums[j] = nums[j - 1]
            j -= 1
        nums[j] = cur_num
    return nums


# 2. 折半插入排序
def half_insert_sort(nums):
    # 和插入排序方法类似,不过是以折半查找方式来寻找插入位置
    for i in range(1, len(nums)):
        cur_num = nums[i]
        # 折半查找,找到要插入位置(left)
        left, right = 0, i - 1
        while left <= right:
            mid = int((left + right) / 2)
            if nums[mid] > cur_num:
                right = mid - 1
            else:
                left = mid + 1
        for j in range(i, left, -1):  # 挪
            nums[j] = nums[j - 1]
        nums[left] = cur_num
    return nums


# 3. 冒泡排序
def bubble_sort(nums):
    # 大的往后冒
    for i in range(len(nums), 0, -1):  # 最后一个不用管
        for j in range(i - 1):
            # 将大的交换到后面去
            if nums[j] > nums[j + 1]:
                temp = nums[j]
                nums[j] = nums[j + 1]
                nums[j + 1] = temp
    return nums


# 4. 选择排序
def select_sort(nums):
    """每次把剩余最小的放在前面"""
    for i in range(len(nums)):
        min_index = i
        # 找剩余最小的下标
        for j in range(i, len(nums)):
            if nums[j] < nums[min_index]:
                min_index = j
        # 交换
        temp = nums[i]
        nums[i] = nums[min_index]
        nums[min_index] = temp
    return nums


# 5. 希尔排序
def shell_sort(nums):
    """间隔步长step的数字为一组,组内进行插入排序,step由大到小"""
    length = len(nums)
    step = int(length / 2)
    while step > 0:
        for i in range(step, length):
            # 每组内部采用插入排序
            j = i
            temp = nums[i]
            while j - step >= 0 and nums[j - step] > temp:
                nums[j] = nums[j - step]
                j -= step
            nums[j] = temp
        step = int(step / 2)
    return nums


# 6. 归并排序
# 合并左右两个子数组
def merge(arr, left, mid, right):
    """相当于有两个递增的子数组,合并成一个递增的数组"""
    n1 = mid - left + 1
    n2 = right - mid
    # 创建临时数组
    L = [0] * (n1)
    R = [0] * (n2)
    # 将左右子数组拷贝数据到临时数组 L[] 和 R[]
    for i in range(0, n1):
        L[i] = arr[left + i]
    for j in range(0, n2):
        R[j] = arr[mid + 1 + j]

    # 归并临时数组到 arr[l..r]
    i = 0  # 初始化第一个子数组的索引
    j = 0  # 初始化第二个子数组的索引
    k = left  # 初始归并子数组的索引
    while i < n1 and j < n2:
        if L[i] <= R[j]:
            arr[k] = L[i]
            i += 1
        else:
            arr[k] = R[j]
            j += 1
        k += 1
    # 拷贝 L[] 的保留元素
    while i < n1:
        arr[k] = L[i]
        i += 1
        k += 1
    # 拷贝 R[] 的保留元素
    while j < n2:
        arr[k] = R[j]
        j += 1
        k += 1


# 递归归并排序
def merge_sort(nums, left, right):
    """"""
    if left < right:
        mid = int((left + (right - 1)) / 2)
        merge_sort(nums, left, mid)
        merge_sort(nums, mid + 1, right)
        merge(nums, left, mid, right)


# 7. 快速排序
def quick_sort(nums, low, high):
    """最优情况是每次划分正好平分数组,最差情况是每次划分落到最边缘,退化为冒泡排序"""
    if low >= high:
        return
    i, j = low, high
    pivot = nums[low]  # 以第一个元素作为基准
    while i < j:
        while i < j and nums[j] > pivot:
            j -= 1
        nums[i] = nums[j]
        while i < j and nums[i] <= pivot:
            i += 1
        nums[j] = nums[i]
    nums[i] = pivot
    quick_sort(nums, low, i - 1)  # 对低位进行递归快排
    quick_sort(nums, i + 1, high)  # 对高位进行递归快排


# 8. 堆排序
def heapify(nums, n, i):
    largest = i  # 最大值下标暂时记录为 i
    left = 2 * i + 1
    right = 2 * i + 2
    if left < n and nums[largest] < nums[left]:
        largest = left
    if right < n and nums[largest] < nums[right]:
        largest = right
    if i != largest:
        nums[i], nums[largest] = nums[largest], nums[i]
        # 子结点递归交换
        heapify(nums, n, largest)


def heap_sort(nums):
    n = len(nums)
    # 构造大顶堆
    for i in range(n - 1, -1, -1):
        heapify(nums, n, i)
    # 从堆顶一个一个弹出
    for j in range(n - 1, -1, -1):
        nums[0], nums[j] = nums[j], nums[0]
        heapify(nums, j, 0)


def mytest():
    # 以下排序都只实现了递增
    print("1. 插入排序")
    nums = [2, 3, 0, 1, 5, 4]
    print(insert_sort(nums))

    print("2. 折半插入排序")
    nums = [2, 3, 0, 1, 5, 4]
    print(half_insert_sort(nums))

    print("3. 冒泡排序")
    nums = [2, 3, 0, 1, 5, 4]
    print(bubble_sort(nums))

    print("4. 选择排序")
    nums = [2, 3, 0, 1, 5, 4]
    print(select_sort(nums))

    print("5. 希尔排序")
    nums = [2, 3, 0, 1, 5, 4]
    print(shell_sort(nums))

    print("6. 归并排序")
    nums = [2, 3, 0, 1, 5, 4]
    merge_sort(nums, 0, len(nums) - 1)
    print(nums)

    print("7. 快速排序")
    nums = [2, 3, 0, 1, 5, 4]
    quick_sort(nums, 0, len(nums) - 1)
    print(nums)

    print("8. 堆排序")
    nums = [2, 3, 0, 1, 5, 4]
    heap_sort(nums)
    print(nums)


if __name__ == '__main__':
    mytest()
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值