宽度优先搜索算法解决八数码问题

宽度优先搜索算法解决八数码问题

原理

1、宽度优先搜索是指在一个搜索树中,搜索以同层邻近节点依次扩展节点。这种搜索是逐层进行的,在对下一层的任一节点进行搜索之前,必须搜索完本层的所有节点。
宽度优先搜索算法主要步骤可描述如下:
①令N为一个由初始状态构成的表。
②若N为空退出,标志失败。
③令n为N中第一个节点,将n从N中删除。
④若n是目标,则退出,标志成功。
⑤若n不是目标,将n的后继节点加入到N表的末端,转第②步。

宽度优先搜索算法流程图下图所示:
在这里插入图片描述
2、八数码问题知识表示方法(状态空间法)分析:
1.定义状态空间:根据八数码问题定义出状态空间。然后通过对象给出问题的初始状态、目标状态,给出状态的一般表示。
2.定义操作规则:规定一组算子,作用于一个状态后过渡到另一个状态。
3.定义搜索策略:使用宽度优先搜索,使得能够从初始状态出发,沿某个路径达到目标状态。搜索过程为:沿着4个方向,如果可行都前进一步看是否达到位置。如果没有达到,则依次从新的位置为起点,沿4个方向继续前进一步,直到搜索到目标位置或者找不到未搜索的位置为止。

3、八数码问题判断有无解
对于棋子数列中任何一个棋子c[i],如果有j>i且c[j]<c[i],那么 c[j]是c[i]的一个逆序,或者c i]和c[j]构成一个逆序对。定义棋子c[i]的逆序数为c[i]的逆序个数;棋子数列的逆序数为该数列所有棋子的逆序数总和。
定理:如果棋子数列经过n次相邻棋子交换后,若n为偶数,则数列逆序数奇偶性不变;若n为奇数,则数列逆序数将发生奇偶性互变。
推广:
(1)当初始状态棋局的棋子数列的逆序数是奇数时,八数码问题无解;
(2)当初始状态棋局的棋子数列的逆序数是偶数时,八数码问题有解。

设计思路

1、设计状态空间表示方式
空格定义为0,将数字按行存入列表中,用列表sample存储八数码问题的初始状态,例如[2,8,3,1,6,4,7,0,5];用goal存储八数码问题的目标状态,例如[1,2,3,8,0,4,7,6,5],八数码问题中数字的移动就看做是空格(0)的移动,在列表中直接完成,同时把操作记录存储在列表的末端,如第一步空格向上移动,则移动后的列表变为[2,8,3,1,0,4,7,6,5,‘up’]。
初始节点为[2,8,3,1,6,4,7,0,5],它是搜索树的根节点,而目标状态为[1,2,3,8,0,4,7,6,5],我们做的,就是找出一条或者多条从根节点通往[1,2,3,8,0,4,7,6,5]节点的路径。

2、设计一组算子
用来求解该问题的算子可以用4条规则来描述
①空格(0)向上移动(注意:要满足前提条件,即空格移动方向有数字且移动后的状态为初次生成状态,以下规则也一样)
②空格(0)向下移动
③空格(0)向左移动
④空格(0)向右移动
这四条规则,事实上就是搜索树上从当前节点生成下一个节点的方法。

3、移动方法以及可移动的条件
向上移动和向下移动:假设初始状态 在这里插入图片描述 中,0(空格)所在的位置为中心,存储为[2,8,3,1,0,4,7,6,5]。0所在位置的下标为4,若0要向上移动或向下移动,则实质为与8或6交换位置,则交换操作为:

temp[flag], temp[flag - 3] = temp[flag - 3], temp[flag](向上移动)
temp[flag], temp[flag + 3] = temp[flag + 3], temp[flag](向下移动)

若0所在的位置已经在边界,即无法向上或向下移动,则判断条件为:

flag - 3 >= 0(向上) 
flag + 3 <= 8(向下)

向左移动和向右移动:假设初始状态如上面相同,0所在的位置为中心,存储为[2,8,3,1,0,4,7,6,5],若0要向左向右移动,则实质为与1或4交换位置,交换操作为:

temp[flag], temp[flag - 1] = temp[flag - 1], temp[flag](向左移动)
temp[flag], temp[flag + 1] = temp[flag + 1], temp[flag](向右移动)

若0已经在边界,即无法向左或向右移动,则判断条件为:

flag % 3 != 0(向左) 
(flag + 1) % 3 != 0(向右)

4、判断有无解实现方法
通过双重循环,将列表逆序循环,逐一比较,计算逆序对的个数,遇到0则跳过。最后判断奇偶,偶数有解,奇数无解。

5、宽度优先搜索实现方法
①把起始节点放到OPEN表中,如果该起始节点为一目标节点,则求得一个解答。
②若OPEN是空表,则没有解,失败退出;否则继续。
③把第一个节点(节点死)从OPEN表移出,并把它放入CLOSED的扩展节点表中。
④扩展n。如果没有后继节点,则转向步骤②。
⑤把n的所有后继节点放到OPEN表的末端,并提供从这些后继节点回到n的指针。
⑥如果n的任一个后继节点是个目标节点,则找到一个解答,成功退出;否则转向步骤②;

