人工智能(第三版)—【第二章】练习题

1.在只允许称重 3 次的情况下,求解 12 枚硬币的假币问题。回忆一下,天平可以返回以下 3 种结果之一:相等、左侧轻或左侧重。

在只允许称重3次的情况下,求解12枚硬币的假币问题,可以采用以下步骤:

  1. 将12枚硬币分成3组,每组4枚硬币。
  2. 将第一组和第二组放在天平的两端进行称重:
    • 如果两组重量相等,说明假币在第三组中,进入步骤3。
    • 如果两组重量不相等,说明假币在较轻的一组中,进入步骤4。
  3. 将第三组中的4枚硬币分成两组,每组2枚硬币。将其中一组放在天平的两端进行称重:
    • 如果两组重量相等,说明假币是剩下的两枚硬币中的一枚,进入步骤5。
    • 如果两组重量不相等,说明假币在较轻的一组中,进入步骤6。
  4. 在较轻的一组中选择任意两枚硬币放在天平的两端进行称重:
    • 如果两枚硬币重量相等,说明另外两枚硬币中有一枚是假币,而且是较重的假币,进入步骤7。
    • 如果两枚硬币重量不相等,说明较轻的一枚硬币就是假币,进入步骤7。
  5. 在剩下的两枚硬币中选择任意一枚硬币放在天平的任意一端进行称重:
    • 如果称重结果是相等,那么剩下的一枚硬币就是假币,而且是较重的假币,进入步骤7。
    • 如果称重结果是左侧重或左侧轻,那么放在天平左端的那枚硬币就是假币,而且是较重的假币,进入步骤7。
  6. 在较轻的一组中选择任意两枚硬币放在天平的两端进行称重:
    • 如果两枚硬币重量相等,说明另外两枚硬币中有一枚是假币,而且是较重的假币,进入步骤7。
    • 如果两枚硬币重量不相等,说明较轻的一枚硬币就是假币,进入步骤7。
  7. 经过以上步骤,可以确定假币是较重的一枚。
    通过以上步骤,可以在只允许称重3次的情况下,找到12枚硬币中的假币,并确定其是较重的假币。

2.在只称重两次的情况下,求解微型假币问题或证明这是不可能的。

在只允许称重两次的情况下,解决12枚硬币的微型假币问题是不可能的。这是因为在两次称重的限制下,无法得到足够的信息来确定哪枚硬币是假币以及其是较轻还是较重。

假设我们将12枚硬币分成两组,每组6枚硬币。在第一次称重时,我们将其中一组放在天平的一侧,另一组放在另一侧。根据称重结果,有以下几种情况:

  1. 如果两组重量相等,那么假币不在这两组中,它可能是剩下的未称重的6枚硬币中的一枚。但是,由于我们只剩下一次称重的机会,无法判断哪一枚是假币以及它的重量。
  2. 如果两组重量不相等,我们无法确定哪组中的硬币是假币,也无法确定它是较轻还是较重的。在剩下的一次称重中,我们可以将其中一组的三枚硬币放在天平的一侧,另一组的三枚硬币放在另一侧。但是,无论称重结果如何,我们都无法确定哪一枚硬币是假币以及它的重量。

因此,在只允许称重两次的情况下,解决12枚硬币的微型假币问题是不可能的,因为无法得到足够的信息来确定假币和它的重量。

3.非确定性搜索(nondeterministic search)是本章未讨论的一种盲目搜索方法。在这种搜索中,刚刚扩展的子节点以随机顺序放在开放表中。请判断非确定性搜索是否完备以及是否最优。

非确定性搜索(nondeterministic search)是一种盲目搜索方法,其中刚刚扩展的子节点以随机顺序放在开放表中。在非确定性搜索中,节点的扩展顺序是随机的,不受特定规则或优先级的限制。

