【七】Python数据结构与算法:搜索与动态规划

在这里插入图片描述

前言

在前六篇文章中,我们学习了各种基础数据结构和排序算法。本文将深入探讨两个重要的算法领域:搜索算法和动态规划。搜索算法是解决查找问题的基本方法,而动态规划则是解决最优化问题的强大工具。这两种算法在实际应用中有着广泛的应用场景,如搜索引擎、路径规划、资源分配等。

一、搜索算法

1. 线性搜索

线性搜索是最简单的搜索算法,它通过逐个检查元素来查找目标值。虽然时间复杂度较高,但实现简单,适用于小规模数据集。

def linear_search(arr, target):
    """
    线性搜索算法
    参数:
        arr: 待搜索的数组
        target: 目标值
    返回:
        目标值在数组中的索引,如果未找到则返回-1
    时间复杂度: O(n)
    空间复杂度: O(1)
    """
    for i in range(len(arr)):  # 遍历数组中的每个元素
        if arr[i] == target:   # 如果找到目标值
            return i           # 返回其索引
    return -1                  # 未找到目标值,返回-1

2. 二分搜索

二分搜索是一种高效的搜索算法,它要求数据必须是有序的。通过不断将搜索范围减半,可以快速定位目标值。

def binary_search(arr, target):
    """
    二分搜索算法
    参数:
        arr: 已排序的数组
        target: 目标值
    返回:
        目标值在数组中的索引,如果未找到则返回-1
    时间复杂度: O(log n)
    空间复杂度: O(1)
    """
    left, right = 0, len(arr) - 1  # 初始化搜索范围的左右边界
    
    while left <= right:           # 当搜索范围有效时
        mid = (left + right) // 2  # 计算中间位置
        if arr[mid] == target:     # 如果找到目标值
            return mid             # 返回其索引
        elif arr[mid] < target:    # 如果中间值小于目标值
            left = mid + 1         # 调整左边界
        else:                      # 如果中间值大于目标值
            right = mid - 1        # 调整右边界
    
    return -1                      # 未找到目标值,返回-1

3. 深度优先搜索(DFS)

深度优先搜索是一种用于遍历或搜索树或图的算法。它沿着树的深度遍历节点,尽可能深地搜索树的分支。

def dfs(graph, start, visited=None):
    """
    深度优先搜索算法
    参数:
        graph: 图的邻接表表示
        start: 起始顶点
        visited: 已访问的顶点集合
    返回:
        无返回值,直接打印访问顺序
    时间复杂度: O(V + E)
    空间复杂度: O(V)
    """
    if visited is None:            # 初始化已访问集合
        visited = set()
    
    visited.add(start)             # 标记当前顶点为已访问
    print(start, end=' ')          # 打印当前顶点
    
    for neighbor in graph[start]:  # 遍历当前顶点的所有邻居
        if neighbor not in visited:  # 如果邻居未被访问
            dfs(graph, neighbor, visited)  # 递归访问邻居

4. 广度优先搜索(BFS)

广度优先搜索是一种用于遍历或搜索树或图的算法。它从根节点开始,逐层遍历所有节点。

from collections import deque

def bfs(graph, start):
    """
    广度优先搜索算法
    参数:
        graph: 图的邻接表表示
        start: 起始顶点
    返回:
        无返回值,直接打印访问顺序
    时间复杂度: O(V + E)
    空间复杂度: O(V)
    """
    visited = set()                # 初始化已访问集合
    queue = deque([start])         # 使用队列存储待访问顶点
    visited.add(start)             # 标记起始顶点为已访问
    
    while queue:                   # 当队列不为空时
        vertex = queue.popleft()   # 取出队首顶点
        print(vertex, end=' ')     # 打印当前顶点
        
        for neighbor in graph[vertex]:  # 遍历当前顶点的所有邻居
            if neighbor not in visited:  # 如果邻居未被访问
                visited.add(neighbor)    # 标记邻居为已访问
                queue.append(neighbor)   # 将邻居加入队列

二、动态规划

1. 动态规划概述

动态规划是一种解决最优化问题的方法,它将问题分解为子问题,并存储子问题的解以避免重复计算。动态规划通常用于解决具有重叠子问题和最优子结构性质的问题。

2. 斐波那契数列

斐波那契数列是动态规划的经典示例,它展示了如何通过存储子问题的解来避免重复计算。