完整代码:

import time
class QNode(object):#定义队列的数据结构
    def __init__(self, elem, next=None):
        self.elem = elem
        self.next = next

class Queue(object):  # 队列
    def __init__(self):
        self.head = None
        self.rear = None

    def is_empty(self):#判断队列是否为空
        return self.head is None

    def enqueue(self, elem):  # 往队列中添加一个elem元素
        p = QNode(elem)
        if self.is_empty():
            self.head = p
            self.rear = p
        else:
            self.rear.next = p
            self.rear = p

    def dequeue(self):  # 从队列的头部删除一个元素
        result = self.head.elem
        self.head = self.head.next
        return result

    def Search(self,elem):#搜索队列中是否有与elem相同的元素
        temp = self.head
        while temp is not None:
            if elem[:9] == temp.elem[:9]:
                return False
            temp = temp.next
        return True

def jugde(open,closed,temp,i,creatpoint):#判断是否重复
    if open.Search(temp) and closed.Search(temp):
        temp.append(operato[i])
        Open.enqueue(temp)
        creatpoint += 1
    return creatpoint

def  Jugde(sample):#判断有无解
    flag = 0
    for i in sample[::-1]:
        if i != '0':
            this = sample.index(i)
            for x in sample[:this]:
                if x > i:
                    flag += 1
    if flag % 2 ==0 :
        return 0
    return 1

if __name__ == '__main__':
    sample = list(input('请输入初始状态:').split())#存储8数码问题的初始状态
    goal = list(input('请输入目标状态:').split(' '))#存储8数码问题的目标状态
    m = Jugde(sample)
    n = Jugde(goal)
    if m != n:#通过求逆序数判断有无解
        print('无解!')
        exit()
    operato = ['up','down','left','right']
    k = creatpoint = 0 # k 为搜索的节点数 creatpoint 为生成节点数
    Open = Queue()#创建open表
    Closed = Queue()#创建closed表
    Open.enqueue(sample)
    Max = input("请输入最大搜索深度:")
    start = time.time()
    while(1):
        if(sample == goal ):
            print("起始节点为目标结点!")
            break
        if Open.is_empty():
            print("没有解!")
            break
        else:
            p = Open.dequeue()#从Open表中取出
            if len(p)-9 >= int(Max)+1:#判断是否超过设置的最大深度
                print('已达最大深度,未找到解!')
                exit()
            Closed.enqueue(p)#放入Closed表中
            if p[:9] == goal:#判断是否相等
                k += 1
                print('当前层次:{},已搜索节点数:{},已生成结点数{}\n查找成功!'.format(len(p)-9,k,creatpoint))
                print("空格的移动路径依次为:",end = '')
                for i in p[9:]:
                    print(i,end='->')
                end = time.time()
                print('完成\nRunning time:{} Seconds'.format(end - start))
                exit()
            k += 1
            flag = p.index('0')# 返回列表中0的索引  flag = p.index('0')
            if flag - 3 >= 0 :#空格向上移动
                temp = p.copy()
                temp[flag], temp[flag - 3] = temp[flag - 3], temp[flag]
                creatpoint = jugde(Open,Closed,temp,0,creatpoint)
            if flag + 3 <= 8:#空格向下移动
                temp = p.copy()
                temp[flag], temp[flag + 3] = temp[flag + 3], temp[flag]
                creatpoint = jugde(Open, Closed, temp, 1, creatpoint)
            if flag % 3 != 0 :#空格向左移动
                temp = p.copy()
                temp[flag], temp[flag - 1] = temp[flag - 1], temp[flag]
                creatpoint = jugde(Open, Closed, temp, 2, creatpoint)
            if (flag + 1) % 3 != 0:#空格向右移动
                temp = p.copy()
                temp[flag], temp[flag + 1] = temp[flag + 1], temp[flag]
                creatpoint = jugde(Open, Closed, temp, 3, creatpoint)

运行结果

参数设置方案:
初始状态:2 8 3 1 6 4 7 0 5
目标状态:1 2 3 8 0 4 7 6 5
最大搜索深度:10
在这里插入图片描述

结果讨论:

对于一些简单的八数码问题,宽度优先算法可以比较快得找到目标,但是对于一些复杂的步数较多的问题,宽度优先搜索的的效率很低
比如当初始状态为:[2,4 8,6,0,3,1,7,5],目标状态为:[1,2,3,8,0,4,7,6,5]的八数码问题,当最大深度设置为10时,宽度优先搜索无法找到目标状态
在这里插入图片描述
当设置最大深度为15时,找到了目标状态。
在这里插入图片描述

