【人工智能】实验三 A*算法求解八/十五数码问题实验与基础知识

实验三 A*算法求解八数码问题实验

实验目的

熟悉和掌握启发式搜索的定义、估价函数和算法过程,并利用A*算法求解N数码难题,理解求解流程和搜索顺序。

实验内容

  • 以8数码问题和15数码问题为例实现A*算法的求解程序(编程语言不限)。
  • 设计两种不同的估价函数。

实验要求

  1. 设置相同的初始状态和目标状态,针对不同的估价函数,求得问题的解,比较它们对搜索算法性能的影响,包括扩展节点数、生成节点数等,填入表1。

表1:不同启发函数h(n)求解8数码问题的结果比较

  1. 设置与上述1相同的初始状态和目标状态,用宽度优先搜索算法(即令估计代价h(n)=0的A*算法)求得问题的解,以及搜索过程中的扩展节点数、生成节点数,填入表1。
  2. 实现A*算法求解15数码问题的程序,设计两种不同的估价函数,然后重复上述2和3的实验内容, 把结果填入表2。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

实验报告要求

  1. 分析不同的估价函数对A*算法性能的影响。
    A算法是一种常用的启发式搜索算法,它通过综合考虑到达目标状态的代价和启发函数的估计来选择下一个扩展的节点。估价函数在A算法中起到关键作用,它用于评估当前节点到目标状态的代价。

不同的估价函数会对A*算法的性能产生影响,下面列举了几个常见的估价函数及其影响:

曼哈顿距离(Manhattan Distance):曼哈顿距离是指两点之间沿着坐标轴的距离总和,被广泛应用于求解拼图问题等。当估价函数选择曼哈顿距离时,A*算法会更倾向于优先扩展距离目标状态更近的节点。这种估价函数比较简单,但可能导致路径偏向直线而忽略其他因素。

欧几里得距离(Euclidean Distance):欧几里得距离是指两点之间的直线距离,它具有更好的几何意义。当估价函数选择欧几里得距离时,A*算法可能会探索更多的路径,并更倾向于选择直线路径。但欧几里得距离在某些情况下可能会高估代价,导致搜索效率较低。

启发式函数的加权和:有时候,一个估价函数难以完整地表达问题的复杂性。在这种情况下,可以考虑使用多个启发式函数,并为它们分配不同的权重。通过调整权重,可以平衡不同启发式函数之间的影响,提高A*算法的性能。

最小生成树估价函数(Minimum Spanning Tree Heuristic):这是一种基于图论中最小生成树概念的估价函数。它通过计算当前节点到剩余未拓展节点的最小生成树代价来评估节点的优先级。该估价函数具有更好的理论保证,并且在某些问题上性能良好。

总体而言,选择合适的估价函数对于A*算法的性能至关重要。一个好的估价函数应该能够准确估计代价,并且在搜索空间中引导算法朝着目标状态前进。对于特定问题,需要根据问题的特点和启发性信息设计合适的估价函数,以获得更好的性能。

  1. 根据宽度优先搜索算法和A*算法求解8、15数码问题的结果, 分析启发式搜索的特点。
    宽度优先搜索算法:宽度优先搜索算法是一种无信息搜索算法,它从初始状态开始逐层扩展节点,直到找到目标状态为止。在8或15数码问题中,宽度优先搜索算法会逐渐扩展各个可能的状态,直到找到最短路径。它可以保证找到最短路径,但对于搜索空间较大的问题,宽度优先搜索的时间和空间复杂度都很高。

A*算法:A算法是一种启发式搜索算法,在估价函数的引导下选择下一个扩展节点。对于8或15数码问题,A算法使用曼哈顿距离作为估价函数

  1. 画出A*算法求解N数码问题的流程图。
    在这里插入图片描述

  2. 提交源程序。

八位:

#include "iostream"  
#include "stdlib.h"  
#include "conio.h"  
#include <math.h>
#include <windows.h>
#define size 3  
using namespace std;  
//定义二维数组来存储数据表示某一个特定状态  
typedef int status[size][size];  
struct SpringLink;  
  
//定义状态图中的结点数据结构  
typedef struct Node  
{  
    status data;//结点所存储的状态 ,一个3*3矩阵 
    struct Node *parent;//指向结点的父亲结点  
    struct SpringLink *child;//指向结点的后继结点  
    struct Node *next;//指向open或者closed表中的后一个结点  
    int fvalue;//结点的总的路径  
    int gvalue;//结点的实际路径  
    int hvalue;//结点的到达目标的困难程度  
}NNode , *PNode;  
  
  
//定义存储指向结点后继结点的指针的地址  
typedef struct SpringLink  
{  
    struct Node *pointData;//指向结点的指针  
    struct SpringLink *next;//指向兄第结点  
}SPLink , *PSPLink;  
  
  
PNode open;  
PNode closed;  
//OPEN表保存所有已生成而未考察的节点,CLOSED表中记录已访问过的节点
  
