NO.71——用A-star算法求解8数码问题

问题描述: 

\begin{bmatrix} 3 & 5 &7 \\ 2& 1 &4 \\ 0& 6 & 8 \end{bmatrix}    到     \begin{bmatrix} 1 & 2 &3 \\ 4&5 &6 \\ 7& 8& 0 \end{bmatrix}

算法原理:

A*的原理已经在上一篇博客讲解。https://blog.csdn.net/ghl1390490928/article/details/104121195

* 初始化open_set和close_set;
* 将起点加入open_set中,并设置优先级为0(优先级最高);
* 如果open_set不为空,则从open_set中选取优先级最高的节点n:
    * 如果节点n为终点,则:
        * 从终点开始逐步追踪parent节点,一直达到起点;
        * 返回找到的结果路径,算法结束;
    * 如果节点n不是终点,则:
        * 将节点n从open_set中删除,并加入close_set中;
        * 遍历节点n所有的邻近节点:
            * 如果邻近节点m在close_set中,则:
                * 跳过,选取下一个邻近节点
            * 如果邻近节点m也不在open_set中,则:
                * 将节点m加入open_set中
                * 设置节点m的parent为节点n
                * 计算节点m的优先级(F.G.H)
            * 如果临近节点m已经在open_set中,则:
                * 检查这条路径(即m到n)是否更好,用G作参考。如果是更小的G,表明这是更好的路径。 
                  如果是这样,设置m的parent为节点n,并重新计算它的G和F。因为open_set是按F值排 
                  序,改变后会重新排序。

 

实践:

  1. 在本例中,利用一个最小堆来构造一个open_set,这样每次弹出的都是最小值
  2. 利用一个hash_set来构造一个close_set,进行判重
# -*- coding: utf-8 -*-
# /usr/bin/python
# 作者:Slash
# 实验日期:20200119
# Python版本:3.7
# 主题:基于A*算法的简单实现

import heapq  #引入堆
import copy
import re
import datetime

BLOCK = []  # 给定状态
GOAL = []  # 目标状态

# 4个方向
direction = [[0, 1], [0, -1], [1, 0], [-1, 0]]

# OPEN表
OPEN = []

# 节点的总数
SUM_NODE_NUM = 0


# 状态节点
class State(object):
    def __init__(self, gn=0, hn=0, state=None, hash_value=None, par=None):
        '''
            初始化
            :param gn: gn是到起点的距离
            :param hn: 启发距离
            :param state: 节点存储的状态
            :param hash_value: 哈希值,用于判重
            :param par: 父节点指针
            '''
        self.gn = gn
        self.hn = hn
        self.fn = self.gn + self.hn
        self.child = []  # 邻居节点
        self.par = par  # 父节点
        self.state = state  # 局面状态
        self.hash_value = hash_value  # 哈希值
    
    def __lt__(self, other):  # 用于堆的比较,返回距离最小的
        return self.fn < other.fn
    
    def __eq__(self, other):  # 相等的判断
        return self.hash_value == other.hash_value
    
    def __ne__(self, other):  # 不等的判断
        return not self.__eq__(other)


def manhattan_dis(cur_node, end_node):
    '''
        计算曼哈顿距离
        :param cur_state: 当前状态
        :return: 到目的状态的曼哈顿距离
        这个坐标系是以左上角为原点,X轴竖直向下,Y轴向右
        '''
    cur_state = cur_node.state
    end_state = end_node.state
    dist = 0
    N = len(cur_state) # N = 3
    #i-x  j-y
    for i in range(N):
        for j in range(N):
            if cur_state[i][j] == end_state[i][j]:
                continue  #直接跳过本次循环,进行下一次循环
            num = cur_state[i][j]
            if num == 0:
                x = N - 1   #理论横坐标。理论上在右下角位置(2,2)
                y = N - 1   #理论纵坐标
            else:
                x = int(num / N)  # 理论横坐标  比如7理论坐标(2,0)
                y = int(num - N * x - 1)  # 理论的纵坐标
            dist += (abs(x - i) + abs(y - j))

    return dist


def test_fn(cur_node, end_node):
    return 0


def generate_child(cur_node, end_node, hash_set, open_table, dis_fn):
    '''
        生成子节点函数
        :param cur_node:  当前节点
        :param end_node:  最终状态节点
        :param hash_set:  哈希表,用于判重
        :param open_table: OPEN表
        :param dis_fn: 距离函数
        :return: None
        '''
    #如果当前节点就是目标,将目标推入open_set
    if cur_node == end_node:
        #将end_node推入open_table这个堆中
        heapq.heappush(open_table, end_node)
        return
    #print(cur_node.state)  #[[1, 6, 3], [4, 5, 2], [8, 7, 0]]
    num = len(cur_node.state)  # num = 3
    for i in range(0, num):
        for j in range(0, num):
            if cur_node.state[i][j] != 0:
                continue
            for d in direction:  # 四个偏移方向
                x = i + d[0]
                y = j + d[1]
                if x < 0 or x >= num or y < 0 or y >= num:  # 越界了
                    continue
                # 记录扩展节点的个数
                global SUM_NODE_NUM
                SUM_NODE_NUM += 1
                
                state = copy.deepcopy(cur_node.state)  # 复制父节点的状态
                state[i][j], state[x][y] = state[x][y], state[i][j]  # 交换位置。意为九宫格中的每一个点尝试向四个方向移动
                h = hash(str(state))  # 哈希时要先转换成字符串
                # 4. 如果当前节点已经在close_set,跳过当前节点
                if h in hash_set:  # 重复了
                    continue
                # 5. 将当前节点添加至close_set
                hash_set.add(h)  # 加入哈希表
                gn = cur_node.gn + 1  # 已经走的距离函数
                hn = dis_fn(cur_node, end_node)  # 启发的距离函数
                node = State(gn, hn, state, h, cur_node)  # 新建节点
                cur_node.child.append(node)  # 加入到邻居队列
                # 6. 将当前节点的邻居加入到open_set
                heapq.heappush(open_table, node)  

    '''
    这个node是3*3结构
    [1, 6, 3]
    [4, 5, 2]
    [8, 7, 0]
    '''
def print_path(node):
    '''
        输出路径
        :param node: 最终的节点
        :return: None
        '''
    num = node.gn
    
    def show_block(block):
        print("---------------")
        for b in block:
            print(b)

    stack = []  # 模拟栈
    while node.par is not None:
        stack.append(node.state)
        node = node.par
    stack.append(node.state)
    while len(stack) != 0:
        t = stack.pop()
        show_block(t)
    return num


def A_start(start, end, distance_fn, generate_child_fn, time_limit=10):
    '''
        A*算法
        :param start: 起始状态
        :param end: 终止状态
        :param distance_fn: 距离函数,可以使用自定义的
        :param generate_child_fn: 产生孩子节点的函数
        :param time_limit: 时间限制,默认10秒
        :return: None
        '''
    #hash()获取一个对象的哈希值,用于判重
    root = State(0, 0, start, hash(str(BLOCK)), None)  # 根节点
    end_state = State(0, 0, end, hash(str(GOAL)), None)  # 最后的节点
    if root == end_state:
        print("start == end !")
    # 1. 首先将起点添加至open_set
    OPEN.append(root)
    #将open列表转化为堆
    heapq.heapify(OPEN)
    # 2. 创建close_set
    node_hash_set = set()  # 存储节点的哈希值
    node_hash_set.add(root.hash_value)
    start_time = datetime.datetime.now()
    # 2. 遍历这个open_set
    while len(OPEN) != 0:
        # 3. 找出优先级最高的节点,并把这个节点从open_set中弹出
        top = heapq.heappop(OPEN) #弹出堆的最小元素
        if top == end_state:  # 结束后直接输出路径
            return print_path(top)
        # 遍历这个节点的邻居节点,并将它的邻居加入open_set
        generate_child_fn(cur_node=top, end_node=end_state, hash_set=node_hash_set,
                          open_table=OPEN, dis_fn=distance_fn)
        cur_time = datetime.datetime.now()
        # 超时处理
        if (cur_time - start_time).seconds > time_limit:
            print("Time running out, break !")
            print("Number of nodes:", SUM_NODE_NUM)
            return -1

    print("No road !")  # 没有路径
    return -1


def read_block(block, line, N):
    '''
        读取一行数据作为原始状态
        :param block: 原始状态
        :param line: 一行数据
        :param N: 数据的总数
        :return: None
        '''
    pattern = re.compile(r'\d+')  # 正则表达式提取数据
    res = re.findall(pattern, line)
    t = 0
    tmp = []
    for i in res:
        t += 1
        tmp.append(int(i))
        #t每迭代到3就换一行。因此BLOCK=[[1, 6, 3], [4, 5, 2], [8, 7, 0]]
        if t == N:
            t = 0
            block.append(tmp)
            tmp = []
    return block