八数码问题是一种经典的搜索问题,它可以使用宽度优先搜索算法进行求解。下面是使用C语言实现的八数码问题宽度优先算法解决方法。 首先,定义一个8个元素的数组,用于表示八数码问题的初始状态。例如,下面的代码表示了一个八数码问题的初始状态。 ```c int start[3][3] = {{1, 2, 3}, {4, 5, 6}, {0, 7, 8}}; ``` 其中,0表示空白格子。 接下来,定义一个节点结构体,用于保存每一个状态的信息。节点包含了当前状态的数组、父节点指针和操作符等信息。 ```c struct node { int state[3][3]; struct node *parent; char op; }; ``` 然后,定义一个队列,用于保存所有的状态节点。初始时,将初始状态节点加入队列中。 ```c struct node *queue[10000]; int head = 0, tail = 0; struct node *startNode = (struct node *)malloc(sizeof(struct node)); memcpy(startNode->state, start, sizeof(start)); startNode->parent = NULL; startNode->op = '\0'; queue[tail++] = startNode; ``` 接下来,使用while循环进行循环搜索。每次循环取出队列头部的节点,对其进行扩展操作。扩展操作包括上下左右四个方向的移动。如果移动后的状态是合法的,将其保存为一个新的节点,并加入队列中。如果移动后的状态是目标状态,则搜索结束。 ```c while (head < tail) { struct node *p = queue[head++]; if (memcmp(p->state, target, sizeof(target)) == 0) { printf("find solution\n"); break; } int x, y; for (x = 0; x < 3; x++) { for (y = 0; y < 3; y++) { if (p->state[x][y] == 0) { break; } } if (y < 3) { break; } } if (x > 0) { struct node *newNode = (struct node *)malloc(sizeof(struct node)); memcpy(newNode->state, p->state, sizeof(p->state)); newNode->state[x][y] = newNode->state[x-1][y]; newNode->state[x-1][y] = 0; newNode->parent = p; newNode->op = 'u'; queue[tail++] = newNode; } if (x < 2) { struct node *newNode = (struct node *)malloc(sizeof(struct node)); memcpy(newNode->state, p->state, sizeof(p->state)); newNode->state[x][y] = newNode->state[x+1][y]; newNode->state[x+1][y] = 0; newNode->parent = p; newNode->op = 'd'; queue[tail++] = newNode; } if (y > 0) { struct node *newNode = (struct node *)malloc(sizeof(struct node)); memcpy(newNode->state, p->state, sizeof(p->state)); newNode->state[x][y] = newNode->state[x][y-1]; newNode->state[x][y-1] = 0; newNode->parent = p; newNode->op = 'l'; queue[tail++] = newNode; } if (y < 2) { struct node *newNode = (struct node *)malloc(sizeof(struct node)); memcpy(newNode->state, p->state, sizeof(p->state)); newNode->state[x][y] = newNode->state[x][y+1]; newNode->state[x][y+1] = 0; newNode->parent = p; newNode->op = 'r'; queue[tail++] = newNode; } } ``` 如果搜索结束后,找到了目标状态,可以通过遍历父节点指针,获取到从初始状态到目标状态的移动路径。 完整代码如下: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> struct node { int state[3][3]; struct node *parent; char op; }; int start[3][3] = {{1, 2, 3}, {4, 5, 6}, {0, 7, 8}}; int target[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 0}}; void printState(int state[3][3]) { int i, j; for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { printf("%d ", state[i][j]); } printf("\n"); } printf("\n"); } int main() { struct node *queue[10000]; int head = 0, tail = 0; struct node *startNode = (struct node *)malloc(sizeof(struct node)); memcpy(startNode->state, start, sizeof(start)); startNode->parent = NULL; startNode->op = '\0'; queue[tail++] = startNode; while (head < tail) { struct node *p = queue[head++]; if (memcmp(p->state, target, sizeof(target)) == 0) { printf("find solution\n"); struct node *q = p; while (q) { printState(q->state); q = q->parent; } break; } int x, y; for (x = 0; x < 3; x++) { for (y = 0; y < 3; y++) { if (p->state[x][y] == 0) { break; } } if (y < 3) { break; } } if (x > 0) { struct node *newNode = (struct node *)malloc(sizeof(struct node)); memcpy(newNode->state, p->state, sizeof(p->state)); newNode->state[x][y] = newNode->state[x-1][y]; newNode->state[x-1][y] = 0; newNode->parent = p; newNode->op = 'u'; queue[tail++] = newNode; } if (x < 2) { struct node *newNode = (struct node *)malloc(sizeof(struct node)); memcpy(newNode->state, p->state, sizeof(p->state)); newNode->state[x][y] = newNode->state[x+1][y]; newNode->state[x+1][y] = 0; newNode->parent = p; newNode->op = 'd'; queue[tail++] = newNode; } if (y > 0) { struct node *newNode = (struct node *)malloc(sizeof(struct node)); memcpy(newNode->state, p->state, sizeof(p->state)); newNode->state[x][y] = newNode->state[x][y-1]; newNode->state[x][y-1] = 0; newNode->parent = p; newNode->op = 'l'; queue[tail++] = newNode; } if (y < 2) { struct node *newNode = (struct node *)malloc(sizeof(struct node)); memcpy(newNode->state, p->state, sizeof(p->state)); newNode->state[x][y] = newNode->state[x][y+1]; newNode->state[x][y+1] = 0; newNode->parent = p; newNode->op = 'r'; queue[tail++] = newNode; } } return 0; } ```
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值