判断非确定性搜索是否完备和最优需要考虑以下两个方面:

  1. 完备性:非确定性搜索不是完备的。完备性意味着在有解的情况下,搜索算法能够找到解。由于非确定性搜索的节点扩展顺序是随机的,它可能会错过某些路径或陷入无限循环,导致无法找到解。因此,非确定性搜索不能保证在有解的情况下找到解,所以它不是完备的。
  2. 最优性:非确定性搜索也不是最优的。最优性意味着搜索算法能够找到最优解,即找到代价最小或评估函数最优的解。由于非确定性搜索的节点扩展顺序是随机的,它无法保证在有限的时间内找到最优解。即使找到了一个解,也不能保证它是最优解。因此,非确定性搜索不是最优的。

综上所述,非确定性搜索不是完备的,也不是最优的。它的随机性质导致在搜索过程中无法保证找到解或找到最优解。在实际应用中,非确定性搜索可能用于一些特定的问题领域,但需要注意其局限性和无法保证的性质。

4.n 皇后问题的另一个生成器如下:第一个皇后放在第一行,第二个皇后不放在受第一个皇后攻击的任何方格中。在状态 i,将第 i 列的皇后放在未受前面(i−1)个皇后攻击的方格中,如图 2.34 所示。

(a)使用这个生成器求解 4 皇后问题。
(b)证明这个生成器比文中使用的两个生成器拥有更多的信息。
(c)画出搜索第一个解时在搜索树中展开的部分。

人工智能(第三版)—【第二章】练习题_第1张图片

(a) 使用这个生成器求解4皇后问题的步骤如下:

  1. 第一个皇后放在第一行的第一列。
    Q - - -
    - - - -
    - - - -
    - - - -
    
  2. 第二个皇后放在第二行的第三列,因为第一列已经被第一个皇后占据。
    Q - - -
    - - Q -
    - - - -
    - - - -
    
  3. 第三个皇后放在第三行的第一列,因为第一列和第二列都没有被前两个皇后占据。
    Q - - -
    - - Q -
    Q - - -
    - - - -
    
  4. 第四个皇后放在第四行的第四列,因为第一列、第二列和第三列都没有被前三个皇后占据。
    Q - - -
    - - Q -
    Q - - -
    - - - Q
    

因此,使用这个生成器可以求解4皇后问题,得到一个解为:

Q - - -
- - Q -
Q - - -
- - - Q

(b) 要证明这个生成器比文中使用的两个生成器拥有更多的信息,需要比较生成的搜索空间大小。这个生成器在每一步都要确保新放置的皇后不会受到前面已放置的皇后的攻击,因此生成的搜索空间比文中的两个生成器更小。这是因为在每一步中,新放置的皇后的选择受到更多的限制,只能放在未受前面皇后攻击的方格中,而不是所有未被占据的方格。因此,这个生成器拥有更多的信息,可以更快地收敛到解。
© 以下是搜索第一个解时在搜索树中展开的部分:

Level 1:  Q - - -
Level 2:  Q - - -
          Q - - -
          Q - - -
Level 3:  Q - - -
          Q - - -
          Q - - -
          Q - - -
Level 4:  Q - - -
          Q - - -
          Q - - -
          Q - - Q

在搜索树中,每一层代表一行,每一列代表一个状态,即放置皇后的位置。从根节点开始,逐层展开,直到找到第一个解。在这个例子中,搜索树只展开了一部分,因为我们只需要找到第一个解。

5.思考下列 4 皇后问题的生成器:从 i=1 到 i=4,随机地分配皇后 i 到某一行。这个生成器完备吗?非冗余吗?解释你的答案。

这个随机分配皇后的生成器在每一步都随机选择一个行来放置皇后,从 i=1 到 i=4。下面对其完备性和非冗余性进行解释:

  1. 完备性:这个生成器是完备的。完备性意味着在有解的情况下,搜索算法能够找到解。虽然这个生成器是随机的,但是在每一步中都会随机选择一个行来放置皇后,因此它会尝试所有可能的放置方式。即使在搜索的早期阶段可能会错过一些解,但是在足够长的时间内,它仍然有机会找到解。因此,这个生成器是完备的。
  2. 非冗余性:这个生成器是非冗余的。非冗余性意味着搜索算法不会生成重复的解。由于这个生成器是随机的,每次生成的解都是随机的,因此不会生成重复的解。即使在相同的初始状态下多次运行生成器,也会得到不同的解。因此,这个生成器是非冗余的。