def fibonacci(n):
    """
    计算第n个斐波那契数
    参数:
        n: 要计算的斐波那契数的位置
    返回:
        第n个斐波那契数
    时间复杂度: O(n)
    空间复杂度: O(n)
    """
    if n <= 1:                    # 基本情况
        return n
    
    dp = [0] * (n + 1)           # 创建动态规划数组
    dp[1] = 1                     # 初始化前两个数
    
    for i in range(2, n + 1):     # 从第3个数开始计算
        dp[i] = dp[i-1] + dp[i-2]  # 状态转移方程
    
    return dp[n]                   # 返回结果

3. 最长公共子序列

最长公共子序列问题要求找出两个序列的最长公共子序列的长度。

def longest_common_subsequence(text1, text2):
    """
    计算两个字符串的最长公共子序列长度
    参数:
        text1: 第一个字符串
        text2: 第二个字符串
    返回:
        最长公共子序列的长度
    时间复杂度: O(mn)
    空间复杂度: O(mn)
    """
    m, n = len(text1), len(text2)  # 获取字符串长度
    dp = [[0] * (n + 1) for _ in range(m + 1)]  # 创建动态规划数组
    
    for i in range(1, m + 1):      # 遍历第一个字符串
        for j in range(1, n + 1):  # 遍历第二个字符串
            if text1[i-1] == text2[j-1]:  # 如果字符相同
                dp[i][j] = dp[i-1][j-1] + 1  # 状态转移方程1
            else:                    # 如果字符不同
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])  # 状态转移方程2
    
    return dp[m][n]                # 返回结果

4. 0-1背包问题

0-1背包问题要求在给定容量的背包中装入价值最大的物品,每个物品只能选择装入或不装入。

def knapsack(weights, values, capacity):
    """
    解决0-1背包问题
    参数:
        weights: 物品重量列表
        values: 物品价值列表
        capacity: 背包容量
    返回:
        背包能装的最大价值
    时间复杂度: O(nW)
    空间复杂度: O(nW)
    """
    n = len(weights)              # 物品数量
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]  # 创建动态规划数组
    
    for i in range(1, n + 1):     # 遍历每个物品
        for w in range(1, capacity + 1):  # 遍历每个可能的容量
            if weights[i-1] <= w:  # 如果当前物品可以放入背包
                # 选择放入或不放入的最大值
                dp[i][w] = max(dp[i-1][w], dp[i-1][w-weights[i-1]] + values[i-1])
            else:                  # 如果当前物品不能放入背包
                dp[i][w] = dp[i-1][w]  # 不放入当前物品
    
    return dp[n][capacity]        # 返回结果

5. 最长递增子序列

最长递增子序列问题要求找出给定序列的最长递增子序列的长度。

def longest_increasing_subsequence(nums):
    """
    计算最长递增子序列的长度
    参数:
        nums: 输入序列
    返回:
        最长递增子序列的长度
    时间复杂度: O(n^2)
    空间复杂度: O(n)
    """
    if not nums:                  # 处理空序列
        return 0
    
    n = len(nums)                 # 序列长度
    dp = [1] * n                  # 初始化动态规划数组
    
    for i in range(1, n):         # 遍历每个元素
        for j in range(i):        # 检查前面的所有元素
            if nums[i] > nums[j]:  # 如果当前元素大于前面的元素
                dp[i] = max(dp[i], dp[j] + 1)  # 更新最长递增子序列长度
    
    return max(dp)                # 返回结果

三、练习题

  1. 实现一个函数,在旋转排序数组中查找目标值。

  2. 实现一个函数,计算两个字符串的最长公共子串。

  3. 实现一个函数,解决完全背包问题。

四、思考题

  1. 深度优先搜索和广度优先搜索各有什么优缺点?在什么情况下应该选择哪种搜索算法?

  2. 动态规划问题有哪些共同特征?如何判断一个问题是否适合用动态规划解决?

  3. 如何优化动态规划算法的空间复杂度?

五、总结

本文详细介绍了搜索算法和动态规划的基本概念、实现方法和应用场景。搜索算法包括线性搜索、二分搜索、深度优先搜索和广度优先搜索,它们分别适用于不同的场景。动态规划则通过将问题分解为子问题并存储子问题的解,有效地解决了具有重叠子问题和最优子结构性质的问题。理解这些算法的原理和实现,对于解决实际问题非常重要。

六、参考资料

  1. 《算法导论》
  2. Python官方文档
  3. LeetCode题库
  4. 各大技术博客和论坛
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Aerkui

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值