目录
[3.1 基础深度优先搜索(DFS)算法](#3.1 基础深度优先搜索(DFS)算法)
[3.2 并查集(Union-Find)算法](#3.2 并查集(Union-Find)算法)
[3.3 增量更新算法](#3.3 增量更新算法)
[3.4 位运算优化算法](#3.4 位运算优化算法)
[3.5 性能测试与比较](#3.5 性能测试与比较)
[3.6 算法选择建议](#3.6 算法选择建议)
在围棋程序开发中,气(liberties)的计算是最核心、最基础的算法。准确高效地计算气不仅关系到提子、自杀等基本规则的实现,更是后续AI算法和胜负判定的基础。本文将深入探讨围棋气计算的各种算法设计,从基础DFS到高级优化技术,帮助您构建高性能的围棋引擎。
步骤3:设计气(Liberties)的计算算法
3.1 基础深度优先搜索(DFS)算法
在步骤2中我们已经介绍了基础的DFS算法,现在让我们深入优化这个算法:
class GoBoard:
def __init__(self, size=19):
self.size = size
self.board = [[None for _ in range(size)] for _ in range(size)]
self.union_find = None # 并查集数据结构
self.liberty_cache = {} # 气数缓存
self.group_cache = {} # 连通块缓存
self._init_advanced_structures()
def _init_advanced_structures(self):
"""初始化高级数据结构"""
# 并查集初始化
self.union_find = UnionFind(self.size * self.size)
# 清空缓存
self.liberty_cache.clear()
self.group_cache.clear()
def get_liberties_dfs(self, row, col, visited=None):
"""优化的DFS气计算算法"""
if self.board[row][col] is None:
return set()
# 检查缓存
cache_key = (row, col)
if cache_key in self.liberty_cache:
return self.liberty_cache[cache_key].copy()
if visited is None:
visited = set()
stone_color = self.board[row][col]
point = (row, col)
if point in visited:
return set()
visited.add(point)
liberties = set()
# 使用迭代DFS避免递归深度限制
stack = [point]
visited_stack = set([point])
while stack:
current_row, current_col = stack.pop()
# 检查四个方向
for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
new_row, new_col = current_row + dr, current_col + dc
if 0 <= new_row < self.size and 0 <= new_col < self.size:
new_point = (new_row, new_col)
if new_point in visited_stack:
continue
if self.board[new_row][new_col] is None:
# 空点,计入气
liberties.add(new_point)
elif self.board[new_row][new_col] == stone_color:
# 同色棋子,加入栈继续搜索
stack.append(new_point)
visited_stack.add(new_point)
# 更新缓存
for stone_point in visited_stack:
self.liberty_cache[stone_point] = liberties.copy()
return liberties
3.2 并查集(Union-Find)算法
并查集是解决连通性问题的高效数据结构,特别适合围棋的连通块计算:
class UnionFind:
"""并查集数据结构实现"""
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n
self.liberty_count = [0] * n # 每个连通块的气数
self.stone_count = [1] * n # 每个连通块的棋子数
def find(self, x):
"""查找根节点,带路径压缩"""
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
"""合并两个集合,按秩合并"""
root_x = self.find(x)
root_y = self.find(y)
if root_x == root_y:
return root_x
if self.rank[root_x] < self.rank[root_y]:
root_x, root_y = root_y, root_x
self.parent[root_y] = root_x
self.stone_count[root_x] += self.stone_count[root_y]
self.liberty_count[root_x] += self.liberty_count[root_y]
if self.rank[root_x] == self.rank[root_y]:
self.rank[root_x] += 1
return root_x
class GoBoardWithUnionFind:
"""使用并查集的气计算实现"""
def __init__(self, size=19):
self.size = size
self.board = [[None for _ in range(size)] for _ in range(size)]
self.uf = UnionFind(size * size)
self._init_union_find_liberties()
def _point_to_index(self, row, col):
"""将坐标转换为并查集索引"""
return row * self.size + col
def _index_to_point(self, index):
"""将并查集索引转换为坐标"""
return index // self.size, index % self.size
def _init_union_find_liberties(self):
"""初始化并查集的气数"""
# 初始化所有点的气数为相邻空点数
for i in range(self.size):
for j in range(self.size):
idx = self._point_to_index(i, j)
self.uf.liberty_count[idx] = self._count_adjacent_empty(i, j)
def _count_adjacent_empty(self, row, col):
"""计算相邻空点数"""
count = 0
for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
new_row, new_col = row + dr, col + dc
if 0 <= new_row < self.size and 0 <= new_col < self.size:
if self.board[new_row][new_col] is None:
count += 1
return count
def get_liberties_union_find(self, row, col):
"""使用并查集获取气数"""
if self.board[row][col] is None:
return 0
root = self.uf.find(self._point_to_index(row, col))
return self.uf.liberty_count[root]
def update_liberties_after_move(self, row, col, player):
"""落子后更新气数"""
placed_index = self._point_to_index(row, col)
# 减少相邻空点的气数
for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
new_row, new_col = row + dr, col + dc
if 0 <= new_row < self.size and 0 <= new_col < self.size:
adj_index = self._point_to_index(new_row, new_col)
if self.board[new_row][new_col] is None:
# 空点气数减少
root = self.uf.find(adj_index)
self.uf.liberty_count[root] -= 1
elif self.board[new_row][new_col] == player:
# 合并同色连通块
self.uf.union(placed_index, adj_index)
3.3 增量更新算法
为了优化性能,我们可以实现增量更新的气计算算法:
class IncrementalLibertyCalculator:
"""增量气计算器"""
def __init__(self, board_size):
self.size = board_size
self.liberty_map = [[set() for _ in range(board_size)] for _ in range(board_size)]
self.group_map = {} # 连通块映射: group_id -> set(points)
self.next_group_id = 0
def initialize_from_board(self, board):
"""从棋盘状态初始化气计算"""
# 清空现有数据
self.liberty_map = [[set() for _ in range(self.size)] for _ in range(self.size)]
self.group_map.clear()
self.next_group_id = 0
# 遍历棋盘,建立连通块
visited = set()
for i in range(self.size):
for j in range(self.size):
if board[i][j] is not None and (i, j) not in visited:
self._flood_fill_group(i, j, board, visited)
# 计算每个点的气
self._calculate_all_liberties(board)
def _flood_fill_group(self, start_row, start_col, board, visited):
"""洪水填充法建立连通块"""
if (start_row, start_col) in visited:
return
color = board[start_row][start_col]
if color is None:
return
group_id = self.next_group_id
self.next_group_id += 1
self.group_map[group_id] = set()
stack = [(start_row, start_col)]
visited.add((start_row, start_col))
while stack:
row, col = stack.pop()
self.group_map[group_id].add((row, col))
# 检查四个方向
for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
new_row, new_col = row + dr, col + dc
if (0 <= new_row < self.size and 0 <= new_col < self.size and
(new_row, new_col) not in visited and
board[new_row][new_col] == color):
stack.append((new_row, new_col))
visited.add((new_row, new_col))
def _calculate_all_liberties(self, board):
"""计算所有点的气"""
for group_id, points in self.group_map.items():
group_liberties = set()
# 计算整个连通块的气
for row, col in points:
for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
new_row, new_col = row + dr, col + dc
if (0 <= new_row < self.size and 0 <= new_col < self.size and
board[new_row][new_col] is None):
group_liberties.add((new_row, new_col))
# 为连通块内每个点分配相同的气集合
for point in points:
row, col = point
self.liberty_map[row][col] = group_liberties.copy()
def update_after_move(self, move_row, move_col, player, board):
"""落子后增量更新气计算"""
# 更新落子点的气
self.liberty_map[move_row][move_col] = self._calculate_point_liberties(move_row, move_col, board)
# 更新相邻点的气
affected_points = set()
for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
new_row, new_col = move_row + dr, move_col + dc
if 0 <= new_row < self.size and 0 <= new_col < self.size:
affected_points.add((new_row, new_col))
# 重新计算受影响点的气
for row, col in affected_points:
if board[row][col] is not None:
self.liberty_map[row][col] = self._calculate_point_liberties(row, col, board)
def _calculate_point_liberties(self, row, col, board):
"""计算单个点的气"""
if board[row][col] is None:
return set()
liberties = set()
color = board[row][col]
visited = set()
stack = [(row, col)]
visited.add((row, col))
while stack:
current_row, current_col = stack.pop()
for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
new_row, new_col = current_row + dr, current_col + dc
if (0 <= new_row < self.size and 0 <= new_col < self.size and
(new_row, new_col) not in visited):
if board[new_row][new_col] is None:
liberties.add((new_row, new_col))
elif board[new_row][new_col] == color:
stack.append((new_row, new_col))
visited.add((new_row, new_col))
return liberties
3.4 位运算优化算法
对于性能要求极高的场景,可以使用位运算进行优化:
class BitwiseLibertyCalculator:
"""位运算气计算器(适用于小棋盘)"""
def __init__(self, board_size):
if board_size > 32:
raise ValueError("位运算优化只适用于32路以下棋盘")
self.size = board_size
self.black_mask = 0 # 黑棋位掩码
self.white_mask = 0 # 白棋位掩码
self.liberty_masks = [0] * (board_size * board_size) # 每个点的气掩码
def _point_to_bit(self, row, col):
"""坐标转位索引"""
return row * self.size + col
def _bit_to_point(self, bit):
"""位索引转坐标"""
return bit // self.size, bit % self.size
def _get_adjacent_bits(self, bit):
"""获取相邻点的位索引"""
row, col = self._bit_to_point(bit)
adjacent_bits = []
for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
new_row, new_col = row + dr, col + dc
if 0 <= new_row < self.size and 0 <= new_col < self.size:
adjacent_bits.append(self._point_to_bit(new_row, new_col))
return adjacent_bits
def initialize_liberty_masks(self):
"""初始化气掩码"""
total_points = self.size * self.size
full_mask = (1 << total_points) - 1
for i in range(total_points):
liberty_mask = 0
for adj_bit in self._get_adjacent_bits(i):
liberty_mask |= (1 << adj_bit)
self.liberty_masks[i] = liberty_mask
def get_liberties_bitwise(self, row, col):
"""使用位运算获取气数"""
if self.size > 32:
return self.get_liberties_dfs(row, col) # 回退到DFS
point_bit = self._point_to_bit(row, col)
color_mask = self.black_mask if self.board[row][col] == 'black' else self.white_mask
# 获取连通块
group_mask = self._flood_fill_bitwise(point_bit, color_mask)
# 计算气:相邻空点且不在对方棋子位置
adjacent_mask = 0
current_bit = group_mask
while current_bit:
bit_pos = current_bit & -current_bit # 获取最低位1
bit_index = (bit_pos - 1).bit_length() - 1
adjacent_mask |= self.liberty_masks[bit_index]
current_bit &= current_bit - 1 # 清除最低位1
# 气 = 相邻点且为空点
empty_mask = full_mask & ~(self.black_mask | self.white_mask)
liberties_mask = adjacent_mask & empty_mask
return bin(liberties_mask).count('1') # 统计1的个数即为气数
def _flood_fill_bitwise(self, start_bit, color_mask):
"""位运算洪水填充"""
group_mask = 1 << start_bit
frontier = group_mask
while frontier:
new_frontier = 0
current_bit = frontier
while current_bit:
bit_pos = current_bit & -current_bit
bit_index = (bit_pos - 1).bit_length() - 1
# 获取相邻同色棋子
adjacent_mask = self.liberty_masks[bit_index] & color_mask
new_bits = adjacent_mask & ~group_mask
new_frontier |= new_bits
group_mask |= new_bits
current_bit &= current_bit - 1
frontier = new_frontier
return group_mask
3.5 性能测试与比较
import time
import random
def performance_test():
"""性能测试比较各种算法"""
size = 9
test_board = GoBoard(size)
# 随机生成测试局面
for _ in range(20):
row = random.randint(0, size-1)
col = random.randint(0, size-1)
color = 'black' if random.random() > 0.5 else 'white'
test_board.board[row][col] = color
algorithms = [
("基础DFS", test_board.get_liberties_dfs),
("并查集", test_board.get_liberties_union_find),
]
# 测试点
test_points = [(4, 4), (2, 2), (6, 6), (0, 0), (8, 8)]
print("气计算算法性能测试结果:")
print("=" * 50)
for algo_name, algo_func in algorithms:
total_time = 0
valid_tests = 0
for row, col in test_points:
if test_board.board[row][col] is not None:
start_time = time.time()
liberties = algo_func(row, col)
end_time = time.time()
execution_time = (end_time - start_time) * 1000 # 毫秒
total_time += execution_time
valid_tests += 1
print(f"{algo_name}: ({row},{col}) -> {len(liberties)}气, 耗时: {execution_time:.3f}ms")
if valid_tests > 0:
avg_time = total_time / valid_tests
print(f"{algo_name}平均耗时: {avg_time:.3f}ms")
print("-" * 30)
def accuracy_test():
"""准确性测试"""
size = 5
test_board = GoBoard(size)
# 创建测试局面
# 黑棋: (0,0), (0,1), (1,0)
# 白棋: (1,1)
test_board.board[0][0] = 'black'
test_board.board[0][1] = 'black'
test_board.board[1][0] = 'black'
test_board.board[1][1] = 'white'
print("准确性测试:")
print("黑棋(0,0)的气:", len(test_board.get_liberties_dfs(0, 0)))
print("白棋(1,1)的气:", len(test_board.get_liberties_dfs(1, 1)))
# 预期结果:黑棋连通块有3气,白棋有1气
if __name__ == "__main__":
performance_test()
accuracy_test()
3.6 算法选择建议
根据不同的应用场景,推荐使用不同的气计算算法:
- 教学/演示用途:基础DFS算法,代码简单易懂
- 中等规模棋盘(9-19路):增量更新算法,平衡性能与复杂度
- 高性能需求:并查集算法,适合AI搜索
- 极小棋盘(9路以下):位运算算法,极致性能
技术要点总结
-
算法复杂度:DFS为O(n),并查集接近O(1),位运算为O(1)但内存消耗大
-
缓存策略:合理使用缓存可以大幅提升性能
-
增量更新:只更新受影响的部分,避免全盘重新计算
-
数据结构选择:根据棋盘大小和性能要求选择合适的数据结构
常见优化技巧
-
懒更新:只在需要时计算气数,避免不必要的计算
-
分组计算:按连通块分组,减少重复计算
-
边界处理:优化棋盘边角的特殊处理
-
内存优化:使用更紧凑的数据表示方法

下一步计划
完成气计算算法的优化后,下一步将实现:
- 步骤4:完善提子逻辑和劫争规则
- 步骤5:开发胜负判定和计分系统
- 步骤6:实现基础AI算法
结语
气计算是围棋程序的核心算法,其性能直接影响整个程序的效率。本文详细介绍了从基础DFS到高级位运算的各种气计算算法,并提供了性能测试和选择建议。通过合理选择算法和优化策略,可以构建出高性能的围棋引擎。
参考文献
- 《算法导论》- Thomas H. Cormen
- 并查集在围棋中的应用研究
- 位运算优化技巧
- 增量计算算法设计模式
通过本指南,您已经掌握了围棋气计算的各种高级算法技术。在接下来的文章中,我们将继续探讨更复杂的围棋规则和AI算法实现。