十个常用排序算法的原理及python模板

这里写目录标题

  • 冒泡排序 Bubble Sort
  • 快速排序 Quick Sort
  • 插入排序 Insertion Sort
  • 希尔排序 Shell Sort
  • 选择排序 Select Sort
  • 堆排序 Heap Sort
  • 归并排序 Merge Sort
  • 计数排序 Counting Sort
  • 基数排序(Radix Sort)
  • 桶排序(Bucket Sort)

十个常用排序算法的原理及python模板_第1张图片

十个常用排序算法的原理及python模板_第2张图片

冒泡排序 Bubble Sort

原理:从第一个元素开始,将相邻的两个元素进行比较,如果前一个元素大于后一个元素就将它们交换,一轮下来最大的元素就会被交换到最后,再对前面的元素进行相同的操作,直到全部排序完成。

模板:

def bubble_sort(nums):
    n = len(nums)
    for i in range(n):
        for j in range(0, n - i - 1):  #表示每一轮循环需要比较的元素数量,每一轮结束后要将已排序的元素排除在外。
            if nums[j] > nums[j + 1]:
                nums[j], nums[j + 1] = nums[j + 1], nums[j]
    return nums

快速排序 Quick Sort

原理:选择一个枢轴元素,将序列分为左右两部分,左边的元素都小于枢轴元素,右边的元素都大于它,然后对左右两部分分别进行相同的操作,直到全部排序完成。

模板:

def quick_sort(nums):
    if len(nums) <= 1:
        return nums
    pivot = nums[0] 	#pivot表示枢轴元素
    left_nums = [x for x in nums[1:] if x < pivot]
    right_nums = [x for x in nums[1:] if x >= pivot]
    return quick_sort(left_nums) + [pivot] + quick_sort(right_nums)

插入排序 Insertion Sort

原理:将未排序序列中的第一个元素插入到已排序序列中合适的位置,对于未排序序列中的其他元素进行相同的操作,直到全部排序完成。

模板:

def insertion_sort(nums):
    n = len(nums)
    for i in range(1, n):
        j = i
        while j > 0 and nums[j - 1] > nums[j]:
            nums[j], nums[j - 1] = nums[j - 1], nums[j]
            j -= 1
    return nums

希尔排序 Shell Sort

原理:希尔排序,也称缩小增量排序,是插入排序的一种高效改进式版本。它的思想是将数组分成若干个子序列,对每个子序列进行插入排序,经过若干轮排序后,整个序列变成有序。与插入排序不同的是,希尔排序是对元素间隔固定的多个子序列分别进行插入排序,随着排序轮数的增加,子序列的长度逐渐缩小,最后变成一个整体有序的序列。
希尔排序通过减小项间距,使得一些逆序对提前得到了交换,从而加快排序的速度。这也是希尔排序高效的主要原因。

模板:

def shell_sort(arr):
    n=len(arr)
    gap=n//2
    while gap>0:
        for i in range(gap,n):
            j=i
            while j>=gap and arr[j]<arr[j-gap]:
                arr[j],arr[j-gap]=arr[j-gap],arr[j]
                j-=gap
        gap//=2
    return arr

选择排序 Select Sort

原理:从第一个元素开始,找到剩余元素中最小的元素,将它与第一个元素交换位置,然后从第二个元素开始重复这个过程,直到全部排序完成。

模板:

def selection_sort(nums):
    n = len(nums)
    for i in range(n - 1):
        min_index = i
        for j in range(i + 1, n):
            if nums[j] < nums[min_index]:
                min_index = j
        if min_index != i:
            nums[i], nums[min_index] = nums[min_index], nums[i]
    return nums

注释:

min_index表示当前未排序元素中最小值的下标。
if nums[j] < nums[min_index]:表示如果找到了比当前最小值还要小的值,就更新min_index。
if min_index != i:表示如果min_index没有变化,就意味着当前元素已经是未排序元素中最小的,不需要交换。

堆排序 Heap Sort

原理:堆排序是一种选择排序。它是借助堆这种数据结构来完成的。堆分为小根堆和大根堆,大根堆就是堆顶是最大值的堆,小根堆就是堆顶是最小值的堆。堆排序主要分为两个步骤:

  1. 建立堆:将数组建立成大根堆或小根堆。
  2. 排序:将堆顶元素与堆底元素交换,然后删除堆底元素并进行堆调整,重复此过程直到堆中只有一个元素。

模板:

def heapify(a, n, i):   # 将小的节点向下交换,使之成为一棵大根堆
    largest = i
    l = 2 * i + 1  # 左子节点
    r = 2 * i + 2  # 右子节点

    # 找到三个节点中的最大值
    if l < n and a[l] > a[largest]:
        largest = l

    if r < n and a[r] > a[largest]:
        largest = r

    # 如果最大节点不是当前节点,就交换它们的位置,并递归地调用 heapify
    if largest != i:
        a[i], a[largest] = a[largest], a[i]
        heapify(a, n, largest)