//开始状态与目标状态  
/*
status startt = {1,3,0,8,2,4,7,6,5};最佳路径为2   
status startt = {1,3,0,2,8,4,7,6,5};迭代超过20000次,手动停止 
status startt = {2,8,3,1,6,4,7,0,5}; 
status startt = {2,8,3,6,0,4,1,7,5}; //实验报告
*/ 
int t=0; //迭代次数,相当于运行时间 
int count_extendnode=0;//扩展结点 
int count_sumnode=0; //生成节点 
status startt = {2,8,3,6,0,4,1,7,5}; //实验报告
status target = {1,2,3,8,0,4,7,6,5};  
//初始化一个空链表  
void initLink(PNode &Head)  
{  
    Head = (PNode)malloc(sizeof(NNode));  
    Head->next = NULL;  
}  
  
  
//判断链表是否为空  
bool isEmpty(PNode Head)  
{  
    if(Head->next == NULL)  
        return true;  
    else  
        return false;  
}  
  
  
//从链表中拿出一个数据  
void popNode(PNode &Head , PNode &FNode)  
{  
    if(isEmpty(Head))  
    {  
        FNode = NULL;  
        return;  
    }  
    FNode = Head->next;  
    Head->next = Head->next->next;  
    FNode->next = NULL;  
}  
  
  
  
//向结点的最终后继结点链表中添加新的子结点  
void addSpringNode(PNode &Head , PNode newData)  
{  
    PSPLink newNode = (PSPLink)malloc(sizeof(SPLink));  
    newNode->pointData = newData;  
  
    newNode->next = Head->child;  
    Head->child = newNode;  
}  
  
  
//释放状态图中存放结点后继结点地址的空间  
void freeSpringLink(PSPLink &Head)  
{  
    PSPLink tmm;  
  
    while(Head != NULL)  
    {  
        tmm = Head;  
        Head = Head->next;  
        free(tmm);  
    }  
}  
  
  
//释放open表与closed表中的资源  
void freeLink(PNode &Head)  
{  
    PNode tmn;  
  
    tmn = Head;  
    Head = Head->next;  
    free(tmn);  
  
    while(Head != NULL)  
    {  
        //首先释放存放结点后继结点地址的空间  
        freeSpringLink(Head->child);  
        tmn = Head;  
        Head = Head->next;  
        free(tmn);  
    }  
}  
  
  
//向普通链表中添加一个结点  
void addNode(PNode &Head , PNode &newNode)  
{  
    newNode->next = Head->next;  
    Head->next = newNode;  
}  
  
  
//向非递减排列的链表中添加一个结点(已经按照权值进行排序)  
void addAscNode(PNode &Head , PNode &newNode)  
{  
    
    PNode P;  
    PNode Q;  
  
    P = Head->next;  
    Q = Head;  
    while(P != NULL && P->fvalue < newNode->fvalue)  
    {  
        Q = P;  
        P = P->next;  
    }  
    //上面判断好位置之后,下面就是简单的插入了  
    newNode->next = Q->next;  
    Q->next = newNode;  
}  
  
/*
//计算结点的 h 值  f=g+h. 按照不在位的个数进行计算 
int computeHValue(PNode theNode)  
{  
    int num = 0;  
  
    for(int i = 0 ; i < 3 ; i++)  
    {  
        for(int j = 0 ; j < 3 ; j++)  
        {  
            if(theNode->data[i][j] != target[i][j])  
                num++;  
        }  
    }  
    return num;  
}   
//计算结点的 h 值  f=g+h. 按照将牌不在位的距离和进行计算 
int computeHValue(PNode theNode)  
{  
    int num = 0;  
  
    for(int i = 0 ; i < 3 ; i++)  
    {  
        for(int j = 0 ; j < 3 ; j++)  
        {  
            if(theNode->data[i][j] != target[i][j]&&theNode->data[i][j] !=0){
            	for(int ii=0;ii<3;ii++){
            		for(int jj=0;jj<3;jj++){
            			if(theNode->data[i][j] == target[ii][jj]){
            				num+=abs(ii-i)+abs(jj-j);
							break; 
						}
					}
				}
			}
			
        }  
    }  
    return num;  
}  */ 
  
//计算结点的f,g,h值  
void computeAllValue(PNode &theNode , PNode parentNode)  
{  
    if(parentNode == NULL)  
        theNode->gvalue = 0;  
    else  
        theNode->gvalue = parentNode->gvalue + 1;  
  
//    theNode->hvalue = computeHValue(theNode);  
    theNode->hvalue = 0;  
    theNode->fvalue = theNode->gvalue + theNode->hvalue;  
}  
  
  
  
//初始化函数,进行算法初始条件的设置  
void initial()  
{  
    //初始化open以及closed表  
    initLink(open);  
    initLink(closed);  
  
    //初始化起始结点,令初始结点的父节点为空结点  
    PNode NULLNode = NULL;  
    PNode Start = (PNode)malloc(sizeof(NNode));  
    for(int i = 0 ; i < 3 ; i++)  
    {  
        for(int j = 0 ; j < 3 ; j++)  
        {  
            Start->data[i][j] = startt[i][j];  
        }  
    }  
    Start->parent = NULL;  
    Start->child = NULL;  
    Start->next = NULL;  
    computeAllValue(Start , NULLNode);  
  
    //起始结点进入open表	  
    addAscNode(open , Start);  
    
}  
  
  
//将B节点的状态赋值给A结点  
void statusAEB(PNode &ANode , PNode BNode)  
{  
    for(int i = 0 ; i < 3 ; i++)  
    {  
        for(int j = 0 ; j < 3 ; j++)  
        {  
            ANode->data[i][j] = BNode->data[i][j];  
        }  
    }  
}  
  
  
//两个结点是否有相同的状态  
bool hasSameStatus(PNode ANode , PNode BNode)  
{  
    for(int i = 0 ; i < 3 ; i++)  
    {  
        for(int j = 0 ; j < 3 ; j++)  
        {  
            if(ANode->data[i][j] != BNode->data[i][j])  
                return false;  
        }  
    }  
    return true;  
}  
  
  
  
