1. 搜索入门基础与常见题型解析
刚接触搜索算法时,很多新手会被各种变体题目绕晕。我在ACM竞赛和算法教学中发现,掌握搜索的核心在于理解其本质——系统性地枚举可能性。就像玩迷宫游戏时,我们总会尝试每条岔路并标记已探索区域,这就是深度优先搜索(DFS)和广度优先搜索(BFS)最直观的体现。
搜索题通常分为几个经典类型:
- 矩阵路径类(如迷宫最短路径)
- 排列组合类(如八皇后问题)
- 状态转换类(如华容道拼图)
- 优化剪枝类(如数独求解)
关键认知:所有搜索算法都是"暴力解法"的优化,区别在于探索顺序和剪枝策略
2. 典型题目实战拆解
2.1 矩阵中的单词搜索(LeetCode 79)
给定二维字符网格和一个单词,判断单词是否存在于网格中。这是典型的DFS应用场景:
python复制def exist(board, word):
def dfs(i, j, k):
if not 0<=i<len(board) or not 0<=j<len(board[0]) or board[i][j]!=word[k]:
return False
if k == len(word)-1:
return True
tmp, board[i][j] = board[i][j], '/'
res = dfs(i+1,j,k+1) or dfs(i-1,j,k+1) or dfs(i,j+1,k+1) or dfs(i,j-1,k+1)
board[i][j] = tmp
return res
for i in range(len(board)):
for j in range(len(board[0])):
if dfs(i, j, 0):
return True
return False
避坑指南:
- 必须使用
/标记已访问位置,否则会重复使用字符 - 四个方向的搜索可以用direction数组简化:
python复制directions = [(-1,0),(1,0),(0,-1),(0,1)] for dx, dy in directions: if dfs(i+dx, j+dy, k+1): return True
2.2 岛屿数量问题(LeetCode 200)
计算二维网格中岛屿的数量,经典连通分量问题:
python复制def numIslands(grid):
count = 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] == '1':
dfs(grid, i, j)
count += 1
return count
def dfs(grid, i, j):
if i<0 or j<0 or i>=len(grid) or j>=len(grid[0]) or grid[i][j]!='1':
return
grid[i][j] = '0'
dfs(grid, i+1, j)
dfs(grid, i-1, j)
dfs(grid, i, j+1)
dfs(grid, i, j-1)
性能优化点:
- 将二维网格视为图,每个单元格是节点,相邻'1'是边
- 时间复杂度O(M×N),空间复杂度最坏情况O(M×N)(递归栈深度)
3. 搜索算法的进阶技巧
3.1 双向BFS实战
以单词接龙(LeetCode 127)为例,传统BFS会超时:
python复制def ladderLength(beginWord, endWord, wordList):
if endWord not in wordList:
return 0
front = {beginWord}
back = {endWord}
wordList = set(wordList)
length = 1
while front:
length += 1
next_front = set()
for word in front:
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
new_word = word[:i] + c + word[i+1:]
if new_word in back:
return length
if new_word in wordList:
next_front.add(new_word)
wordList.remove(new_word)
front = next_front
if len(front) > len(back):
front, back = back, front
return 0
双向BFS优势:
- 搜索空间从O(b^d)降到O(b^(d/2)),b是分支因子,d是深度
- 每次从较小的一端扩展,平衡两端搜索进度
3.2 A*搜索算法实例
以滑动谜题(LeetCode 773)为例,使用启发式搜索:
python复制def slidingPuzzle(board):
target = (1,2,3,4,5,0)
start = tuple(board[0] + board[1])
heap = [(0, start)]
cost = {start: 0}
while heap:
_, current = heapq.heappop(heap)
if current == target:
return cost[current]
zero = current.index(0)
for d in (-1, 1, -3, 3):
neighbor = zero + d
if abs(neighbor//3 - zero//3) + abs(neighbor%3 - zero%3) != 1:
continue
if 0 <= neighbor < 6:
new_state = list(current)
new_state[zero], new_state[neighbor] = new_state[neighbor], new_state[zero]
new_state = tuple(new_state)
new_cost = cost[current] + 1
if new_state not in cost or new_cost < cost[new_state]:
cost[new_state] = new_cost
priority = new_cost + heuristic(new_state)
heapq.heappush(heap, (priority, new_state))
return -1
def heuristic(state):
# 曼哈顿距离
distance = 0
for i in range(6):
if state[i] == 0:
continue
row, col = i//3, i%3
target_row = (state[i]-1)//3
target_col = (state[i]-1)%3
distance += abs(row - target_row) + abs(col - target_col)
return distance
启发函数设计要点:
- 必须可接受(不大于实际成本)
- 越接近真实成本,算法效率越高
- 本例使用曼哈顿距离评估每个数字到目标位置的距离和
4. 常见错误与调试技巧
4.1 栈溢出问题
DFS递归深度过大时会出现栈溢出。以二叉树最大深度为例:
python复制# 危险写法(链状树会栈溢出)
def maxDepth(root):
if not root:
return 0
return 1 + max(maxDepth(root.left), maxDepth(root.right))
# 安全写法(改为迭代)
def maxDepth(root):
stack = [(root, 1)]
max_depth = 0
while stack:
node, depth = stack.pop()
if node:
max_depth = max(max_depth, depth)
stack.append((node.left, depth+1))
stack.append((node.right, depth+1))
return max_depth
4.2 访问顺序导致的BUG
BFS中忘记标记已访问节点会导致重复入队:
python复制# 错误示例
queue = [start]
while queue:
node = queue.pop(0)
for neighbor in get_neighbors(node):
queue.append(neighbor) # 会重复添加
# 正确写法
visited = set([start])
queue = [start]
while queue:
node = queue.pop(0)
for neighbor in get_neighbors(node):
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
4.3 剪枝优化案例
在组合总和问题(LeetCode 39)中,排序+剪枝大幅提升效率:
python复制def combinationSum(candidates, target):
res = []
candidates.sort() # 关键排序
def backtrack(start, path, target):
if target == 0:
res.append(path.copy())
return
for i in range(start, len(candidates)):
if candidates[i] > target: # 提前终止
break
path.append(candidates[i])
backtrack(i, path, target-candidates[i])
path.pop()
backtrack(0, [], target)
return res
剪枝原理:
- 排序后当
candidates[i] > target时,后面更大的数必然不符合 - 使用
start参数避免生成重复组合(如[2,2,3]和[2,3,2])
5. 性能对比与算法选择
通过N皇后问题对比DFS和回溯法的效率差异:
| 算法类型 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 朴素DFS | O(N^N) | O(N^2) | N较小(N<10) |
| 回溯+剪枝 | O(N!) | O(N) | 中等规模(N<15) |
| 位运算优化 | O(N!) | O(1) | 大规模(N<32) |
位运算优化示例:
python复制def totalNQueens(n):
def backtrack(row, cols, diags, anti_diags):
if row == n:
return 1
count = 0
for col in range(n):
diag = row - col
anti_diag = row + col
if (col in cols
or diag in diags
or anti_diag in anti_diags):
continue
cols.add(col)
diags.add(diag)
anti_diags.add(anti_diag)
count += backtrack(row+1, cols, diags, anti_diags)
cols.remove(col)
diags.remove(diag)
anti_diags.remove(anti_diag)
return count
return backtrack(0, set(), set(), set())
选择建议:
- 当N<10时,任何方法都可
- N≥15时,必须使用位运算或对称性优化
- 实际测试显示:当N=15时,朴素DFS需要300秒,位运算仅需2秒
6. 竞赛中的特殊技巧
6.1 状态压缩技巧
在解决如"最短哈密尔顿路径"问题时,需要用二进制表示节点访问状态:
python复制def shortestPath(graph):
n = len(graph)
dp = [[float('inf')] * n for _ in range(1<<n)]
dp[1][0] = 0 # 状态1表示只访问过0号节点
for s in range(1<<n):
for i in range(n):
if not (s & (1<<i)):
continue
for j in range(n):
if s & (1<<j):
continue
new_s = s | (1<<j)
dp[new_s][j] = min(dp[new_s][j], dp[s][i]+graph[i][j])
return min(dp[(1<<n)-1][i] + graph[i][0] for i in range(1,n))
关键点:
- 状态s的二进制第k位表示是否访问过节点k
- 时间复杂度O(n^2 * 2^n),空间O(n * 2^n)
6.2 Meet-in-the-Middle算法
对于子集和问题,当N=40时,传统O(2^N)算法不可行:
python复制def subsetSum(nums, target):
n = len(nums)
left = nums[:n//2]
right = nums[n//2:]
left_sums = set()
for mask in range(1<<len(left)):
s = sum(x for i,x in enumerate(left) if mask&(1<<i))
left_sums.add(s)
for mask in range(1<<len(right)):
s = sum(x for i,x in enumerate(right) if mask&(1<<i))
if (target - s) in left_sums:
return True
return False
算法优势:
- 时间复杂度从O(2^N)降到O(N*2^(N/2))
- 空间复杂度O(2^(N/2)),通常可接受
7. 可视化调试方法
对于复杂的搜索问题,我常用两种可视化方法:
- 打印搜索树(适合递归算法):
python复制def backtrack(path, choices, depth=0):
print(" "*depth + f"Depth {depth}: {path}")
if is_solution(path):
return path
for choice in choices:
path.append(choice)
result = backtrack(path, prune_choices(choices, path), depth+1)
if result:
return result
path.pop()
return None
- 绘制搜索过程(使用networkx和matplotlib):
python复制import networkx as nx
import matplotlib.pyplot as plt
def visualize_search(graph, path):
pos = nx.spring_layout(graph)
nx.draw(graph, pos, with_labels=True)
edge_list = [(path[i], path[i+1]) for i in range(len(path)-1)]
nx.draw_networkx_edges(graph, pos, edgelist=edge_list,
edge_color='r', width=2)
plt.show()
调试心得:
- 对于DFS,关注递归深度是否合理
- 对于BFS,检查队列长度增长是否正常
- 当程序卡住时,先打印前几步搜索过程
8. 经典题目训练路线
根据难度梯度推荐的练习顺序:
-
入门阶段:
-
- 岛屿数量
-
- 图像渲染
-
- 岛屿的最大面积
-
-
进阶阶段:
-
- 单词接龙
-
- 单词接龙 II
-
- 被围绕的区域
-
-
高手阶段:
-
- 删除无效的括号
-
- 祖玛游戏
-
- 破解保险箱
-
-
竞赛专项:
- 状态压缩DP:847. 访问所有节点的最短路径
- 双向BFS:752. 打开转盘锁
- A*算法:773. 滑动谜题
训练建议:每个类别至少完成3题再进入下一阶段,重点理解如何将实际问题转化为搜索问题
9. 搜索算法的变体与延伸
9.1 迭代加深搜索(IDDFS)
结合BFS和DFS的优点,适用于状态空间大但解深度小的情况:
python复制def iddfs(start, target, max_depth):
for depth in range(max_depth):
visited = set()
if dls(start, target, depth, visited):
return True
return False
def dls(node, target, depth, visited):
if node == target:
return True
if depth <= 0:
return False
visited.add(node)
for neighbor in get_neighbors(node):
if neighbor not in visited:
if dls(neighbor, target, depth-1, visited):
return True
return False
9.2 爬山算法与模拟退火
用于优化问题的局部搜索方法:
python复制def hill_climbing(start):
current = start
while True:
neighbors = get_neighbors(current)
if not neighbors:
break
neighbor = max(neighbors, key=evaluate)
if evaluate(neighbor) <= evaluate(current):
break
current = neighbor
return current
def simulated_annealing(start):
current = start
temp = INIT_TEMP
while temp > FINAL_TEMP:
neighbor = random.choice(get_neighbors(current))
delta = evaluate(neighbor) - evaluate(current)
if delta > 0 or random.random() < math.exp(delta/temp):
current = neighbor
temp *= COOLING_RATE
return current
10. 实际工程中的应用案例
10.1 文件系统搜索优化
实现快速文件搜索时,可以结合多种算法:
python复制class FileSearcher:
def __init__(self, root_dir):
self.index = {} # {filename: path}
self.build_index(root_dir)
def build_index(self, dir):
# BFS构建索引
from collections import deque
q = deque([dir])
while q:
curr = q.popleft()
for entry in os.scandir(curr):
if entry.is_file():
self.index[entry.name] = entry.path
elif entry.is_dir():
q.append(entry.path)
def search(self, pattern):
# 正则匹配+前缀树优化
import re
regex = re.compile(pattern)
return [path for name, path in self.index.items()
if regex.search(name)]
10.2 游戏AI中的路径规划
RTS游戏中的单位移动常用A*算法变体:
python复制class GameUnit:
def find_path(self, target):
open_set = PriorityQueue()
open_set.put((0, self.position))
came_from = {}
g_score = {self.position: 0}
while not open_set.empty():
current = open_set.get()[1]
if current == target:
return reconstruct_path(came_from, current)
for neighbor in self.get_neighbors(current):
tentative_g = g_score[current] + self.move_cost(current, neighbor)
if neighbor not in g_score or tentative_g < g_score[neighbor]:
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f_score = tentative_g + self.heuristic(neighbor, target)
open_set.put((f_score, neighbor))
return None
def heuristic(self, a, b):
# 曼哈顿距离带地形权重
dx = abs(a.x - b.x)
dy = abs(a.y - b.y)
return (dx + dy) * self.terrain_cost(a, b)
11. 性能优化深度技巧
11.1 记忆化搜索实战
以滑雪问题(LeetCode 329)为例:
python复制def longestIncreasingPath(matrix):
if not matrix:
return 0
m, n = len(matrix), len(matrix[0])
memo = [[0]*n for _ in range(m)]
def dfs(i, j):
if memo[i][j] != 0:
return memo[i][j]
max_len = 1
for x, y in [(i-1,j),(i+1,j),(i,j-1),(i,j+1)]:
if 0<=x<m and 0<=y<n and matrix[x][y]>matrix[i][j]:
max_len = max(max_len, 1 + dfs(x, y))
memo[i][j] = max_len
return max_len
return max(dfs(i,j) for i in range(m) for j in range(n))
优化效果:
- 时间复杂度从O(2^(M+N))降到O(MN)
- 避免了重复计算相同子问题
11.2 分支定界法应用
在旅行商问题(TSP)中的实现:
python复制def tsp_branch_and_bound(graph):
n = len(graph)
min_cost = float('inf')
best_path = []
def backtrack(path, visited, current_cost):
nonlocal min_cost, best_path
if len(path) == n:
total = current_cost + graph[path[-1]][path[0]]
if total < min_cost:
min_cost = total
best_path = path.copy()
return
for next_node in range(n):
if not visited[next_node]:
new_cost = current_cost + graph[path[-1]][next_node]
# 关键剪枝:当前成本+最小剩余边和
lower_bound = new_cost + min_remaining_edges(next_node, visited)
if lower_bound >= min_cost:
continue
visited[next_node] = True
path.append(next_node)
backtrack(path, visited, new_cost)
path.pop()
visited[next_node] = False
def min_remaining_edges(node, visited):
# 计算从node出发未访问节点的最小出边和
min_sum = 0
for i in range(n):
if not visited[i]:
min_sum += min(graph[node][j] for j in range(n) if not visited[j] or j==i)
return min_sum
# 从每个节点出发尝试
for start in range(n):
visited = [False]*n
visited[start] = True
backtrack([start], visited, 0)
return best_path, min_cost
12. 多线程搜索实现
Python中使用concurrent.futures进行并行搜索:
python复制from concurrent.futures import ThreadPoolExecutor
def parallel_bfs(start, target):
visited = set([start])
current_level = [start]
depth = 0
with ThreadPoolExecutor() as executor:
while current_level:
next_level = []
futures = []
for node in current_level:
if node == target:
return depth
futures.append(
executor.submit(process_node, node, visited)
)
for future in concurrent.futures.as_completed(futures):
next_level.extend(future.result())
current_level = next_level
depth += 1
return -1
def process_node(node, visited):
neighbors = []
for neighbor in get_neighbors(node):
if neighbor not in visited:
visited.add(neighbor)
neighbors.append(neighbor)
return neighbors
注意事项:
- 需要线程安全的visited集合(使用Lock或线程安全数据结构)
- 适合邻居获取成本高的场景(如网络请求)
- 每层并行处理,层间同步
13. 测试用例设计方法论
有效的搜索算法测试应包含:
-
边界测试:
- 空输入(如空矩阵)
- 单元素输入
- 全连通/全不连通图
-
特殊结构测试:
- 链状结构(测试递归深度)
- 星型结构(测试中心节点处理)
- 完全图(测试密集连接)
-
性能测试:
- 大规模稀疏图(测试内存使用)
- 大规模稠密图(测试时间复杂度)
- 极端深度/广度结构
示例测试框架:
python复制import unittest
from collections import deque
class TestSearch(unittest.TestCase):
def test_bfs(self):
# 链状图 1-2-3-4
graph = {1:[2], 2:[1,3], 3:[2,4], 4:[3]}
self.assertEqual(bfs(graph, 1, 4), 3)
def test_dfs(self):
# 星型图 1-2, 1-3, 1-4
graph = {1:[2,3,4], 2:[1], 3:[1], 4:[1]}
self.assertTrue(dfs(graph, 1, 4))
def test_large_graph(self):
# 生成1000个节点的环
graph = {i:[(i-1)%1000, (i+1)%1000] for i in range(1000)}
start, end = 0, 999
self.assertEqual(bfs(graph, start, end), 1)
14. 算法选择决策树
根据问题特征选择合适搜索方法的流程图:
code复制是否要求最短路径?
├─ 是 → BFS/双向BFS/A*
└─ 否 → 是否需要记忆化?
├─ 是 → DFS+记忆化
└─ 否 → 状态空间大小?
├─ 小 → 朴素DFS
└─ 大 → 是否可分割?
├─ 是 → Meet-in-the-Middle
└─ 否 → 启发式搜索(A*/IDA*)
决策因素说明:
- 路径长度:BFS保证最短路径,DFS不保证
- 状态重复:存在重复子问题时用记忆化
- 空间大小:指数级空间考虑双向搜索或启发式
- 可分割性:问题能否分解为独立子问题
15. 经典竞赛题目精讲
15.1 八数码问题(POJ 1077)
python复制def solve_8_puzzle(start):
target = (1,2,3,4,5,6,7,8,0)
start = tuple(start)
if start == target:
return 0
from collections import deque
queue = deque([(start, 0)])
visited = set([start])
while queue:
state, steps = queue.popleft()
zero = state.index(0)
x, y = zero//3, zero%3
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
nx, ny = x+dx, y+dy
if 0<=nx<3 and 0<=ny<3:
new_zero = nx*3 + ny
new_state = list(state)
new_state[zero], new_state[new_zero] = new_state[new_zero], new_state[zero]
new_state = tuple(new_state)
if new_state == target:
return steps+1
if new_state not in visited:
visited.add(new_state)
queue.append((new_state, steps+1))
return -1
优化技巧:
- 使用元组存储状态,便于哈希去重
- 提前计算目标状态,直接比较
- 使用双端队列提升BFS效率
15.2 骑士最短路径(LeetCode 1197)
python复制def minKnightMoves(x, y):
# 双向BFS优化
x, y = abs(x), abs(y) # 利用对称性
if (x, y) == (0, 0):
return 0
moves = [(1,2),(2,1),(1,-2),(2,-1),(-1,2),(-2,1),(-1,-2),(-2,-1)]
front = {(0,0)}
back = {(x,y)}
visited = set()
steps = 0
while front and back:
if len(front) > len(back):
front, back = back, front
next_front = set()
steps += 1
for i, j in front:
for di, dj in moves:
ni, nj = i+di, j+dj
if (ni, nj) in back:
return steps
if (ni, nj) not in visited and -2<=ni<=x+2 and -2<=nj<=y+2:
visited.add((ni, nj))
next_front.add((ni, nj))
front = next_front
return -1
算法亮点:
- 利用对称性将问题限定在第一象限
- 双向BFS大幅减少搜索空间
- 限制搜索范围(-2到x+2, -2到y+2)
16. 搜索与动态规划的结合
16.1 记忆化搜索转DP
以最长递增路径为例:
python复制# 记忆化搜索版本
def longestIncreasingPath(matrix):
m, n = len(matrix), len(matrix[0])
memo = [[0]*n for _ in range(m)]
def dfs(i, j):
if memo[i][j] != 0:
return memo[i][j]
max_len = 1
for x, y in [(i-1,j),(i+1,j),(i,j-1),(i,j+1)]:
if 0<=x<m and 0<=y<n and matrix[x][y]>matrix[i][j]:
max_len = max(max_len, 1 + dfs(x, y))
memo[i][j] = max_len
return max_len
return max(dfs(i,j) for i in range(m) for j in range(n))
# 转DP版本
def longestIncreasingPathDP(matrix):
if not matrix:
return 0
m, n = len(matrix), len(matrix[0])
cells = sorted(
[(i, j) for i in range(m) for j in range(n)],
key=lambda x: matrix[x[0]][x[1]]
)
dp = [[1]*n for _ in range(m)]
for i, j in cells:
for x, y in [(i-1,j),(i+1,j),(i,j-1),(i,j+1)]:
if 0<=x<m and 0<=y<n and matrix[x][y]>matrix[i][j]:
dp[x][y] = max(dp[x][y], dp[i][j]+1)
return max(max(row) for row in dp)
转换要点:
- 按节点值排序,保证计算顺序
- 状态转移方程与DFS逻辑一致
- 时间复杂度均为O(MN),但DP常数更小
17. 启发式函数设计进阶
17.1 可采纳性与一致性
好的启发式函数需要满足:
- 可采纳性:h(n) ≤ h*(n),不高估实际成本
- 一致性:h(n) ≤ c(n,n') + h(n'),满足三角不等式
以十五数码问题为例:
python复制def heuristic_15_puzzle(state):
# 曼哈顿距离和
total = 0
for i in range(4):
for j in range(4):
val = state[i][j]
if val != 0:
target_i = (val-1) // 4
target_j = (val-1) % 4
total += abs(i - target_i) + abs(j - target_j)
return total
def linear_conflict(state):
# 线性冲突启发式
manhattan = heuristic_15_puzzle(state)
conflict = 0
# 行冲突
for i in range(4):
row = [state[i][j] for j in range(4) if state[i][j] != 0]
for j in range(len(row)):
for k in range(j+1, len(row)):
a, b = row[j], row[k]
ai, aj = (a-1)//4, (a-1)%4
bi, bj = (b-1)//4, (b-1)%4
if ai == i == bi and aj > bj:
conflict += 2
# 列冲突同理
return manhattan + conflict
效果对比:
- 纯曼哈顿距离:可采纳但不精确
- 线性冲突:更接近真实成本,搜索节点数减少30-50%
18. 非传统搜索问题转化
18.1 将数学问题转化为搜索
以完美平方数(LeetCode 279)为例:
python复制def numSquares(n):
# 转化为BFS问题:找到从0到n的最短路径
from collections import deque
squares = [i*i for i in range(1, int(n**0.5)+1)]
queue = deque([(0, 0)])
visited = set()
while queue:
num, steps = queue.popleft()
for s in squares:
next_num = num + s
if next_num == n:
return steps + 1
if next_num < n and next_num not in visited:
visited.add(next_num)
queue.append((next_num, steps+1))
return 0
转化思路:
- 将每个数字视为节点
- 平方数作为边权重
- 问题转化为最短路径问题
18.2 将字符串处理转化为搜索
以单词拆分(LeetCode 139)为例:
python复制def wordBreak(s, wordDict):
# BFS解法
from collections import deque
word_set = set(wordDict)
visited = set()
queue = deque([0])
while queue:
start = queue.popleft()
if start == len(s):
return True
for end in range(start+1, len(s)+1):
if end not in visited and s[start:end] in word_set:
if end == len(s):
return True
queue.append(end)
visited.add(end)
return False
性能对比:
| 方法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 朴素DFS | O(2^N) | O(N) |
| 记忆化DFS | O(N^3) | O(N) |
| BFS | O(N^3) | O(N) |
| DP | O(N^3) | O(N) |
19. 搜索空间分析技巧
19.1 状态空间估算
对于排列类问题,状态数为N!;对于组合类问题,状态数为2^N。实际需要考虑有效状态:
- 八皇后问题:有效状态约4.4×10^6(远小于8^8=1.7×10^7)
- 数独问题:有效状态约6.7×10^21(远小于9^81)
估算公式:
code复制有效状态 = 总状态 × 约束条件过滤比例
19.2 分支因子计算
平均分支因子影响搜索效率:
- 国际象棋:约35
- 围棋:约250
- 魔方:约13
- 八数码:约2-4
计算方法:
python复制def average_branching_factor(state_space):
total_branches = 0
total_states = len(state_space)
for state in state_space:
total_branches += len(get_neighbors(state))
return total_branches / total_states
20. 现代搜索算法前沿
20.1 蒙特卡洛树搜索(MCTS)
适用于AlphaGo等游戏AI:
python复制class Node:
def __init__(self, state, parent=None):
self.state = state
self.parent = parent
self.children = []
self.wins = 0
self.visits = 0
def uct_select_child(self):
log_parent_visits = math.log(self.visits)
return max(self.children,
key=lambda child: child.wins/child.visits
+ math.sqrt(2*log_parent_visits/child.visits))
def expand(self):
for action in self.state.get_legal_actions():
new_state = self.state.move(action)
self.children.append(Node(new_state, self))
def simulate(self):
current_state = self.state.copy()
while not current_state.is_terminal():
current_state = current_state.random_move()
return current_state.get_result()
def mcts(root_state, iterations):
root = Node(root_state)
for _ in range(iterations):
node = root
# Selection
while node.children:
node = node.uct_select_child()
# Expansion
if not node.state.is_terminal():
node.expand()
if node.children:
node = random.choice(node.children)
# Simulation
result = node.simulate()
# Backpropagation
while node:
node.visits += 1
node.wins += result
node = node.parent
return max(root.children, key=lambda c: c.visits).state.last_move
20.2 遗传算法示例
python复制