if __name__ == '__main__':
    try:
        file = open("./nfile.txt", "r")
    except IOError:
        print("can not open file nfile.txt !")
        exit(1)
    
    f = open("./nfile.txt")
    #readline()为默认读取第一行。[-2]为读取倒数第二个字符,倒数第一个字符默认是空格
    NUMBER = int(f.readline()[-2])
    n = 1
    #通过i j进行换行操作。最终GOAL=[[1,2,3],[4,5,6],[7,8,0]]
    for i in range(NUMBER):
        l = []
        for j in range(NUMBER):
            l.append(n)
            n += 1
        GOAL.append(l)
    GOAL[NUMBER - 1][NUMBER - 1] = 0
    
    for line in f:  # 读取每一行数据
        OPEN = []  # 这里别忘了清空
        BLOCK = []
        BLOCK = read_block(BLOCK, line, NUMBER)  #读取起始状态
        SUM_NODE_NUM = 0
        start_t = datetime.datetime.now()
        # 这里添加5秒超时处理,可以根据实际情况选择启发函数
        length = A_start(BLOCK, GOAL, manhattan_dis, generate_child, time_limit=10)
        end_t = datetime.datetime.now()
        if length != -1:
            print("length =", length)
            print("time = ", (end_t - start_t).total_seconds(), "s")
            print("Nodes =", SUM_NODE_NUM)

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
include using namespace std; struct node{ int nodesun[4][4]; int pre; //上一步在队列中的位置 int flag ; //步数标识,表示当前的步数为有效的 int value; //与目标的差距 int x,y; //空格坐标 }queue[1000]; //移动方向数组 int zx[4]={-1,0,1,0}; int zy[4]={0,-1,0,1}; //当前步数 int top; int desti[4][4];//目标状态 int detect(struct node *p)//检查是否找到 {int i,j; for(i=1;i<4;i++) for(j=1;jnodesun[i][j]!=desti[i][j]) return 0; return 1; } //打印 void printlj() {int tempt; int i,j; tempt=top; while(tempt!=0) { for(i=1;i<4;i++) for(j=1;j<4;j++) {cout<<queue[tempt].nodesun[i][j]; if(j==3) cout<<" "<<endl; } tempt=queue[tempt].pre; } } //现在状态与目标状态有多少个不同位置 int VALUE(struct node *p) {int count=0; int i,j; for(i=1;i<4;i++) for(j=1;jnodesun[i][j]!=desti[i][j]) count++; return count; } void main() { //初始化 int i,j,m,n,f; int min=10; int temp,find=0,minnumber; top=1; for(i=1;i<4;i++) for(j=1;j<4;j++) {cout<<"请输入第"<<i<<"行"<<"第"<<j<<"列的值"<>temp; queue[1].nodesun[i][j]=temp; } cout<<"请输入初始状态的空格的位置(行)"<>temp; queue[1].x=temp; cout<<"请输入初始状态的空格的位置(列)"<>temp; queue[1].y=temp; queue[1].value=VALUE(&queue[1]); queue[1].pre=0; //上一步在队列中的位置 queue[1].flag=0; //目标状态 for(i=1;i<4;i++) for(j=1;j<4;j++) {cout<<"请输入目标状态第"<<i<<"行"<<"第"<<j<<"列的值"<>temp; desti[i][j]=temp; } //根据估价函数 while(!find&&top>0) { for(i=1;i<=top;i++) //////////////////////////////////////////// //min为上一图中与目标图有多少个元素不相同,queue[i]为当前图与目标图有多少个元素不相同通过这两个数的比较,就可以得出当前图较之上一图向目标图接近同时把当前的i记录下来进行下一步比较 {if(queue[i].value<min&&queue[i].flag==0) {minnumber=i;// min=queue[i].value; //还有多少不同的位数 } } queue[minnumber].flag=1; //表示此位有效 ////////////////////////////////////// // for(f=0;f=1&&i=1&&j<=3) {top++; ///////////////////////////////////////////// //位置交换 queue[top]=queue[minnumber]; queue[top].nodesun[m][n]=queue[minnumber].nodesun[i][j]; queue[top].nodesun[i][j]=0; /////////////////////////////////////// //空格移动方向 queue[top].x=i; queue[top].y=j; /////////////////////////////////////// queue[top].pre=minnumber; //上一步在队列中的位置 queue[top].value=VALUE(&queue[top]); //有多少位与目标不同 queue[top].flag=0; //标识位初始化 if(detect(&queue[top])) //检查是否为目标 {printlj(); //打印 find=1; //设找到标识位 break; } } } } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值