//结点与其祖先结点是否有相同的状态  
bool hasAnceSameStatus(PNode OrigiNode , PNode AnceNode)  
{  
    while(AnceNode != NULL)  
    {  
        if(hasSameStatus(OrigiNode , AnceNode))  
            return true;  
        AnceNode = AnceNode->parent;  
    }  
    return false;  
}  
  
  
//取得方格中空的格子的位置  
void getPosition(PNode theNode , int &row , int &col)  
{  
    for(int i = 0 ; i < 3 ; i++)  
    {  
        for(int j = 0 ; j < 3 ; j++)  
        {  
            if(theNode->data[i][j] == 0)  
            {  
                row = i;  
                col = j;  
                return;  
            }  
        }  
    }  
}  
  
  
//交换两个数字的值  
void changeAB(int &A , int &B)  
{  
    int C;  
    C = B;  
    B = A;  
    A = C;  
}  
  
  
//检查相应的状态是否在某一个链表中  
bool inLink(PNode spciNode , PNode theLink , PNode &theNodeLink , PNode &preNode)  
{  
    preNode = theLink;  
    theLink = theLink->next;  
  
    while(theLink != NULL)  
    {  
        if(hasSameStatus(spciNode , theLink))  
        {  
            theNodeLink = theLink;  
            return true;  
        }  
        preNode = theLink;  
        theLink = theLink->next;  
    }  
    return false;  
}  
  
  
  
//产生结点的后继结点(与祖先状态不同)链表  
void SpringLink(PNode theNode , PNode &spring)  
{  
    int row;  
    int col;  
  
    getPosition(theNode , row , col);  
  
    //空的格子右边的格子向左移动  
    if(col != 2)  
    {  
        PNode rlNewNode = (PNode)malloc(sizeof(NNode));  
        statusAEB(rlNewNode , theNode);  
        changeAB(rlNewNode->data[row][col] , rlNewNode->data[row][col + 1]);  
        if(hasAnceSameStatus(rlNewNode , theNode->parent))  
        {  
            free(rlNewNode);//与父辈相同,丢弃本结点  
        }  
        else  
        {  
            rlNewNode->parent = theNode;  
            rlNewNode->child = NULL;  
            rlNewNode->next = NULL;  
            computeAllValue(rlNewNode , theNode);  
            //将本结点加入后继结点链表  
            addNode(spring , rlNewNode);  
        }  
    }  
    //空的格子左边的格子向右移动  
    if(col != 0)  
    {  
        PNode lrNewNode = (PNode)malloc(sizeof(NNode));  
        statusAEB(lrNewNode , theNode);  
        changeAB(lrNewNode->data[row][col] , lrNewNode->data[row][col - 1]);  
        if(hasAnceSameStatus(lrNewNode , theNode->parent))  
        {  
            free(lrNewNode);//与父辈相同,丢弃本结点  
        }  
        else  
        {  
            lrNewNode->parent = theNode;  
            lrNewNode->child = NULL;  
            lrNewNode->next = NULL;  
            computeAllValue(lrNewNode , theNode);  
            //将本结点加入后继结点链表  
            addNode(spring , lrNewNode);  
        }  
    }  
    //空的格子上边的格子向下移动  
    if(row != 0)  
    {  
        PNode udNewNode = (PNode)malloc(sizeof(NNode));  
        statusAEB(udNewNode , theNode);  
        changeAB(udNewNode->data[row][col] , udNewNode->data[row - 1][col]);  
        if(hasAnceSameStatus(udNewNode , theNode->parent))  
        {  
            free(udNewNode);//与父辈相同,丢弃本结点  
        }  
        else  
        {  
            udNewNode->parent = theNode;  
            udNewNode->child = NULL;  
            udNewNode->next = NULL;  
            computeAllValue(udNewNode , theNode);  
            //将本结点加入后继结点链表  
            addNode(spring , udNewNode);  
        }  
    }  
    //空的格子下边的格子向上移动  
    if(row != 2)  
    {  
        PNode duNewNode = (PNode)malloc(sizeof(NNode));  
        statusAEB(duNewNode , theNode);  
        changeAB(duNewNode->data[row][col] , duNewNode->data[row + 1][col]);  
        if(hasAnceSameStatus(duNewNode , theNode->parent))  
        {  
            free(duNewNode);//与父辈相同,丢弃本结点  
        }  
        else  
        {  
            duNewNode->parent = theNode;  
            duNewNode->child = NULL;  
            duNewNode->next = NULL;  
            computeAllValue(duNewNode , theNode);  
            //将本结点加入后继结点链表  
            addNode(spring , duNewNode);  
        }  
    }  
}  
  
  
//输出给定结点的状态  
void outputStatus(PNode stat)  
{  
    for(int i = 0 ; i < 3 ; i++)  
    {  
        for(int j = 0 ; j < 3 ; j++)  
        {  
            cout << stat->data[i][j] << " ";  
        }  
        cout << endl;  
    }  
}  
  
  
  