def heap_sort(a):
    n = len(a)

    # 构建二叉堆
    for i in range(n // 2 - 1, -1, -1):
        heapify(a, n, i)

    # 针对二叉树的每个节点,将根节点与末尾节点交换,末尾节点被"排序",然后 heapify 重新平衡行,从而保证根节点的最大值
    for i in range(n - 1, 0, -1):
        a[i], a[0] = a[0], a[i]
        heapify(a, i, 0)

    return a

使用 heapq 模块内置函数实现堆排序的 Python 模板:

import heapq

def heap_sort(arr):
    # 将列表转换为堆
    heapq.heapify(arr)

    # 依次弹出堆中元素,即可获得有序序列
    result = []
    while arr:
        result.append(heapq.heappop(arr))

    return result

归并排序 Merge Sort

原理:将序列拆分成每个只有一个元素的子序列,然后对这些子序列进行两两归并,直到所有子序列都归并成一个有序序列。

模板:

def merge_sort(nums):
    if len(nums) <= 1:
        return nums
    mid = len(nums) // 2
    left_nums = nums[:mid]
    right_nums = nums[mid:]
    return merge(merge_sort(left_nums), merge_sort(right_nums)) 	# 先对左右两个子序列进行排序,然后进行归并

def merge(left_nums, right_nums):
    result = []
    i, j = 0, 0
    while i < len(left_nums) and j < len(right_nums):
        if left_nums[i] < right_nums[j]:
            result.append(left_nums[i])
            i += 1
        else:
            result.append(right_nums[j])
            j += 1
    result += left_nums[i:]
    result += right_nums[j:]
    return result

计数排序 Counting Sort

原理:计数排序是一种非比较排序算法,它的主要思想是对于给定的一组元素,确定每个元素在序列中出现的个数,进而推算出每个元素在最终有序序列中所处的位置。要求输入的数据必须是有确定范围的整数(如内部所有元素均在 0~K 的范围内),并且是离散的元素。

在计数排序中,我们利用待排序数列中的最大值和最小值来确定统计数组的长度,然后逐个遍历计算出待排序序列中每个元素出现的次数。接下来,我们依据统计数组中记录的元素出现次数,从小到大或从大到小地做一个累加,目的是确定待排序元素在有序序列中的索引位置。最后,我们对待排序序列进行遍历,按照有序序列的索引位置将待排序元素放入其应该在的位置,从而得到有序的序列。

适应场景:要求输入的数据必须是有确定范围的整数,适合于最大值和最小值的差值不是很大的情况,或对时间复杂度要求较高,快排O(nlogn) 解决不了,需要O(n) 或题目明确约束要线性时间内运行的场景。基本思想就是把数组元素作为数组的下标,然后用一个临时数组统计该元素出现的次数,例如 temp[i] = m, 表示元素 i 一共出现了 m 次。最后再把临时数组统计的数据从小到大汇总起来。

:LeetCode计数排序,274,561, 912,1122
模板:

def counting_sort(arr):
    size = len(arr)
    if size<2:  # 处理长度为0或1的情况
        return arr

    # 找到序列中的最大值和最小值,以确定计数数组(c)的长度
    max_val = max(arr)
    min_val = min(arr)

    # 统计数组c用于记录元素i出现的次数
    c = [0] * (max_val - min_val + 1)
    for i in arr:
        c[i - min_val] += 1

    # 累加计数数组c并更新这些元素的顺序
    for i in range(1, len(c)):
        c[i] = c[i - 1] + c[i]

    # 创建一个临时数组,用于存储排序后的结果
    ans = [0] * size
    for i in reversed(arr):
        ans[c[i - min_val] - 1] = i
        c[i - min_val] -= 1

    return ans

基数排序(Radix Sort)

原理:基数排序是按照数字位数从低到高依次对待排序数字进行排序,最终可以获得有序序列。具体实现中,每次操作时将待排序序列按照指定位数进行分桶,然后按照顺序将桶中的数字输出,重复该过程直到全部数字排序完成。

以下是基数排序的实现模板:

def radix_sort(nums):
    # 找到待排序序列中最大的数字
    max_digit = 0
    for num in nums:
        max_digit = max(max_digit, len(str(num)))
    # 从个位到最高位进行排序
    for i in range(max_digit):
        # 初始化10个桶
        bucket = [[] for _ in range(10)]
        # 将每个数字分配到对应的桶中
        for num in nums:
            digit = num // (10 ** i) % 10
            bucket[digit].append(num)
        # 重构待排序序列为已分配的桶中的数字
        nums.clear()
        for j in range(10):
            nums += bucket[j]
    return nums

桶排序(Bucket Sort)

原理:桶排序是一种按照区间依次对数字进行排序的算法。具体实现中,首先需要将待排序序列分配到不同的桶中,然后对每一个非空的桶进行排序,并将结果合并到一起。在桶排序中,分配每个数字到桶中的过程可以使用不同的划分函数实现,比如可以按照数字的区间、数字的排名等进行划分。

适应场景:数列取值范围过大,或者不是整数时,如double,就可以用桶排序。桶排序的性能并非绝对稳定,理想情况是桶中的元素分布均匀,元素个数等于桶的个数时,时间复杂度可以达到O(n);但如果桶内元素的分布极不均衡,极端情况下第一个桶中有n-1个元素,最后一个桶中有1个元素,时间复杂度将退化为O(nlogn) ,还白白浪费空间,创建了许多空桶。
典型题目:LeetCode桶排序,164,220,347,451,692, 908
以下是桶排序的实现模板:

def linear_sort(nums):
    max_value = max(nums)    # 找到待排序序列中的最大值
    bucket = [0] * (max_value + 1)		# 初始化一个长度为max_value + 1的桶,用于存放每个数字的出现次数    
    for num in nums:		# 遍历待排序序列,统计每个数字出现的次数
        bucket[num] += 1    
    count = 0		# 记录桶中出现的数字的个数 
    for i in range(max_value + 1):		 # 遍历桶,输出排序结果
        while bucket[i] > 0:
            nums[count] = i
            count += 1
            bucket[i] -= 1
    return nums

你可能感兴趣的:(排序算法,python,算法,笔记)