Python经典算法实现:排序、搜索与动态规划代码示例

栗子
栗子
发布时间:2026-10-26 | 6人浏览过
原创
算法设计的核心在于利用数据的内在规律,在有限的计算资源下求得正确解。本文系统梳理计算机科学中最经典的三个基础领域:排序、搜索与动态规划。通过提供规范、严谨的 Python 完整实现代码、逐步推导示意图与边界处理要点,帮助读者建立从“理解算法思路”到“独立编写可运行程序”的扎实落地能力。
Python经典算法实现:排序、搜索与动态规划代码示例
图 1:Python 经典算法分类、核心策略与典型时空特征总览

一、经典排序算法:快速排序与归并排序

排序是将一组乱序数据按照特定单调规则(递增或递减)重新排列的运算过程。在实际工程与算法竞赛中,基于分治法(Divide and Conquer)的快速排序(QuickSort)和归并排序(MergeSort)应用最为广泛。两者均将庞大的全量数组拆解为独立的子数组,但在“划分”与“合并”的时机上存在本质区别。

1. 快速排序(QuickSort)原理与原地实现

快速排序通过选取一个基准元素(Pivot),通过单次分区操作(Partition)将原数组重新排布,使得基准左侧的所有元素均不大于基准,右侧所有元素均不小于基准。随后,基准的位置永久确定,程序仅需递归处理左右两个子区间。

def quick_sort(arr: list[int], low: int, high: int) -> None:
    """
    快速排序原地实现(双指针 Lomuto 分区方案)
    :param arr: 待排序列表
    :param low: 当前排序区间的起始下标
    :param high: 当前排序区间的结束下标
    """
    if low < high:
        # 获得基准分割点索引
        pivot_index = partition(arr, low, high)
        # 递归排序左半区间
        quick_sort(arr, low, pivot_index - 1)
        # 递归排序右半区间
        quick_sort(arr, pivot_index + 1, high)


def partition(arr: list[int], low: int, high: int) -> int:
    """
    分区函数:以末尾元素作为基准,调整数组并返回基准最终归位索引
    """
    pivot = arr[high]
    i = low - 1  # 小于等于 pivot 区域的右边界

    for j in range(low, high):
        if arr[j] <= pivot:
            i += 1
            arr[i], arr[j] = arr[j], arr[i]

    # 将基准元素放置在正确定位处
    arr[i + 1], arr[high] = arr[high], arr[i + 1]
    return i + 1


# 示例执行代码
if __name__ == "__main__":
    test_data = [29, 10, 14, 37, 13]
    print(f"排序前: {test_data}")
    quick_sort(test_data, 0, len(test_data) - 1)
    print(f"排序后: {test_data}")
示例运行输出: 排序前: [29, 10, 14, 37, 13] 排序后: [10, 13, 14, 29, 37]
Python经典算法实现:排序、搜索与动态规划代码示例
图 2:快速排序基于单向扫描分区的状态演进过程推导

2. 归并排序(MergeSort)代码实现

归并排序则走相反的路线:它并不在下潜阶段调整元素,而是直接从中间将数组对半分裂,直到子数组长度为 1;随后在回溯上升阶段,通过双指针比较,将两个有序子列表合并为一个新的有序列表。这种思路保证了极其稳定的 O(n log n) 时间开销。

def merge_sort(arr: list[int]) -> list[int]:
    """
    归并排序实现(稳定排序,分治合并)
    :param arr: 输入列表
    :return: 全新排好序的列表
    """
    if len(arr) <= 1:
        return arr

    mid = len(arr) // 2
    left_half = merge_sort(arr[:mid])
    right_half = merge_sort(arr[mid:])

    return merge(left_half, right_half)


def merge(left: list[int], right: list[int]) -> list[int]:
    """双指针合并两个有序序列"""
    merged = []
    i = j = 0

    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            merged.append(left[i])
            i += 1
        else:
            merged.append(right[j])
            j += 1

    # 追加剩余元素
    merged.extend(left[i:])
    merged.extend(right[j:])
    return merged


