文章目录
前言
在前六篇文章中,我们学习了各种基础数据结构和排序算法。本文将深入探讨两个重要的算法领域:搜索算法和动态规划。搜索算法是解决查找问题的基本方法,而动态规划则是解决最优化问题的强大工具。这两种算法在实际应用中有着广泛的应用场景,如搜索引擎、路径规划、资源分配等。
一、搜索算法
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) # 返回结果
三、练习题
-
实现一个函数,在旋转排序数组中查找目标值。
-
实现一个函数,计算两个字符串的最长公共子串。
-
实现一个函数,解决完全背包问题。
四、思考题
-
深度优先搜索和广度优先搜索各有什么优缺点?在什么情况下应该选择哪种搜索算法?
-
动态规划问题有哪些共同特征?如何判断一个问题是否适合用动态规划解决?
-
如何优化动态规划算法的空间复杂度?
五、总结
本文详细介绍了搜索算法和动态规划的基本概念、实现方法和应用场景。搜索算法包括线性搜索、二分搜索、深度优先搜索和广度优先搜索,它们分别适用于不同的场景。动态规划则通过将问题分解为子问题并存储子问题的解,有效地解决了具有重叠子问题和最优子结构性质的问题。理解这些算法的原理和实现,对于解决实际问题非常重要。
六、参考资料
- 《算法导论》
- Python官方文档
- LeetCode题库
- 各大技术博客和论坛