需要注意的是,尽管这个生成器是完备的和非冗余的,但是由于其随机性质,它可能需要更长的时间才能找到解,尤其是在问题规模较大时。此外,由于随机选择行的方式,生成的解可能没有特定的模式或规律,可能会导致解的质量不一致。

6.如果一个数等于其因数(不包括这个数本身)的和,则称这个数是完美数。例如,6 是完美数,因为 6 = 1 + 2 + 3,其中整数 1、2 和 3 都是 6 的因数。给出你所能想到的拥有最多信息的生成器,使用这个生成器,可以找到 1 和 100 之间(包括 1 和 100 在内)的所有完美数。

要找到1和100之间的所有完美数,可以使用以下生成器:

  1. 对于每个数n从1到100,进行迭代。
  2. 对于每个n,计算n的因数之和。
  3. 如果n的因数之和等于n本身,则n是一个完美数。
    这个生成器拥有最多信息,因为它直接根据完美数的定义来生成解。它遍历了1到100之间的每个数,计算其因数之和,并检查是否等于自身。这样可以保证找到所有满足条件的完美数。

以下是使用这个生成器找到1和100之间的所有完美数的步骤:

  1. 对于n=6,计算其因数之和:1 + 2 + 3 = 6。因为因数之和等于n本身,所以6是一个完美数。
  2. 对于n=28,计算其因数之和:1 + 2 + 4 + 7 + 14 = 28。因为因数之和等于n本身,所以28是一个完美数。

因此,1和100之间的所有完美数是6和28。

7.使用 Dijkstra 算法找到从源顶点 V0到所有其他顶点的最短路径,如图 2.35 所示。

人工智能(第三版)—【第二章】练习题_第2张图片

Dijkstra算法是一种用于解决单源最短路径问题的算法,它可以找到从源顶点到所有其他顶点的最短路径。
以下是使用Dijkstra算法找到从源顶点V0到所有其他顶点的最短路径的步骤:

  1. 初始化距离数组dist[],将源顶点V0的距离设置为0,其他顶点的距离设置为无穷大。
  2. 创建一个空的优先队列Q,并将所有顶点加入队列。
  3. 重复以下步骤,直到队列Q为空:
    • 从队列Q中选择距离最小的顶点u,并将其从队列中移除。
    • 对于顶点u的所有相邻顶点v,计算从源顶点V0到顶点v的距离。如果通过顶点u到顶点v的路径比当前已知的最短路径更短,则更新顶点v的距离。
  4. 完成后,dist[]数组中存储的就是从源顶点V0到所有其他顶点的最短路径。

8.创建拼图(如 15 拼图)的表示以适合检查重复状态。

为了创建适合检查重复状态的拼图表示,可以使用一个二维数组或字符串来表示拼图的状态。以下是一个示例,展示如何创建一个适合检查重复状态的15拼图的表示:

  1. 使用二维数组表示:可以使用一个3x3的二维数组来表示15拼图的状态。其中,每个数组元素代表一个拼图块的值,0表示空白块。例如,以下是一个15拼图的二维数组表示:

    [[1, 2, 3],
     [4, 5, 6],
     [7, 8, 0]]
    

    这个表示方式可以方便地进行状态比较和重复状态检查。

  2. 使用字符串表示:可以使用一个字符串来表示15拼图的状态。其中,每个字符代表一个拼图块的值,0表示空白块。例如,以下是一个15拼图的字符串表示:

    "123456780"
    

    这个表示方式可以方便地进行字符串比较和重复状态检查。

无论选择哪种表示方式,都可以根据拼图的当前状态创建一个唯一的标识符,以便在搜索算法中检查重复状态。这样可以避免在搜索过程中重复访问相同的状态,提高算法的效率。

9.使用广度优先搜索求解传教士与野人问题。

传教士与野人问题是一个经典的搜索问题,可以使用广度优先搜索算法来求解。

