实验3 知识表示与推理
实验3 知识表示与推理
一、实验目的
(1)掌握知识和知识表示的基本概念,理解其在AI中的深刻含义与意义;
(2)熟悉AI中常用的知识表示方法的优缺点及其应用场景;
(3)掌握产生式系统知识表示的方法及其构成要素;
(4)掌握状态空间法的基本构成及其特点,能用其表示实际AI问题;
(5)深入理解图的深度、宽度搜索方法,并能应用于实际问题;
(6)根据自身情况,能选择合适的编程语言,解决实际AI问题。
二、实验内容
借助产生式系统和状态空间法,选择一种编程语言(最好为python或java),完成题目要求。
1、八数码难题
在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格可用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局,找到一种移动方法,实现从初始布局到目标布局的转变。
1.需求分析
在一个3×3的棋盘格中,摆有1-8数字的八个棋子,剩余的空格用0表示。给出一种初始布局和目标布局,找到一种移动方法,实现从初始布局到目标布局的转变,规则是移动空格,并且空格不能移出棋盘外。
2.数据结构、功能模块设计与说明
采用广度优先搜索算法,从初始状态开始,每次进行可行操作(与0所在位置相邻数字交换),得到新的状态,并将其加入队列中,直到找到目标状态为止。
在搜索之前需要判断一下目标状态是否可达,根据八数码问题的特性,合法的移动操作只涉及两个数字的交换,空格左右移动不会改变任何两对数字之间的逆序对数量,因为整个序列的相对顺序保持不变。空格上下移动会改变两对数字之间的逆序对数量。当初始状态的空白格和目标状态的空白格在不同行时,只有通过上下移动才有可能改变逆序对的数量,从而实现初始状态到目标状态的转换。故当初始状态和结果状态逆序数奇偶性相同的时候才可达,否则不进行搜索。
当目标状态可达的时候,又因为有很多状态会重复出现,所以判断移动之后的状态是否出现过?这里用哈希表来去重,如果出现过则丢弃;否则,将它加入队列,并将它对应的步数设为前一个状态的步数+1,直到找到目标状态为止。
3.核心代码与测试结果说明
# 在 A* 算法 中的一个节点。代表了搜索空间中的一个状态
class Node:
def __init__(self, state, parent=None, move=0, depth=0):
self.state = state
self.parent = parent
self.move = move
self.depth = depth
self.cost = 0 # g(n) + h(n)
def __lt__(self, other):
return self.cost < other.cost
# A* 算法
# 结合实际成本(g(n))和估计成本(h(n))来选择最有希望的路径
# g(n) 是从起始节点到当前节点的移动次数(或称为深度),
# 而 h(n) 是当前状态与目标状态之间的曼哈顿距离
def a_star(initial, goal):
open_list = []
closed_set = set()
root = Node(initial)
heapq.heappush(open_list, (manhattan_distance(initial, goal), root))
while open_list:
_, current = heapq.heappop(open_list)
closed_set.add(tuple(current.state))
if current.state == goal:
path = []
while current:
path.append(current.state)
current = current.parent
return path[::-1]
zero_pos = current.state.index(0)
x, y = divmod(zero_pos, 3)
moves = [(-1, 0), (1, 0), (0, -1), (0, 1)]
for dx, dy in moves:
new_x, new_y = x + dx, y + dy
if 0 <= new_x < 3 and 0 <= new_y < 3:
new_state = current.state[:]
new_pos = new_x * 3 + new_y
new_state[zero_pos], new_state[new_pos] = new_state[new_pos], new_state[zero_pos]
if tuple(new_state) not in closed_set:
new_node = Node(new_state, current, move=current.move + 1, depth=current.depth + 1)
new_node.cost = new_node.depth + manhattan_distance(new_state, goal)
heapq.heappush(open_list, (new_node.cost, new_node))
return None
结果:
4.存在的问题与体会
4.1 存在的问题
这种解法空间复杂度较高。使用广度优先搜索算法时,需要存储所有的状态和路径信息。通过哈希表来存储状态路径信息,可能会占用较大内存空间,特别是当搜索空间非常庞大时。所以可以考虑使用其他数据结构或优化算法,以减少空间复杂度。
4.2 体会
虽然代码存在一些问题和可以改进的地方,但我深入理解了广度优先搜索算法,并在实践中获得了关于数据结构和代码设计的经验。
2、设有3个传教士和3个野人来到河边,打算乘一只船从右岸渡到左岸去。该船每一次只能装载2人。在任何时候,如果野人人数超过传教士人数,那么野人就会把传教士吃掉。请你设计一个方案怎样才能用这条船安全地把所有人都渡过河去?编程实现你的方案。
1.需求分析
有3个传教士和3个野人从河右岸乘一只船到左岸,且该船每次只能装载2人。必须保证在任何时候,野人人数不能超过传教士人数,否则野人就会把传教士吃掉。设计一个方案怎样才能用这条船安全地把所有人都渡过河去?并且推广到有n个传教士和n个野人,河边的船每次至多可供k个人渡河的解决方案。
2.数据结构、功能模块设计与说明
使用深度优先搜索算法,寻找所有可能的移动方案。其中,每个状态包括左岸传教士和野人的数量,以及船的位置(左岸或右岸)。通过不断尝试不同的移动方案,最终找到一种能够使所有人都安全到达右岸的方法。
3.核心代码:
# 检查一组传教士和野人的数量是否符合规则
def is_valid_state(left_missionaries, left_cannibals):
if left_missionaries < 0 or left_cannibals < 0:
return False
if (3 - left_missionaries) < 0 or (3 - left_cannibals) < 0:
return False
if (left_missionaries > 0 and left_cannibals > left_missionaries) or ((3 - left_missionaries) > 0 and (3 - left_cannibals) > (3 - left_missionaries)):
return False
return True
# 接收一个状态state作为输入,并返回可能的下一个状态列表
def next_states(state):
left_missionaries, left_cannibals, right_missionaries, right_cannibals, boat_position = state
next_states_list = []
if boat_position == 'left':
for i in range(3):
for j in range(3):
if i + j > 0 and i + j <= 2:
new_left_missionaries = left_missionaries - i
new_left_cannibals = left_cannibals - j
new_right_missionaries = right_missionaries + i
new_right_cannibals = right_cannibals + j
if is_valid_state(new_left_missionaries, new_left_cannibals):
def bfs():
initial_state = (3, 3, 0, 0, 'left')
goal_state = (0, 0, 3, 3, 'right')
visited = set()
queue = deque([(initial_state, [])])
while queue:
state, path = queue.popleft()
if state == goal_state:
return path + [state]
if state not in visited:
visited.add(state)
for next_state in next_states(state):
queue.append((next_state, path + [state]))
return None
结果: