Python : GBF / A * 알고리즘으로 여덟 개의 여왕 문제 해결


1 여덟 여왕 문제

8x8 체스 판이 있으며 이제 체스 판에 8 개의 퀸을 올려야합니다. 만족스러운 점은 각 퀸에 대해 행, 열 또는 두 개의 대각선에 다른 퀸이 없습니다.
여기에 사진 설명 삽입
초기 상태는 [Empty Board]이고 동작은 [한 번에 여왕없이 맨 왼쪽 열에 여왕 만 놓기]입니다. 이런 식으로 체스 판의 같은 열에 최대 한 명의 여왕이 나타날 수 있습니다.

GBF / A * 알고리즘을 이해하지 못하는 경우 여기 를 참조 하세요 .


2 프로그램 코드

2.1 절차 1

프로그램 1 : functions.py. 여기에는 attacked_queens_pairs와 display_board의 두 가지 기능이 있으며, 각각 [체스 판에 해당하는 시퀀스에 해당하는 상호 공격 퀸 쌍 수 계산] 및 [시퀀스의 해당 체스 판 인쇄]의 기능을 완료합니다. 다음과 같이 :

import numpy as np

def attacked_queens_pairs(seqs):
    """
    计算序列对应棋盘的【互相攻击的皇后对数n】
    只需要检查当前棋盘的八个皇后在各自的行和两条对角线上是否有其他皇后,不需判断同列是否有其他皇后
    """
    a = np.array([0] * 81)  # 创建一个有81个0的一维数组
    a = a.reshape(9, 9)  # 改为9*9二维数组。为方便后面使用,只用后八行和后八列的8*8部分,作为一个空白棋盘
    n = 0  # 互相攻击的皇后对数初始化为0

    for i in range(1, 9):
        if seqs[i-1] != 0: # seqs的某一元素为0代表对应棋盘的该列不应该放置任何皇后
            a[seqs[i - 1]][i] = 1  # 根据序列,按从第一列到最后一列的顺序,在空白棋盘对应位置放一个皇后,生成当前序列对应的棋盘

    for i in range(1, 9):
        if seqs[i - 1] == 0:
            continue # seqs的某一元素为0代表着对应的棋盘该列未放置任何皇后,直接进行下一列的判断
        for k in list(range(1, i)) + list(range(i + 1, 9)):  # 检查每个皇后各自所在的行上是否有其他皇后
            if a[seqs[i - 1]][k] == 1:  # 有其他皇后
                n += 1
        t1 = t2 = seqs[i - 1]
        for j in range(i - 1, 0, -1):  # 看左半段的两条对角线
            if t1 != 1:
                t1 -= 1
                if a[t1][j] == 1:
                    n += 1  # 正对角线左半段上还有其他皇后

            if t2 != 8:
                t2 += 1
                if a[t2][j] == 1:
                    n += 1  # 次对角线左半段上还有其他皇后

        t1 = t2 = seqs[i - 1]
        for j in range(i + 1, 9):  # 看右半段的两条对角线
            if t1 != 1:
                t1 -= 1
                if a[t1][j] == 1:
                    n += 1  # 正对角线右半段上还有其他皇后

            if t2 != 8:
                t2 += 1
                if a[t2][j] == 1:
                    n += 1  # 次对角线右半段上还有其他皇后
    return int(n/2)  # 返回n/2,因为A攻击B也意味着B攻击A,因此返回n的一半

def display_board(seqs):
    """
     显示序列对应的棋盘
    """
    board = np.array([0] * 81)  # 创建一个有81个0的一维数组
    board = board.reshape(9, 9)  # 改变为9*9二维数组,为了后面方便使用,只用后八行和后八列的8*8部分,作为一个空白棋盘

    for i in range(1, 9):
        board[seqs[i - 1]][i] = 1  # 根据序列,从第一列到最后一列的顺序,在对应位置放一个皇后,生成当前序列对应的棋盘
    print('对应棋盘如下:')
    for i in board[1:]:
        for j in i[1:]:
            print(j, ' ', end="")  # 有了end="",print就不会换行
        print()  # 输出完一行后再换行,这里不能是print('\n'),否则会换两行
    print('攻击的皇后对数为' + str(attacked_queens_pairs(seqs)))

이 프로그램에는 출력이 없지만 호출 할 주 프로그램에 대해 두 개의 함수가 정의되어 있습니다.

2.2 절차 2

2.2.1 GBF (탐욕스러운 최고의 첫 검색)

Greedy best-first search (Greedy best-first search, GBF). GBF는 가장 빠른 솔루션을 찾을 수 있으므로 대상에 가장 가까운 노드를 먼저 확장합니다. 따라서 GBF는 휴리스틱 정보 (휴리스틱 함수, h (n), n은 노드), 즉 f (n) = h (n) 만 사용하고 노드를 확장하기 전에 목표 테스트를 수행합니다.

프로그램 2 : main.py. 메인 프로그램으로 프로그램 1의 두 가지 기능을 호출하여 8 명의 여왕 문제를 해결하는 GBF의 전체 과정이 완료됩니다. 코드에서 h (n) = 서로 공격하는 여왕의 로그. 다음과 같이 :

import random
import time
from functions import attacked_queens_pairs, display_board

start = time.time()
frontier_priority_queue = [{
    
    'pairs':28, 'seqs':[0] * 8}] # 使用优先级队列去存储未扩展的叶子节点;初始状态为8个0,代表棋盘上无皇后;h(n)=互相攻击的皇后对数,初始设h(n)=28
solution = []
flag = 0 # 代表还未找到解

while frontier_priority_queue: # 若frontier非空就继续循环,若成功找到解则跳出循环输出解,若frontier为空时还未找到解则宣告失败
    first = frontier_priority_queue.pop(0)  # 先扩展h(n)最小的序列;由于每次都会按h(n)将各个序列从小到大排序,所以扩展第一个序列
    seqs = first['seqs']
    if 0 not in seqs: # 扩展节点前做goal test:若序列中无0元素,即八个皇后均已放好,则序列为解序列
        solution = seqs
        flag = 1  # 成功
        break
    nums = list(range(1, 9))  # 元素为1-8的列表
    for j in range(8): # 在序列中第一个为0的位置,即最左未放置皇后的列中挑选一行放置皇后
        pos = seqs.index(0)
        temp_seqs = list(seqs)
        temp = random.choice(nums)  # 在该列随机挑选一行放置皇后
        temp_seqs[pos] = temp # 将皇后放在该列的第temp行
        nums.remove(temp)  # 从nums移除已产生的值
        frontier_priority_queue.append({
    
    'pairs':attacked_queens_pairs(temp_seqs),'seqs':temp_seqs})
    frontier_priority_queue = sorted(frontier_priority_queue, key=lambda x:x['pairs'])

if solution:
    print('已找到解序列:' + str(solution))
    display_board(solution)
else:
    print('算法失败,未找到解')

end = time.time()
print('用时' + str('%.2f' % (end-start)) + 's')

출력은 다음과 같습니다.

已找到解序列:[8, 3, 1, 6, 2, 5, 7, 4]
对应棋盘如下:
0  0  1  0  0  0  0  0  
0  0  0  0  1  0  0  0  
0  1  0  0  0  0  0  0  
0  0  0  0  0  0  0  1  
0  0  0  0  0  1  0  0  
0  0  0  1  0  0  0  0  
0  0  0  0  0  0  1  0  
1  0  0  0  0  0  0  0  
攻击的皇后对数为0
用时4.53s

더 많은 시간이 소요됩니다 ...

2.2.2 A * 알고리즘 (또는 A-star 알고리즘)

A * 알고리즘 f (n) = g (n) + h (n)에서 노드를 확장하기 전에 골 테스트를 수행합니다.

프로그램 2 : main.py. 메인 프로그램으로 프로그램 1의 두 가지 함수를 호출하여 여덟 퀸즈 문제를 해결하는 A * 알고리즘의 전체 과정이 완료됩니다. 코드에서 g (n) = 배치되지 않은 여왕의 수, h (n) = 서로를 공격하는 여왕의 수. 다음과 같이 :

import random
import time
from functions import attacked_queens_pairs, display_board

start = time.time()
frontier_priority_queue = [{
    
    'unplaced_queens':8, 'pairs':28, 'seqs':[0] * 8}] # 初始状态为8个0,代表棋盘上无皇后;g(n)=未放置好的皇后个数,h(n)=互相攻击的皇后对数,初始设h(n)=28,g(n)=8
solution = []
flag = 0 # 代表还未找到解

while frontier_priority_queue: # 若frontier非空就继续循环,若成功找到解则跳出循环输出解,若frontier为空时还未找到解则宣告失败
    first = frontier_priority_queue.pop(0)  # 由于每次都会将frontier排序,所以扩展的是第一个序列
    if first['pairs'] == 0 and first['unplaced_queens'] == 0: # 扩展节点前做goal test:若序列h(n)=g(n)=0,则序列为解序列
        solution = first['seqs']
        flag = 1  # 成功
        break
    nums = list(range(1, 9))  # 元素为1-8的列表
    seqs = first['seqs']
    if seqs.count(0) == 0: # 由于后面代码中的排序机制可能将【8个皇后已放好,即g(n)=0,但互相攻击的皇后对数接近于0,但是不为0,即h(n)!=0】的节点放在首位;而此类节点肯定不符合要求,但是这样的节点是无法扩展的,因为8个皇后已经放完了
        continue # 只能跳过这种节点
    for j in range(8): # 在序列中第一个为0的位置,即最左未放置皇后的列中挑选一行放置皇后
        pos = seqs.index(0)
        temp_seqs = list(seqs)
        temp = random.choice(nums)  # 在该列随机挑选一行放置皇后
        temp_seqs[pos] = temp # 将皇后放在该列的第temp行
        nums.remove(temp)  # 从nums移除已产生的值
        frontier_priority_queue.append({
    
    'unplaced_queens':temp_seqs.count(0), 'pairs':attacked_queens_pairs(temp_seqs),'seqs':temp_seqs})
    frontier_priority_queue = sorted(frontier_priority_queue, key=lambda x:(x['pairs']+x['unplaced_queens']))

if solution:
    print('已找到解序列:' + str(solution))
    display_board(solution)
else:
    print('算法失败,未找到解')

end = time.time()
print('用时' + str('%.2f' % (end-start)) + 's')

출력은 다음과 같습니다.

已找到解序列:[4, 8, 1, 3, 6, 2, 7, 5]
对应棋盘如下:
0  0  1  0  0  0  0  0  
0  0  0  0  0  1  0  0  
0  0  0  1  0  0  0  0  
1  0  0  0  0  0  0  0  
0  0  0  0  0  0  0  1  
0  0  0  0  1  0  0  0  
0  0  0  0  0  0  1  0  
0  1  0  0  0  0  0  0  
攻击的皇后对数为0
用时0.01s

위의 코드는 대부분의 경우 거의 실행되지 않지만 프론티어 정렬 메커니즘으로 인해 실행 시간이 30 분 또는 1 분 이상에 이르는 경우도 있습니다. 왜?

A * 알고리즘 f (n) = g (n) + h (n)에서 f (n)에 따라 프론티어 세트를 작은 것에서 큰 것까지 정렬합니다. 이러한 간단한 추가 때때로 많은 수의 [8 여왕은 g (n) = 0으로 배치되었지만 서로를 공격하는 여왕의 로그는 0에 가깝지만 0이 아닙니다. 즉, h (n)! = 0]의 노드는 1 위 또는 1 위, 이러한 노드는 요구 사항을 확실히 충족하지 못하지만 8 개의 퀸이 이미 출시 되었기 때문에 이러한 노드는 확장 할 수 없으며 노드는 건너 뛸 수 있고 다음 노드는 판단됩니다. [때때로] 그러한 [다량]은 때때로 많은 양 의 실행 시간을 필요로 할 것입니다 . 즉, 실행 시간이 불안정합니다.

주문 메커니즘은 아래에서 수정됩니다.

2.2.3 A * 알고리즘 코드의 사소한 변경

프로그램 2 : main.py. 메인 프로그램으로서 섹션 2.2.2의 프로그램 2 코드는 다음과 같이 수정되었습니다 : 1. 18 행과 19 행의 코드는 더 이상 판단 조건이 필요하지 않기 때문에 삭제되었습니다 .2. 27 행의 정렬 조건이 수정되었습니다. -먼저 h (n)에 따라 순서를 작은 것에서 큰 순서로 정렬합니다. h (n)이 동일하면 g (n)에 따라 작은 순서에서 큰 순서로 순서를 정렬합니다. 다음과 같이 :

import random
import time
from functions import attacked_queens_pairs, display_board

start = time.time()
frontier_priority_queue = [{
    
    'unplaced_queens':8, 'pairs':28, 'seqs':[0] * 8}] # 初始状态为8个0,代表棋盘上无皇后;g(n)=未放置好的皇后个数,h(n)=互相攻击的皇后对数,初始设h(n)=28,g(n)=8
solution = []
flag = 0 # 代表还未找到解

while frontier_priority_queue: # 若frontier非空就继续循环,若成功找到解则跳出循环输出解,若frontier为空时还未找到解则宣告失败
    first = frontier_priority_queue.pop(0)  # 由于每次都会将frontier排序,所以扩展的是第一个序列
    if first['pairs'] == 0 and first['unplaced_queens'] == 0: # 扩展节点前做goal test:若序列h(n)=g(n)=0,则序列为解序列
        solution = first['seqs']
        flag = 1  # 成功
        break
    nums = list(range(1, 9))  # 元素为1-8的列表
    seqs = first['seqs']
    for j in range(8): # 在序列中第一个为0的位置,即最左未放置皇后的列中挑选一行放置皇后
        pos = seqs.index(0)
        temp_seqs = list(seqs)
        temp = random.choice(nums)  # 在该列随机挑选一行放置皇后
        temp_seqs[pos] = temp # 将皇后放在该列的第temp行
        nums.remove(temp)  # 从nums移除已产生的值
        frontier_priority_queue.append({
    
    'unplaced_queens':temp_seqs.count(0), 'pairs':attacked_queens_pairs(temp_seqs),'seqs':temp_seqs})
    frontier_priority_queue = sorted(frontier_priority_queue, key=lambda x:(x['pairs'], x['unplaced_queens'])) # 先按h(n)从小到大的顺序将序列排序;若h(n)相同,则按g(n)从小到大的顺序将序列排序

if solution:
    print('已找到解序列:' + str(solution))
    display_board(solution)
else:
    print('算法失败,未找到解')

end = time.time()
print('用时' + str('%.2f' % (end-start)) + 's')

출력은 다음과 같습니다.

已找到解序列:[1, 7, 4, 6, 8, 2, 5, 3]
对应棋盘如下:
1  0  0  0  0  0  0  0  
0  0  0  0  0  1  0  0  
0  0  0  0  0  0  0  1  
0  0  1  0  0  0  0  0  
0  0  0  0  0  0  1  0  
0  0  0  1  0  0  0  0  
0  1  0  0  0  0  0  0  
0  0  0  0  1  0  0  0  
攻击的皇后对数为0
用时0.05s

프런티어 정렬 메커니즘을 수정하면 얻을 수있는 이점은 분명합니다. 실행 시간은 1 초 이내에 안정적입니다. 문제를 해결하기 위해 A * 알고리즘을 사용할 때 f (n)에 g (n)과 h (n)을 더한 다음 f (n)에 따라 정렬할지 여부는 고려할 가치가있는 질문임을 알 수 있습니다. .

이 섹션에서 수정 된 코드는 더 이상 A * 알고리즘의 코드로 계산할 수 없습니다.


3 건의 리뷰

GBF 알고리즘에 비해 A * 알고리즘 코드는 실행 시간이 적고 결국 A * 알고리즘은 글로벌 최적화이며보다 유용한 정보를 사용합니다.


종료

추천

출처blog.csdn.net/qq_40061206/article/details/112033438