以下是使用广度优先搜索求解传教士与野人问题的步骤:

  1. 定义问题的状态表示:将传教士、野人和船的位置作为问题的状态。可以使用一个元组 (M, C, B) 来表示状态,其中 M 表示左岸的传教士数量,C 表示左岸的野人数量,B 表示船的位置(0 表示左岸,1 表示右岸)。
  2. 初始化:将初始状态 (3, 3, 0) 加入到队列中,并创建一个空的已访问状态集合。
  3. 循环直到找到解或队列为空:
    • 从队列中取出一个状态进行扩展。
    • 对于当前状态,生成所有可能的合法下一步状态,并将其加入队列中。
    • 如果生成的下一步状态满足问题的目标条件(例如,(0, 0, 1)),则找到了解,结束搜索。
    • 将当前状态标记为已访问。
  4. 如果队列为空且没有找到解,则问题无解。

以下是一个示例的Python代码实现,使用广度优先搜索来求解传教士与野人问题:

from collections import deque
def is_valid_state(state):
    # 检查传教士和野人数量的合法性
    m, c, b = state
    if m < 0 or c < 0 or m > 3 or c > 3:
        return False
    # 检查传教士数量小于野人数量的情况
    if m > 0 and m < c:
        return False
    # 检查右岸的传教士和野人数量
    m = 3 - m
    c = 3 - c
    if m > 0 and m < c:
        return False
    return True
def solve_missionaries_and_cannibals():
    start_state = (3, 3, 0)
    visited = set()
    queue = deque([(start_state, [])])
    while queue:
        state, path = queue.popleft()
        if state == (0, 0, 1):
            return path  # 找到解,返回路径
        if state in visited:
            continue
        visited.add(state)
        m, c, b = state
        if b == 0:
            next_states = [(m - 1, c, 1), (m - 2, c, 1), (m, c - 1, 1), (m, c - 2, 1), (m - 1, c - 1, 1)]
        else:
            next_states = [(m + 1, c, 0), (m + 2, c, 0), (m, c + 1, 0), (m, c + 2, 0), (m + 1, c + 1, 0)]
        for next_state in next_states:
            if is_valid_state(next_state):
                queue.append((next_state, path + [next_state]))
    return None  # 无解
# 调用函数求解并打印结果
result = solve_missionaries_and_cannibals()
if result:
    print("找到解:")
    for step, state in enumerate(result):
        print(f"步骤 {step+1}: {state}")
else:
    print("无解")

这段代码将输出传教士和野人问题的解,如果存在解的话。每一步都会打印出传教士和野人的数量及船的位置。

10.在河的西岸,一个农夫带着一匹狼、一只山羊和一篮子卷心菜(参见图 2.0)。河上有一艘船,可以装下农夫以及狼、山羊、卷心菜三者中的一个。如果留下狼与羊单独在一起,那么狼会吃掉羊。如果留下羊与卷心菜单独在一起,那么羊会吃掉卷心菜。现在的目标是将它们都安全地转移到河的对岸。请分别使用以下搜索算法解决上述问题:

(a)深度优先搜索;
(b)广度优先搜索。

(a) 使用深度优先搜索解决农夫与狼、羊、卷心菜问题:
深度优先搜索算法会优先探索当前路径的最深处,直到无法继续扩展或找到解为止。以下是使用深度优先搜索解决农夫与狼、羊、卷心菜问题的步骤:

  1. 定义问题的状态表示:将农夫、狼、羊和卷心菜的位置作为问题的状态。可以使用一个元组 (F, W, G, C) 来表示状态,其中 F 表示农夫的位置(0 表示西岸,1 表示东岸),W 表示狼的位置,G 表示羊的位置,C 表示卷心菜的位置。
  2. 初始化:将初始状态 (0, 0, 0, 0) 加入到栈中,并创建一个空的已访问状态集合。
  3. 循环直到找到解或栈为空:
    • 从栈中取出一个状态进行扩展。
    • 对于当前状态,生成所有可能的合法下一步状态,并将其加入栈中。
    • 如果生成的下一步状态满足问题的目标条件(例如,(1, 1, 1, 1)),则找到了解,结束搜索。
    • 将当前状态标记为已访问。
  4. 如果栈为空且没有找到解,则问题无解。
    以下是一个示例的Python代码实现,使用深度优先搜索来求解农夫与狼、羊、卷心菜问题:
def is_valid_state(state):
    # 检查狼和羊单独在一起的情况
    if state[1] == state[2] and state[0] != state[1]:
        return False
    # 检查羊和卷心菜单独在一起的情况
    if state[2] == state[3] and state[0] != state[2]:
        return False
    return True
def solve_farmer_wolf_goat_cabbage_dfs():
    start_state = (0, 0, 0, 0)
    visited = set()
    stack = [(start_state, [])]
    while stack:
        state, path = stack.pop()
        if state == (1, 1, 1, 1):
            return path  # 找到解,返回路径
        if state in visited:
            continue
        visited.add(state)
        next_states = []
        for i in range(4):
            new_state = list(state)
            new_state[i] = 1 - new_state[i]
            if is_valid_state(new_state):
                next_states.append((tuple(new_state), path + [tuple(new_state)]))
        stack.extend(next_states[::-1])
    return None  # 无解
# 调用函数求解并打印结果
result = solve_farmer_wolf_goat_cabbage_dfs()
if result:
    print("找到解:")
    for step, state in enumerate(result):
        print(f"步骤 {step+1}: {state}")
else:
    print("无解")

这段代码将输出农夫与狼、羊、卷心菜问题的解,如果存在解的话。每一步都会打印出农夫、狼、羊和卷心菜的位置。
(b) 使用广度优先搜索解决农夫与狼、羊、卷心菜问题:
广度优先搜索算法会逐层扩展当前路径,直到找到解或遍历完所有可能的状态。以下是使用广度优先搜索解决农夫与狼、羊、卷心菜问题的步骤:

  1. 定义问题的状态表示:同上述深度优先搜索算法的步骤。
  2. 初始化:将初始状态 (0, 0, 0, 0) 加入到队列中,并创建一个空的已访问状态集合。
  3. 循环直到找到解或队列为空:
    • 从队列中取出一个状态进行扩展。
    • 对于当前状态,生成所有可能的合法下一步状态,并将其加入队列中。
    • 如果生成的下一步状态满足问题的目标条件(例如,(1, 1, 1, 1)),则找到了解,结束搜索。
    • 将当前状态标记为已访问。
  4. 如果队列为空且没有找到解,则问题无解。
    以下是一个示例的Python代码实现,使用广度优先搜索来求解农夫与狼、羊、卷心菜问题:
from collections import deque
def is_valid_state(state):
    # 同上述深度优先搜索算法中的 is_valid_state 函数
def solve_farmer_wolf_goat_cabbage_bfs():
    start_state = (0, 0, 0, 0)
    visited = set()
    queue = deque([(start_state, [])])
    while queue:
        state, path = queue.popleft()
        if state == (1, 1, 1, 1):
            return path  # 找到解,返回路径
        if state in visited:
            continue
        visited.add(state)
        next_states = []
        for i in range(4):
            new_state = list(state)
            new_state[i] = 1 - new_state[i]
            if is_valid_state(new_state):
                next_states.append((tuple(new_state), path + [tuple(new_state)]))
        queue.extend(next_states)
    return None  # 无解
# 调用函数求解并打印结果
result = solve_farmer_wolf_goat_cabbage_bfs()
if result:
    print("找到解:")
    for step, state in enumerate(result):
        print(f"步骤 {step+1}: {state}")
else:
    print("无解")

这段代码将输出农夫与狼、羊、卷心菜问题的解,如果存在解的话。每一步都会打印出农夫、狼、羊和卷心菜的位置。

14.我们已经确定,12 枚硬币的假币问题需要对 3 组硬币进行称重才能确定假币。那么在15 枚硬币中,需要称重多少次才可以确定假币?20 枚硬币时又会怎么样?请开发出一种算法来证明自己的结论。提示:可以先考虑 2~5 枚硬币所需的基本称量次数,从而开发出事实知识库,自底向上得到这个问题的解。

要解决这个问题,我们可以使用二分法来逐步缩小假币的范围。