//输出最佳的路径  
void outputBestRoad(PNode goal)  
{  
    int deepnum = goal->gvalue;  
  
    if(goal->parent != NULL)  
    {  
        outputBestRoad(goal->parent);  
    }  
    cout << "第" << deepnum-- << "层的状态:" << endl;  
    outputStatus(goal);  
}  
  
  
void AStar()  
{  
 
    PNode tmpNode;//指向从open表中拿出并放到closed表中的结点的指针  
    PNode spring;//tmpNode的后继结点链  
    PNode tmpLNode;//tmpNode的某一个后继结点  
    PNode tmpChartNode; 
	 
    PNode thePreNode;//指向将要从closed表中移到open表中的结点的前一个结点的指针  
    bool getGoal = false;//标识是否达到目标状态  
    long numcount = 1;//记录从open表中拿出结点的序号  
  	
    initial();//对函数进行初始化  
    initLink(spring);//对后继链表的初始化  
    tmpChartNode = NULL;  
    //Target.data=target;
    	cout<<"1"<<endl;
    PNode Target = (PNode)malloc(sizeof(NNode)); 
    for(int i = 0 ; i < 3 ; i++)  
    {  
        for(int j = 0 ; j < 3 ; j++)  
        {  
            Target->data[i][j] =target[i][j];  
        }  
    }
  	cout<<"1"<<endl;
    cout << "从open表中拿出的结点的状态及相应的值" << endl;
 
    while(!isEmpty(open))  
    {  
    	t++;
        //从open表中拿出f值最小的元素,并将拿出的元素放入closed表中  
        popNode(open , tmpNode); 
        addNode(closed , tmpNode);
		count_extendnode=count_extendnode+1;    
		  
        cout << "第" << numcount++ << "个状态是:" << endl;  
        outputStatus(tmpNode);  
        cout << "其f值为:" << tmpNode->fvalue << endl;  
        cout << "其g值为:" << tmpNode->gvalue << endl;  
        cout << "其h值为:" << tmpNode->hvalue << endl;  
  
  
       /* //如果拿出的元素是目标状态则跳出循环  
        if(computeHValue(tmpNode) == 0)  
        {  count_extendnode=count_extendnode-1;
            getGoal = true;  
            break;  
        }*/ 
		//如果拿出的元素是目标状态则跳出循环  
        if(hasSameStatus(tmpNode,Target)== true)  
        {  count_extendnode=count_extendnode-1;
            getGoal = true;  
            break;  
        }  
  
        //产生当前检测结点的后继(与祖先不同)结点列表,产生的后继结点的parent属性指向当前检测的结点  
        SpringLink(tmpNode , spring);  
  
        //遍历检测结点的后继结点链表  
        while(!isEmpty(spring))  
        {  
            popNode(spring , tmpLNode);  
            //状态在open表中已经存在,thePreNode参数在这里并不起作用  
            if(inLink(tmpLNode , open , tmpChartNode , thePreNode))  
            {  
                addSpringNode(tmpNode , tmpChartNode);  
                if(tmpLNode->gvalue < tmpChartNode->gvalue)  
                {  
                    tmpChartNode->parent = tmpLNode->parent;  
                    tmpChartNode->gvalue = tmpLNode->gvalue;  
                    tmpChartNode->fvalue = tmpLNode->fvalue;  
                }  
                free(tmpLNode);  
            }  
            //状态在closed表中已经存在  
            else if(inLink(tmpLNode , closed , tmpChartNode , thePreNode))  
            {  
                addSpringNode(tmpNode , tmpChartNode);  
                if(tmpLNode->gvalue < tmpChartNode->gvalue)  
                {  
                    PNode commu;  
                    tmpChartNode->parent = tmpLNode->parent;  
                    tmpChartNode->gvalue = tmpLNode->gvalue;  
                    tmpChartNode->fvalue = tmpLNode->fvalue;  
                    freeSpringLink(tmpChartNode->child);  
                    tmpChartNode->child = NULL;  
                    popNode(thePreNode , commu);  
                    addAscNode(open , commu);  
                }  
                free(tmpLNode);  
            }  
            //新的状态即此状态既不在open表中也不在closed表中  
            else  
            {  
                addSpringNode(tmpNode , tmpLNode);  
                addAscNode(open , tmpLNode); 	
				count_sumnode+=1;//生成节点			 
            }  
        }  
    }  
  
    //目标可达的话,输出最佳的路径  
    if(getGoal)  
    {  
        cout << endl;  
        cout << "最佳路径长度为:" << tmpNode->gvalue << endl;
		cout << "最佳路径为:" <<endl;
        outputBestRoad(tmpNode);  
    }  
  
    //释放结点所占的内存  
    freeLink(open);  
    freeLink(closed);  
//    getch();  
}  
  
  
int main()  
{ 	double start = GetTickCount(); 
    AStar();  
    printf("生成节点数目:%d\n",count_sumnode);	
    printf("扩展节点数目:%d\n",count_extendnode); 
    printf("运行时间:%f\n",GetTickCount()-start); 
    return 0;  
}  