if __name__ == "__main__":
    nums = [38, 27, 43, 3, 9, 82, 10]
    sorted_nums = merge_sort(nums)
    print(f"归并排序输入: {nums}")
    print(f"归并排序输出: {sorted_nums}")
注意事项:快速排序在最坏情况下(例如输入本身已经完全有序且总是选固定末尾为基准)时间复杂度会退化为 O(n²)。工程中常使用“三数取中法”(首、中、尾中位值)或随机选取基准来规避这一退化风险。

二、经典搜索算法:二分搜索的精细控制

搜索算法旨在从数据集中快速定位目标值或确定其不存在。顺序线性搜索的时间复杂度为 O(n),当数据规模庞大时效率极低。如果待检索序列具备“单调有序”的先决条件,二分搜索(Binary Search)即可发挥巨大威力,每次判定排除一半候选空间,将时间压制在 O(log n)。

1. 二分搜索代码实现(迭代与递归版本)

二分搜索代码看似简短,但极易在循环终止条件、区间闭合方式以及索引防溢出上出现 Off-by-one 错误。以下为标准的闭区间 [left, right] 迭代与递归实现:

def binary_search_iterative(arr: list[int], target: int) -> int:
    """
    二分搜索(双指针迭代实现,双闭区间 [left, right])
    :param arr: 升序排列的整数列表
    :param target: 待查找目标值
    :return: 目标所在索引;若不存在则返回 -1
    """
    left = 0
    right = len(arr) - 1

    # 双闭区间下,left == right 仍是有意义的探测点
    while left <= right:
        # 防止较大数值下标相加溢出 (Python虽自动处理长整型,此为通用算法规范)
        mid = left + (right - left) // 2

        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1   # 目标在右侧,收缩左边界
        else:
            right = mid - 1  # 目标在左侧,收缩右边界

    return -1


def binary_search_recursive(arr: list[int], target: int, left: int, right: int) -> int:
    """二分搜索(递归调用实现)"""
    if left > right:
        return -1

    mid = left + (right - left) // 2
    if arr[mid] == target:
        return mid
    elif arr[mid] < target:
        return binary_search_recursive(arr, target, mid + 1, right)
    else:
        return binary_search_recursive(arr, target, left, mid - 1)


if __name__ == "__main__":
    ordered_list = [4, 8, 12, 16, 23, 29, 35, 42]
    target_val = 23
    idx = binary_search_iterative(ordered_list, target_val)
    print(f"数据列表: {ordered_list}")
    print(f"目标数值: {target_val},查找索引为: {idx}")
示例运行输出: 数据列表: [4, 8, 12, 16, 23, 29, 35, 42] 目标数值: 23,查找索引为: 4
Python经典算法实现:排序、搜索与动态规划代码示例
图 3:二分查找在有序数组中缩小边界直至命中目标值的完整状态机示意

三、动态规划算法:从重叠子问题到状态转移

动态规划(Dynamic Programming, 简称 DP)主要用于求解多阶段决策的最优化问题。它的核心逻辑是:将原问题拆解为相互重叠的子问题,并记录子问题的最优解,避免重复计算。

判断一个问题是否适合使用动态规划,主要关注两个充要属性:

  • 最优子结构:大问题的最优解包含着子问题的最优解。
  • 重叠子问题:在自顶向下的递归树中,存在大量参数完全相同的重复计算节点。

1. 经典模型:0-1 背包问题(Knapsack Problem)

给定一个承重上限为 W 的背包,以及 n 件具有特定重量 w[i] 和价值 v[i] 的物品。每件物品仅有一件,决策者只能选择“装入”(1)或“不装入”(0)。目标是在背包总承重不超标的前提下,使得装入物品的总价值最大。

设状态 dp[i][w] 表示:仅考虑前 i 件物品,背包容量为 w 时能获取的最大总价值。状态转移方程如下:

# 若当前物品重量大于背包容量,无法装入:
dp[i][w] = dp[i-1][w]

# 若能装入,则在“不装”与“装入”两种决策间取最大值:
dp[i][w] = max(dp[i-1][w], dp[i-1][w - weights[i-1]] + values[i-1])

2. Python 完整代码示例(二维 DP 与一维空间压缩)

def knapsack_01_2d(weights: list[int], values: list[int], capacity: int) -> int:
    """
    0-1背包标准二维动态规划实现
    :param weights: 物品重量列表
    :param values: 物品价值列表
    :param capacity: 背包最大承重
    :return: 最大可能达到的价值总和
    """
    n = len(weights)
    # 初始化 (n+1) x (capacity+1) 的 DP 表格,基准状态为 0
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]

    for i in range(1, n + 1):
        w = weights[i - 1]
        v = values[i - 1]
        for c in range(1, capacity + 1):
            if c >= w:
                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - w] + v)
            else:
                dp[i][c] = dp[i - 1][c]

    return dp[n][capacity]


def knapsack_01_optimized(weights: list[int], values: list[int], capacity: int) -> int:
    """
    0-1背包空间优化(一维滚动数组逆序遍历)
    空间复杂度从 O(n*capacity) 降低至 O(capacity)
    """
    dp = [0] * (capacity + 1)

    for w, v in zip(weights, values):
        # 必须逆序遍历,确保计算 dp[c] 时所依赖的 dp[c-w] 尚未被本轮物品污染
        for c in range(capacity, w - 1, -1):
            dp[c] = max(dp[c], dp[c - w] + v)

    return dp[capacity]


if __name__ == "__main__":
    item_weights = [1, 3, 4]
    item_values = [15, 20, 30]
    bag_cap = 4

    res_2d = knapsack_01_2d(item_weights, item_values, bag_cap)
    res_1d = knapsack_01_optimized(item_weights, item_values, bag_cap)

    print(f"物品规格: 重量={item_weights}, 价值={item_values}, 容量={bag_cap}")
    print(f"二维 DP 结果: {res_2d}")
    print(f"一维压缩 DP 结果: {res_1d}")
示例运行输出: 物品规格: 重量=[1, 3, 4], 价值=[15, 20, 30], 容量=4 二维 DP 结果: 35 一维压缩 DP 结果: 35
Python经典算法实现:排序、搜索与动态规划代码示例
图 4:0-1背包问题二维动态规划填表路径与状态转移取舍过程

四、三大类算法复杂度与选型决策指南

在实际软件架构中,算法的选择并非盲目追求理论最高效率,而是根据输入数据的分布形态、规模极限以及内存敏感度进行综合权衡。下表总结了本文涉及的经典算法特性:

算法类型 算法名称 平均时间复杂度 最坏时间复杂度 额外空间复杂度 主要依赖条件与场景
排序算法 快速排序 (QuickSort) O(n log n) O(n²) O(log n) 原地递归栈 内存受限、追求常数项性能,需防范极端逆序退化
归并排序 (MergeSort) O(n log n) O(n log n) O(n) 额外数组 要求排序稳定性(相同元素相对顺序不变)、外部海量排序
搜索算法 二分搜索 (Binary Search) O(log n) O(log n) O(1) 迭代方案 严格要求数据已单调排序;常用于高频只读查询与极值判定
动态规划 0-1 背包问题 O(n · W) O(n · W) O(W) 滚动压缩 背包容量 W 为离散正整数,具备重叠子问题和无后效性
工程选型总结: 若数据杂乱且需要快速有序,首选经过基准防退化处理的快速排序;若需要对不可变有序列表进行超低延迟的按需检索,二分搜索是最佳实践;当面对具有多阶段依赖的收益最大化或成本最小化目标时,先审视问题是否具备“最优子结构”并写出递归关系,再通过动态规划自底向上填表求解。
相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表