对于 15 枚硬币,我们可以将其分为三组:A、B 和 C,每组各有 5 枚硬币。首先,我们将 A 和 B 组的硬币放在天平的两端进行称重。

  • 如果两端的重量相等,说明假币在 C 组中。我们可以将 C 组的硬币分为两组,每组各有 2 枚硬币,再次进行称重。如果两端的重量相等,说明剩下的一枚硬币为假币。总共进行了 2 次称重。
  • 如果 A 和 B 组的重量不相等,说明假币在 A 或 B 组中。我们可以将重量较轻的一组(假设为 A 组)再次分为两组,每组各有 2 枚硬币,再次进行称重。
  • 如果两端的重量相等,说明剩下的一枚硬币为假币。总共进行了 3 次称重。
  • 如果 A 组的重量不相等,说明假币在 A 组中。我们可以将 A 组的硬币分为两组,每组各有 2 枚硬币,再次进行称重。
  • 如果两端的重量相等,说明剩下的一枚硬币为假币。总共进行了 4 次称重。因此,对于 15 枚硬币,最多需要进行 4 次称重就可以确定假币的位置。

同样的方法,我们可以推广到 20 枚硬币的情况。将 20 枚硬币分为三组:A、B 和 C,每组各有 6 枚硬币。按照上述的步骤进行称重,最多需要进行 4 次称重就可以确定假币的位置。

通过这种自底向上的方法,我们可以得到结论:对于 n 枚硬币,最多需要进行 log3(n) 次称重就可以确定假币的位置。

这个结论可以通过数学归纳法来证明。假设对于 k 枚硬币,最多需要进行 m 次称重来确定假币的位置。那么对于 3k 枚硬币,我们可以将其分为三组,每组各有 k 枚硬币。根据上述的步骤,最多需要进行 m+1 次称重来确定假币的位置。由此可知,对于任意的 n,最多需要进行 log3(n) 次称重来确定假币的位置。

因此,我们可以使用这个算法来确定假币的位置,并且通过数学归纳法证明了结论的正确性。

15.我们讨论了传教士与野人问题。假定“移动”或“转移”是强行(受迫)的,找出这个问题的一个解。确定问题解决状态的“子目标状态”,我们必须获得这个状态,才能解决这个问题。

传教士与野人问题是一个经典的逻辑推理问题,描述了一种情境,在这个情境中,有三名传教士和三名野人需要过河,但是船一次只能容纳两人。而且,如果传教士的数量少于野人的数量,野人会攻击传教士。

为了解决这个问题,我们可以使用深度优先搜索算法,通过尝试所有可能的移动组合来找到一个解。在每一步中,我们需要考虑一些限制条件:

  1. 传教士和野人不能超过船的容量(两人)。
  2. 在河的任一岸边,传教士的数量不能少于野人的数量,否则野人会攻击传教士。
    我们可以使用递归来实现深度优先搜索算法。每一次递归调用,我们尝试将两人从一边移动到另一边,并检查移动后的状态是否满足上述限制条件。如果满足条件,我们继续递归地尝试其他可能的移动组合,直到找到一个解。

以下是一个示例的解决方案:

  1. 初始状态:3 传教士、3 野人在一边,船在一边。
  2. 子目标状态:0 传教士、0 野人在一边,船在一边。

移动过程:

  1. 2 传教士、2 野人从一边到另一边。
  2. 1 传教士、1 野人从另一边到一边。
  3. 2 传教士、0 野人从一边到另一边。
  4. 0 传教士、2 野人从另一边到一边。
  5. 1 传教士、0 野人从一边到另一边。
  6. 1 传教士、1 野人从另一边到一边。
  7. 0 传教士、0 野人从一边到另一边。

通过以上步骤,我们成功地找到了一个解,达到了子目标状态。这个解可以被看作是问题的一个解决状态。需要注意的是,在实际问题中,我们可能需要更多的移动步骤才能找到一个解决状态。这取决于初始状态和限制条件的具体设置。通过深度优先搜索算法,我们可以找到传教士与野人问题的解决状态的子目标状态,并找到一个解。

你可能感兴趣的:(人工智能,算法)