十五位:

#-*-coding:utf-8-*-

import heapq
import copy
import time
import math
import argparse

# 初始状态
# S0 = [[11, 9, 4, 15],
#       [1, 3, 0, 12],
#       [7, 5, 8, 6],
#       [13, 2, 10, 14]]
S0 = [[5, 1, 2, 4],
      [9, 6, 3, 8],
      [13, 15, 10, 11],
      [0, 14, 7, 12]]

# 目标状态
SG = [[1, 2, 3, 4],
      [5, 6, 7, 8],
      [9, 10, 11, 12],
      [13, 14, 15, 0]]

# 上下左右四个方向移动
MOVE = {'up': [1, 0],
        'down': [-1, 0],
        'left': [0, -1],
        'right': [0, 1]}

# OPEN表
OPEN = []

# 节点的总数
SUM_NODE_NUM = 0

# 状态节点
class State(object):
    def __init__(self, deepth=0, rest_dis=0.0, state=None, hash_value=None, father_node=None):
        '''
        初始化
        :参数 deepth: 从初始节点到目前节点所经过的步数
        :参数 rest_dis: 启发距离
        :参数 state: 节点存储的状态 4*4的列表
        :参数 hash_value: 哈希值,用于判重
        :参数 father_node: 父节点指针
        '''
        self.deepth = deepth
        self.rest_dis = rest_dis
        self.fn = self.deepth + self.rest_dis
        self.child = []  # 孩子节点
        self.father_node = father_node  # 父节点
        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 cal_M_distence(cur_state):
    '''
    计算曼哈顿距离
    :参数 state: 当前状态,4*4的列表, State.state
    :返回: M_cost 每一个节点计算后的曼哈顿距离总和
    '''
    M_cost = 0
    for i in range(4):
        for j in range(4):
            if cur_state[i][j] == SG[i][j]:
                continue
            num = cur_state[i][j]
            if num == 0:
                x, y = 3, 3
            else:
                x = num / 4  # 理论横坐标
                y = num - 4 * x - 1  # 理论的纵坐标
                M_cost += (abs(x - i) + abs(y - j))
    return M_cost

def cal_E_distence(cur_state):
    '''
    计算曼哈顿距离
    :参数 state: 当前状态,4*4的列表, State.state
    :返回: M_cost 每一个节点计算后的曼哈顿距离总和
    '''
    E_cost = 0
    for i in range(4):
        for j in range(4):
            if cur_state[i][j] == SG[i][j]:
                continue
            num = cur_state[i][j]
            if num == 0:
                x, y = 3, 3
            else:
                x = num / 4  # 理论横坐标
                y = num - 4 * x - 1  # 理论的纵坐标
                E_cost += math.sqrt((x - i)*(x - i) + (y - j)*(y - j))
    return E_cost

def generate_child(sn_node, sg_node, hash_set, open_table, cal_distence):
    '''
    生成子节点函数
    :参数 sn_node:  当前节点
    :参数 sg_node:  最终状态节点
    :参数 hash_set:  哈希表,用于判重
    :参数 open_table: OPEN表
    :参数 cal_distence: 距离函数
    :返回: None
    '''
    if sn_node == sg_node:
        heapq.heappush(open_table, sg_node)
        print('已找到终止状态!')
        return
    for i in range(0, 4):
        for j in range(0, 4):
            if sn_node.state[i][j] != 0:
                continue
            for d in ['up', 'down', 'left', 'right']:  # 四个偏移方向
                x = i + MOVE[d][0]
                y = j + MOVE[d][1]
                if x < 0 or x >= 4 or y < 0 or y >= 4:  # 越界了
                    continue
                state = copy.deepcopy(sn_node.state)  # 复制父节点的状态
                state[i][j], state[x][y] = state[x][y], state[i][j]  # 交换位置
                h = hash(str(state))  # 哈希时要先转换成字符串
                if h in hash_set:  # 重复了
                    continue
                hash_set.add(h)  # 加入哈希表

                # 记录扩展节点的个数
                global SUM_NODE_NUM
                SUM_NODE_NUM += 1

                deepth = sn_node.deepth + 1  # 已经走的距离函数
                rest_dis = cal_distence(state)  # 启发的距离函数
                node = State(deepth, rest_dis, state, h, sn_node)  # 新建节点
                sn_node.child.append(node)  # 加入到孩子队列
                heapq.heappush(open_table, node)  # 加入到堆中

                # show_block(state, deepth) # 打印每一步的搜索过程


def show_block(block, step):
    print("------", step, "--------")
    for b in block:
        print(b)

def print_path(node):
    '''
    输出路径
    :参数 node: 最终的节点
    :返回: None
    '''
    print("最终搜索路径为:")
    steps = node.deepth

    stack = []  # 模拟栈
    while node.father_node is not None:
        stack.append(node.state)
        node = node.father_node
    stack.append(node.state)
    step = 0
    while len(stack) != 0:
        t = stack.pop()
        show_block(t, step)
        step += 1
    return steps


def A_start(start, end, distance_fn, generate_child_fn):
    '''
    A*算法
    :参数 start: 起始状态
    :参数 end: 终止状态
    :参数 distance_fn: 距离函数,可以使用自定义的
    :参数 generate_child_fn: 产生孩子节点的函数
    :返回: 最优路径长度
    '''
    root = State(0, 0, start, hash(str(S0)), None)  # 根节点
    end_state = State(0, 0, end, hash(str(SG)), None)  # 最后的节点
    if root == end_state:
        print("start == end !")

    OPEN.append(root)
    heapq.heapify(OPEN)

    node_hash_set = set()  # 存储节点的哈希值
    node_hash_set.add(root.hash_value)
    while len(OPEN) != 0:
        top = heapq.heappop(OPEN)
        if top == end_state:  # 结束后直接输出路径
            return print_path(top)
        # 产生孩子节点,孩子节点加入OPEN表
        generate_child_fn(sn_node=top, sg_node=end_state, hash_set=node_hash_set,
                          open_table=OPEN, cal_distence=distance_fn)
    print("无搜索路径!")  # 没有路径
    return -1

if __name__ == '__main__':

    # 可配置式运行文件
    parser = argparse.ArgumentParser(description='选择距离计算方法')
    parser.add_argument('--method', '-m', help='method 选择距离计算方法(cal_E_distence or cal_M_distence)', default = 'cal_M_distence')
    args = parser.parse_args()
    method = args.method

    time1 = time.time()
    if method == 'cal_E_distence':
        length = A_start(S0, SG, cal_E_distence, generate_child)
    else:
        length = A_start(S0, SG, cal_M_distence, generate_child)
    time2 = time.time()
    if length != -1:
        if method == 'cal_E_distence':
            print("采用欧式距离计算启发函数")
        else:
            print("采用曼哈顿距离计算启发函数")
        print("搜索最优路径长度为", length)
        print("搜索时长为", (time2 - time1), "s")
        print("共检测节点数为", SUM_NODE_NUM)
}
  1. 不同估价函数对A*算法性能的影响分析:
    • 针对不同的估价函数,比较其在搜索过程中的扩展节点数、生成节点数以及运行时间。分析估价函数对算法性能的影响。
  2. 宽度优先搜索和A算法求解数码问题的比较分析:
    • 比较宽度优先搜索和A
    算法在求解8数码问题时的扩展节点数、生成节点数等性能指标,分析启发式搜索的特点。
  3. A算法求解N数码问题的流程图:
    • 画出A
    算法求解N数码问题的流程图,明确每个步骤和决策点,以展示算法的搜索流程。
  4. 提交源程序:
    • 提交实现A*算法的源代码,包括估价函数的设计和算法实现部分。

实验思考及实践

估价函数选择:
在设计估价函数时,需要权衡精度和计算复杂度。一些常见的估价函数包括曼哈顿距离、不在位数等。曼哈顿距离计算复杂度较高,但更准确,而不在位数计算简单但可能不够精确。在实验中,对于不同问题和数据集,可以根据实际情况选择合适的估价函数。
A算法性能分析:
A
算法在搜索过程中利用估价函数进行启发式搜索,能够更智能地选择节点进行扩展,相比于宽度优先搜索,减少了搜索空间,提高了搜索效率。然而,不同的估价函数会导致算法性能的差异,有时需要根据具体问题进行调优。
流程图绘制:
画出A算法求解N数码问题的流程图有助于理解算法的执行过程,包括节点扩展、估价值计算、优先队列的维护等关键步骤。流程图能够直观地展示算法的执行逻辑,便于深入理解和讨论。
实验总结
通过本次实验,我深入学习了启发式搜索和A
算法的基本原理,并通过具体问题的求解实践,加深了对算法性能和估价函数选择的理解。实验过程中,对搜索算法的性能指标进行比较分析,能够更好地评估算法的优劣。这次实验对我的深度学习和掌握算法设计的能力都有很好的提升。

当然,我来为你提供更具体的内容:

图遍历算法

广度优先搜索(BFS)

广度优先搜索是一种图遍历算法,它从起始节点开始,逐层地向外扩展,确保先访问距离起始节点更近的节点。具体步骤如下:

  1. 将起始节点放入队列。
  2. 从队列中取出一个节点,将其标记为已访问。
  3. 将该节点的未访问邻居节点加入队列。
  4. 重复步骤2和3,直到队列为空。

深度优先搜索(DFS)

深度优先搜索是一种图遍历算法,它从起始节点开始,沿着路径尽可能深入搜索,直到无法再深入为止,然后回退到上一个节点继续搜索。具体步骤如下:

  1. 将起始节点放入栈。
  2. 从栈中取出一个节点,将其标记为已访问。
  3. 将该节点的未访问邻居节点加入栈。
  4. 重复步骤2和3,直到栈为空。

优先队列

优先队列的基本概念

优先队列是一种特殊的队列,其中每个元素都有一个与之关联的优先级。在优先队列中,元素按照优先级进行排序,优先级高的元素先出队列。

优先队列的实现方式

常见的实现优先队列的方式有二叉堆、斐波那契堆等。其中,二叉堆是一种完全二叉树,可以用数组实现,它满足堆的性质,即父节点的优先级大于或等于子节点的优先级。

如何根据估价函数的评估结果选择合适的节点进行扩展

在启发式搜索中,根据估价函数的评估结果选择合适的节点进行扩展是关键。一般情况下,优先选择具有较低估计代价的节点进行扩展,以期望更快地接近目标节点。因此,在优先队列中,节点的优先级应该基于估价函数的评估结果确定。

估价函数的设计

估价函数的作用和目标

估价函数用于评估当前节点到目标节点的估计代价。它的作用是指导搜索算法选择扩展的节点,并为算法提供启发性信息,以期望更快地找到解决方案。估价函数的目标是提供一个合理的代价评估,使算法能够在搜索过程中朝着最有希望的方向前进。

启发式规则和经验知识的运用

设计估价函数常常依赖于具体问题领域的启发式规则和经验知识。通过分析问题特征和相关规律,可以制定一些启发式规则,从而提供有益的评估信息。这些规则可以基于问题的结构、先验知识、可行解的特征等方面。

如何设计合理的估价函数以指导搜索方向和搜索顺序

设计合理的估价函数需要考虑多个因素,如问题的特性、可行解的特点以及算法的要求等。合理的估价函数应该能够准确地估计节点到目标节点的代价,同时能够提供有用的启发信息,以便算法能够优先选择最有希望的节点进行扩展。以下是设计合理的估价函数的一些常见方法:

  • 直线距离估价:对于问题的状态空间,可以使用两点之间的直线距离作为估价函数的值。这适用于问题中有明确的空间关系,并且直线路径是最优路径的情况。

  • 曼哈顿距离估价:对于网格型问题,可以使用曼哈顿距离作为估价函数的值。曼哈顿距离是两点横向和纵向距离的总和,适用于只能沿着格子移动的情况。

  • 启发式规则估价:对于特定问题,可以基于问题的启发式规则和经验知识设计估价函数。这些启发式规则可以基于问题的特征、约束条件、可行解的特点等。通过分析问题的特点,设计合理的启发式规则可以提供更准确的估价函数。

启发式搜索算法的求解流程

启发式搜索算法的求解流程如下:

  1. 初始化搜索状态和起始节点。
  2. 创建一个优先队列,并将起始节点加入队列。
  3. 重复以下步骤直到找到目标节点或搜索队列为空:
    • 从优先队列中取出优先级最高的节点。
    • 如果该节点是目标节点,搜索完成,找到了解决方案。
    • 根据估价函数评估节点的好坏,选择合适的节点加入优先队列。
  4. 如果搜索队列为空,表示未找到目标节点,搜索失败。

以上流程通过不断选择和扩展具有更高优先级的节点,指导搜索方向和搜索顺序,以期望更快地找到解决问题的路径。

综上所述,启发式搜索涉及图遍历算法、优先队列和估价函数的设计。理解和应用这些知识将有助于更好地使用启发式搜索算法来解决问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/251794.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis知识详解(超详细)

1. 背景 Redis是由意大利人Antirez&#xff08;Salvatore Sanfilippo&#xff09;在2009年创造的开源内存数据结构存储系统。Redis的名字来自意大利语“Repubblica di Redis”&#xff0c;意思是“基于字典的共和国”。它是一个基于内存的键值对存储系统&#xff0c;具有快速、…

Leetcode 491 递增子序列

题意理解&#xff1a; 输入&#xff1a;nums [4,6,7,7] 输出&#xff1a;[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]] 这里不止要找一个子序列&#xff0c;还要元素保证其在原来的集合中的前后顺序&#xff0c;且应为增序。 为保证一个增序序列&#xff0c;…

移动端Vant中的Calendar日历增加显示农历(节日、节气)功能

核心&#xff1a; 使用 js-calendar-converter 库实现 npm地址&#xff1a;js-calendar-converter 内部使用原生calendar.js&#xff0c; 中国农历&#xff08;阴阳历&#xff09;和西元阳历即公历互转JavaScript库&#xff0c;具体实现感兴趣的可自行查看其实现源码。 原日…

【人工智能】实验四:遗传算法求函数最大值实验与基础知识

实验四&#xff1a;遗传算法求函数最大值实验 实验目的 熟悉和掌握遗传算法的原理、流程和编码策略&#xff0c;并利用遗传算法求解函数优化问题&#xff0c;理解求解流程并测试主要参数对结果的影响。 实验内容 采用遗传算法求解函数最大值。 实验要求 1. 用遗传算法求解…

扁平化菜单功能制作

网页效果&#xff1a; HTML部分&#xff1a; <body><ul class"nav"><li><a href"javascript:void(0);">菜单项目一</a><ul><li>子菜单项01</li><li>子菜单项02</li><li>子菜单项03<…

【C++】optional的使用(一)

这篇文章介绍下C17引入的std::optional 为什么要有 optional 一般来说&#xff0c;如果想要一个函数返回“多个”值&#xff0c;C程序员倾向于使用结构体/类完成这个操作。即定义一个通用的结构体&#xff0c;在函数内部完成装填&#xff0c;然后返回一个实例化的结构体。 #…

解决PP材质粘合问题用PP专用UV胶水

PP材料已经广泛应用于各行各业&#xff0c;在粘接中会有不同的问题需求&#xff0c;那么使用专用于PP的UV胶水可能是解决PP材质粘合问题的一种有效方法。 主要在于&#xff1a;UV胶水在紫外线照射下可以快速固化&#xff0c;形成坚固的连接。所以使用PP专用UV胶水时可以考虑&am…

oracle Job 定时任务

目录 介绍&#xff1a; 优点&#xff1a; 缺点&#xff1a; 使用场景&#xff1a; --查看定时任务 --查询存储过程 案例&#xff1a; --创建一个名为t_oracle_job的表 在创建定时任务时&#xff0c;各个参数的含义如下&#xff1a; 1. job_name&#xff1a…

day02-报表技术POI

1、基于模板导出列表数据 1.1、需求 按照以下样式导出excel 1.2、思路 首先准备一个excel模板&#xff0c;这个模板把复杂的样式和固定的内容先准备好并且放入到项目中&#xff0c;然后读取到模板后向里面放入数据。 1.3、实现 第一步&#xff1a;准备一个excel作为导出的…

【ArkTS】入门

代码结构分析 struct Index{ } 「自定义组件&#xff1a;可复用的UI单元」 xxx 「装饰器&#xff1a;用来装饰类结构、方法、变量」 Entry 标记当前组件是入口组件&#xff08;该组件可被独立访问&#xff0c;通俗来讲&#xff1a;它自己就是一个页面&#xff09;Component 用…

LeetCode-克服链表不能随机访问的问题

1.重排链表 题目描述&#xff1a; 给定一个单链表 L 的头节点 head &#xff0c;单链表 L 表示为&#xff1a; L0 → L1 → … → Ln - 1 → Ln 请将其重新排列后变为&#xff1a; L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → … 不能只是单纯的改变节点内部的值&#xff0…

解决vue3+ts打包,ts类型检查报错导致打包失败

最近拉的开源大屏项目goview&#xff0c;在打包的过程中一直报Ts类型报错导致打包失败&#xff0c;项目的打包命令为&#xff1a; "build": "vue-tsc --noEmit && vite build" 是因为 vue-tsc --noEmit 是 TypeScript 编译器&#xff08;tsc&#…

python 基于imageio_ffmpeg 直接操作ffmpeg,无需额外在官网下载!

python直接操作ffmpeg&#xff0c;无需在官网下载&#xff01; 一、前言 在要使用ffmpeg处理的时候&#xff0c;不想去官网下载ffmpeg然后添加到环境变量再使用。研究了一下&#xff0c;可以通过下面的方法解决 imageio_ffmpeg subprocess 二、具体步骤 1、环境配置 pip i…

报数游戏C语言

分析:掌握数字移动的规律&#xff0c;以及判断&#xff0c;我们可以用一个二维数组来记录每一个人说的数字&#xff0c;就像第一张图片一样&#xff0c;西安向右边移动&#xff0c;再向左下移动&#xff0c;再向左边移动&#xff0c;在向右边移动&#xff0c;在可以用一个数组来…

pytorch强化学习(1)——DQNSARSA

实验环境 python3.10 torch2.1.1 gym0.26.2 gym[classic_control] matplotlib3.8.0 numpy1.26.2DQN代码 首先是module.py代码&#xff0c;在这里定义了网络模型和DQN模型 import torch import torch.nn as nn import numpy as npclass Net(nn.Module):# 构造只有一个隐含层的…

Zabbix监控系统部署与管理

目录 zabbix介绍 zabbix构成 zabbix进程 环境 zabbix-server节点部署 安装zabbix服务 安装与配置数据库 修改zabbix-PHP时区 登录网页安装 ​编辑数据库Access denied故障 zabbix-agent节点部署 zabbix web管理 中文乱码问题 zabbix介绍 zabbix是⼀个基于 Web 界…

【人工智能】实验二: 洗衣机模糊推理系统实验与基础知识

实验二: 洗衣机模糊推理系统实验 实验目的 理解模糊逻辑推理的原理及特点&#xff0c;熟练应用模糊推理。 实验内容 设计洗衣机洗涤时间的模糊控制。 实验要求 已知人的操作经验为&#xff1a; “污泥越多&#xff0c;油脂越多&#xff0c;洗涤时间越长”&#xff1b;“…

如何使用ycsb工具对mongodb进行性能测试过程

测试环境&#xff1a; linux系统&#xff1a;Centos 7.2 ,版本&#xff1a;Red Hat 4.8.5-44) YCSB简介 ycsb是一款性能测试工具&#xff0c;用Java写的&#xff0c;并且什么都可以压&#xff0c;像是mongodb&#xff0c;redis&#xff0c;mysql&#xff0c;hbase&#xff0c;等…

JavaScript值类型和引用类型两道经典面试题

JavaScript值类型和引用类型两道经典面试题 题目1题目2 题目1 首先&#xff0c;小试牛刀&#xff0c;请看第一道题。 let a {x: 10 } let b a a.x 20 console.log(b.x)a {x: 30 } console.log(b.x) a.x 40 console.log(b.x);那么上述代码输出结果是多少呢&#xff1f; …

逻辑分析仪_使用手册

LA1010 1> 能干啥&#xff1f;2> 硬件连接3> 软件安装4> 参数设置4.1> 采样深度和采样率4.2> 添加协议解析器4.3> 毛刺过滤设置 1> 能干啥&#xff1f; 测量通信波形&#xff0c;并自动解析&#xff1b; 比如测量&#xff0c;UART&#xff0c;SPI&…