华 中 师 范 大 学 计 算 机 学 院
实 验 报 告 书
实验题目 : 八数码问题求解
课程名称 : 人工智能
主讲教师 : **
班 级 :
试验时间 :
1.问题描述:
八数码问题也称为九宫问题。在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格(以数字0来表示),与空格相邻的棋子可以移到空格中。
要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。
2.初始状态
1 0 3
7 2 4
6 8 5
3.目标状态
1 2 3,
8 0 4,
7 6 5
4.搜索策略
启发式搜索技术
(1) 原理:启发式搜索就是在状态空间中的搜索对每一个搜索的位置进行评估,得到最好的位置,再从这个位置进行搜索直到目标。这样可以省略大量无谓的搜索路径,提高了效率。在启发式搜索中,对位置的估价是十分重要的。采用了不同的估价可以有不同的效果。
(2) 估价函数
计算一个节点的估价函数,可以分成两个部分:
1、 已经付出的代价(起始节点到当前节点);
2、 将要付出的代价(当前节点到目标节点)。
节点n的估价函数定义为从初始节点、经过n、到达目标节点的路径的最小代价的估计值,即= +。
是从初始节点到达当前节点n的实际代价;
是从节点n到目标节点的最佳路径的估计代价,体现出搜索过程中采用的启发式信息(背景知识),称之为启发函数。
所占的比重越大,越趋向于宽度优先或等代价搜索;反之,的比重越大,表示启发性能就越强。
本实验中我们使用函数,其值是节点n与目标状态节点相比较,每个错位棋牌在假设不受阻拦的情况下,移动到目标状态相应位置所需走步(移动次数)的总和。显然比更接近于,因为不仅考虑了错位因素,还考虑了错位的距离。
5.算法
Begin:
读入初始状态和目标状态,并计算初始状态评价函数值f;
根据初始状态和目标状态,判断问题是否可解;
If(问题可解)
把初始状态假如open表中;
While(未找到解&&状态表非空)
①在open表中找到评价值最小的节点,作为当前结点;
②判断当前结点状态和目标状态是否一致,若一致,跳出循环;否则跳转到③;
③对当前结点,分别按照上、下、左、右方向移动空格位置来扩展新的状态结点,并计算新扩展结点的评价值f并记录其父节点;
④对于新扩展的状态结点,判断其是否重复,若不重复,把其加入到open表中;
⑤把当前结点从open表中移除;
End while
End if
输出结果;
End
6.源代码
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
#define Overflow 1
#define N 3
int goal[N][N]={1,2,3,8,0,4,7,6,5};
int zero[2],NodeQTY=0;
int *z=zero;//记录0的位置,zero[0]:r行;zero[1]:c列
typedef int Piece;
struct Chessboard{//棋盘信息
Piece pos[N][N];//记录每个数码a的位置r行c列
int d,f,move;//d:深度;f:启发函数值 ;move:父节点移动到该节点的方式
};
struct LNode{
Chessboard board;
LNode *parent,*next;
bool flag;
};
typedef LNode* List;
int* Findzero(LNode* &Node)
{
int i,j,zr[2];
int *z=zr;
for(i=0;i<N;i++){
for(j=0;j<N;j++){
if(Node->board.pos[i][j]==0){
zr[0]=i+1;
zr[1]=j+1;
break;
}
}
}
return z;
}
int pick(LNode *Node)
{
int w=0,i,j,ii,jj;
for(i=0;i<N;i++){
for(j=0;j<N;j++){
if(Node->board.pos[i][j]!=goal[i][j]&&Node->board.pos[i][j]!=0){
for(ii=0;ii<N;ii++)
for(jj=0;jj<N;jj++)
if(Node->board.pos[i][j]==goal[ii][jj]){
w=w+abs(ii-i)+abs(jj-j);
break;
}
}
}
}
return w;
}
LNode* extend(LNode *Node,int depth,int zero[2],int moveflag,int Choose)
{
LNode* NewNode=new LNode;
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
NewNode->board.pos[i][j]=Node->board.pos[i][j];
}
}
switch(moveflag)
{
case 1: //向左移,不能出界:zero[1]>=2
NewNode->board.pos[zero[0]-1][zero[1]-1]=NewNode->board.pos[zero[0]-1][zero[1]-2];
NewNode->board.pos[zero[0]-1][zero[1]-2]=0;
break;
case 2: //向右移,不能出界:zero[1]<=2
NewNode->board.pos[zero[0]-1][zero[1]-1]=NewNode->board.pos[zero[0]-1][zero[1]];
NewNode->board.pos[zero[0]-1][zero[1]]=0;
break;
case 3: //向上移,不能出界:zero[0]>=2
NewNode->board.pos[zero[0]-1][zero[1]-1]=NewNode->board.pos[zero[0]-2][zero[1]-1];
NewNode->board.pos[zero[0]-2][zero[1]-1]=0;
break;
case 4: //向下移,不能出界:zero[0]<=2
NewNode->board.pos[zero[0]-1][zero[1]-1]=NewNode->board.pos[zero[0]][zero[1]-1];
NewNode->board.pos[zero[0]][zero[1]-1]=0;
break;
}
NewNode->board.d=depth+1;
switch(Choose){
case 1:NewNode->board.f=NewNode->board.d+pick(NewNode);break;
}
NewNode->board.move=moveflag;
NewNode->parent=Node;
NodeQTY++;
return NewNode;
}
void InitList(LNode* &Open,LNode* &Close)
{
Open=(List)malloc(sizeof(LNode));
Close=(List)malloc(sizeof(LNode));
if(!Open&&!Close)
exit(Overflow);
Open->next=NULL;
Close->next=NULL;
}
int ListInsert(List &L,LNode* NewNode)
{
List p=L;
while(p->next){
p=p->next;
}
NewNode->next=p->next;
p->next=NewNode;
return true;
}
LNode* Getminf(List &L)
{
List p=L,q=L->next,r=L,min;
min=q;//p,q寻找f最小值的指针,r指向表L中min前一个元素
if(!q)
return NULL;
while(q)
{
if(min->board.f>q->board.f){
r=p;
min=q;
}
p=q;
q=q->next;
}
r->next=min->next;
min->next=NULL;
return min;
}
int main()
{
int i,j,choose;
List Open,Close;
LNode *Best,*current;
LNode *Start=new LNode;
printf("\t\t\t八 数 码 问 题 求 解\n");
printf("\n请输入初始状态:");
for(i=0;i<N;i++){
for(j=0;j<N;j++){
scanf("%d",&(Start->board.pos[i][j]));
}
}
printf("(注:The flag of movement--1:左移;2:右移;3:上移;4:下移)\n");
printf("初始棋盘状态:\n");
for(i=0;i<N;i++){
for(j=0;j<N;j++){
printf("|%d",Start->board.pos[i][j]);
}
printf("|\n");
}
InitList(Open,Close);
printf("A*算法:");
scanf("%d",&choose);
Start->board.d=0;
switch(choose){
case 1:Start->board.f=Start->board.d+pick(Start);break;
}
Start->board.move=0;
Start->parent=NULL;
Start->next=NULL;
Start->flag=1;
ListInsert(Open,Start);//将S加入到Open表中
while(Open->next){
Best=Getminf(Open);
ListInsert(Close,Best);
if(!(Best->board.f-Best->board.d)){
printf("$$$******有解!******$$$\n");
break;
}
z=Findzero(Best);
zero[0]=*(z+0);zero[1]=*(z+1);
if(zero[1]>=N-1&&Best->board.move!=2)
ListInsert(Open,extend(Best,Best->board.d,zero,1,choose));
if(zero[1]<=N-1&&Best->board.move!=1)
ListInsert(Open,extend(Best,Best->board.d,zero,2,choose));
if(zero[0]>=N-1&&Best->board.move!=4)
ListInsert(Open,extend(Best,Best->board.d,zero,3,choose));
if(zero[0]<=N-1&&Best->board.move!=3)
ListInsert(Open,extend(Best,Best->board.d,zero,4,choose));
}
printf("\t最佳路径如下所示:\n");
if(Open->next)
{
while(Best->parent){
Best->flag=1;
Best=Best->parent;
}
List p=Close->next,q=Close->next;
if(p==Start) q=p->next;
else exit(1);
int Step=0;
while(p&&q)//在Close表中依标记找到路径
{
if(q->flag==1&&q->parent==p){
printf("Step %d:0 move as the %d-flag of movement.\n",++Step,q->board.move);
for(i=0;i<N;i++){
for(j=0;j<N;j++){
printf("|%d",q->board.pos[i][j]);
}
printf("|\n");
}
p=q;//记住父节点
}
q=q->next;
}
printf("到达目标状态!\n");
}
else printf("该问题无法求解!\n");
}
7.实验心得
通过本次实验,我更加深入的了解了A*算法、启发函数以及搜索方法。启发式搜索有很多种方法,比如:局部择优搜索法,最好优先搜索法等等,其中也包括了A*算法,这些算法都使用了启发函数,但在具体的选取最佳搜索节点时的策略不同。A*是众多搜索方法中综合效果最好的。使用A*算法,也要懂得构建估价函数,不同的估计函数对实验结果影响很大。比如该实验,对于f(n)的考虑最简单的便是比较每个状态与目标状态相比错位的牌数。这个启发意味着如果其他条件相同,那么错位的牌数最少的状态可能最接近目标状态。然而这个启发没有使用从棋盘格局中可以得到的所有信息,因为它没有把牌必要的移动距离纳入考虑。一个“更好一点”的启发是对错位的牌必须要移动的距离求和,为了达到这个目的,每张牌必须要移动的每个方格算为一个距离单位。这两种启发都存在一种不足,就是没有认识到点到牌的难度。也就是说,如果两张牌是彼此相邻的,而且目标是要求互相颠倒它们的位置,那么要把它们放到适当的位置需要远不止两次的移动,因为各张牌必须相互绕来绕去。
昆明理工大学信息工程与自动化学院学生实验报告
( 20XX — 20XX 学年 第 1 学期 )
课程名称:人工智能 开课实验室:信自楼445
20XX年10月 25日
一、上机目的及内容
1.上机内容
用确定性推理算法求解教材65-66页介绍的八数码难题。
2.上机目的
(1)复习程序设计和数据结构课程的相关知识,实现课程间的平滑过渡;
(2)掌握并实现在小规模状态空间中进行图搜索的方法;
(3)理解并掌握图搜索的技术要点。
二、实验原理及基本技术路线图(方框原理图或程序流程图)
(1)设计并实现程序,求解出正确的解答路径;
(2)对所设计的算法采用大O符号进行时间复杂性和空间复杂性分析;
(3)对一般图搜索的技术要点和技术难点进行评述性分析。
三、所用仪器、材料(设备名称、型号、规格等或使用软件)
1台PC及VISUAL C++6.0软件
四、实验方法、步骤(或:程序代码或操作过程)
1、先创建项目,新建Source File文件:main.cpp。
#include <iostream>
#include "Node.h"
#include "Queue.h"
#include "Search.h"
#include "Tree.h"
void CreateNode1(std::vector<int>& s)
{
s.push_back(2);
s.push_back(8);
s.push_back(3);
s.push_back(1);
s.push_back(0);
s.push_back(4);
s.push_back(7);
s.push_back(6);
s.push_back(5);
}
void CreateNode4(std::vector<int>& d)
{
d.push_back(2);
d.push_back(8);
d.push_back(3);
d.push_back(1);
d.push_back(6);
d.push_back(4);
d.push_back(7);
d.push_back(0);
d.push_back(5);
}
void CreateNode8(std::vector<int>& d)
{
d.push_back(0);
d.push_back(2);
d.push_back(3);
d.push_back(1);
d.push_back(8);
d.push_back(4);
d.push_back(7);
d.push_back(6);
d.push_back(5);
}
void CreateNode20(std::vector<int>& d)
{
d.push_back(2);
d.push_back(0);
d.push_back(8);
d.push_back(1);
d.push_back(4);
d.push_back(3);
d.push_back(7);
d.push_back(6);
d.push_back(5);
}
void CreateNode27(std::vector<int>& d)
{
d.push_back(1);
d.push_back(2);
d.push_back(3);
d.push_back(8);
d.push_back(0);
d.push_back(4);
d.push_back(7);
d.push_back(6);
d.push_back(5);
}
void CreateNode_test1(std::vector<int>& d)
{
d.push_back(7);
d.push_back(6);
d.push_back(5);
d.push_back(4);
d.push_back(0);
d.push_back(1);
d.push_back(3);
d.push_back(8);
d.push_back(2);
}
void test_expand()
{
std::vector<int> s;
CreateNode1(s);
std::vector<int> d;
CreateNode4(d);
Node source(s);
Node dest(d);
source.Display();
Search search(&source);
std::cout << source.Expand(dest, search);
}
void test_search()
{
std::vector<int> s;
CreateNode1(s);
std::vector<int> d;
CreateNode4(d);
Node source(s);
Node dest(d);
source.Display();
dest.Display();
Search search(&source);
search.Find( & dest);
search.DisplayRoute();
}
void test_search2level()
{
std::vector<int> s;
CreateNode1(s);
std::vector<int> d;
CreateNode8(d);
Node source(s);
Node dest(d);
source.Display();
dest.Display();
Search search(&source);
search.Find( & dest);
search.DisplayRoute();
}
void test_search_lab1()
{
std::vector<int> s;
CreateNode1(s);
std::vector<int> d;
CreateNode27(d);
Node source(s);
Node dest(d);
source.Display();
dest.Display();
Search search(&source);
search.Find( & dest);
search.DisplayRoute();
}
int main(int argc, char** argv)
{
// test_expand();
// test_search();
// test_search2level();
// test_search_lab1();
std::vector<int> s;
CreateNode1(s);
std::vector<int> d;
CreateNode27(d);
Node source(s);
Node dest(d);
source.Display();
dest.Display();
Search search(&source);
search.Find( & dest);
search.DisplayRoute();
return 0;
}
2、新建Source File文件:Node.cpp
#ifndef PROGECT_1_NODE
#define PROGECT_1_NODE
#include <vector>
#include "Search.h"
enum OP
{
EMPTY,
UP,
DOWN,
LEFT,
RIGHT
};
bool IsOpposite(OP opa, OP opb);
class Node
{
public:
Node(std::vector<int> const& state);
bool Expand(Node const& destNode, Search& search);
void Display() const;
void DisplayRoute() const;
bool operator==(Node const& v) const;
private:
Node* CreateChild(OP op);
int FindEmptySpaceId() const;
std::vector<OP> GenerateLegalOperators(int spaceId) const;
int CalIdBasedOP(OP op, int spaceId) const;
bool IsWithinRange(OP op, int spaceId) const;
std::vector<int> m_state;
Node *m_pParent;
std::vector<Node*> m_children;
OP m_op;
};
#endif // PROGECT_1_NODE
3新建Heard File文件:node.h。
代码:#include <iostream>
#include <math.h>
#include "Node.h"
bool IsOpposite(OP opa, OP opb)
{
if (LEFT==opa && RIGHT == opb)
return true;
if (RIGHT==opa && LEFT == opb)
return true;
if (UP==opa && DOWN == opb)
return true;
if (DOWN==opa && UP == opb)
return true;
return false;
}
Node::Node(std::vector<int> const& state)
: m_state(state)
, m_pParent(NULL)
, m_children()
, m_op(EMPTY)
{
}
void ShowOP(OP op)
{
switch (op)
{
case EMPTY:
std::cout << "EMPTY";
break;
case UP:
std::cout << "UP";
break;
case DOWN:
std::cout << "DOWN";
break;
case LEFT:
std::cout << "LEFT";
break;
case RIGHT:
std::cout << "RIGHT";
break;
default:
exit(-1);
}
}
void ShowOPs(std::vector<OP> const& ops)
{
for (int id=0; id<ops.size(); ++id)
{
ShowOP(ops[id]);
std::cout << " ";
}
std::cout << std::endl;
}
bool Node::Expand(Node const& destNode, Search& search)
{
int spaceId = FindEmptySpaceId();
std::cout << "space is at " << spaceId << std::endl;
std::vector<OP> legalOPs = GenerateLegalOperators(spaceId);
ShowOPs(legalOPs);
while ( legalOPs.size() > 0 )
{
OP op = legalOPs[ legalOPs.size() - 1 ];
legalOPs.pop_back();
Node* pChild = CreateChild(op);
if ( *pChild == destNode )
{
search.SetDestPt(pChild);
return true;
}
search.GetQueue().EnQueue(pChild);
}
return false;
}
void Node::Display() const
{
for(int i=0; i<m_state.size(); ++i)
{
std::cout << m_state[i] << " ";
}
std::cout << std::endl;
std::cout << " pParent: " << m_pParent << std::endl;
std::cout << " op: ";
ShowOP(m_op);
std::cout << std::endl;
std::cout << " ";
for(int j=0; j<m_children.size(); ++j)
{
std::cout << m_children[j] << " ";
}
std::cout << std::endl;
}
void Node::DisplayRoute() const
{
std::vector<OP> routeOps;
Node const* pNode = this;
while ( NULL != pNode )
{
routeOps.push_back(pNode->m_op);
pNode = pNode->m_pParent;
}
for(int id=routeOps.size()-2; id>=0 ; --id)
{
ShowOP( routeOps[id] );
std::cout << " ";
}
std::cout << std::endl;
}
bool Node::operator==(Node const& v) const
{
for (int id=0; id<m_state.size(); ++ id)
{
if ( m_state[id] != v.m_state[id] )
return false;
}
return true;
}
Node* Node::CreateChild(OP op)
{
std::vector<int> childState = m_state;
int exchangePos1 = FindEmptySpaceId();
int exchangePos2 = CalIdBasedOP(op, exchangePos1);
int temp = childState[exchangePos1];
childState[exchangePos1] = childState[exchangePos2];
childState[exchangePos2] = temp;
Node* child = new Node(childState);
child->m_pParent = this;
child->m_op = op;
m_children.push_back(child);
return child;
}
int Node::FindEmptySpaceId() const
{
for (int id=0; id<m_state.size(); ++id)
{
if ( 0 == m_state[id] )
{
return id;
}
}
return -1;
}
std::vector<OP> Node::GenerateLegalOperators(int spaceId) const
{
std::vector<OP> allPossibleOps;
allPossibleOps.push_back(UP);
allPossibleOps.push_back(DOWN);
allPossibleOps.push_back(LEFT);
allPossibleOps.push_back(RIGHT);
std::vector<OP> ops;
for (int id=0; id<allPossibleOps.size(); ++id)
{
OP op = allPossibleOps[id];
if( IsOpposite(op, m_op) )
{
continue;
}
if ( IsWithinRange(op, spaceId) )
{
ops.push_back(op);
}
}
return ops;
}
int Node::CalIdBasedOP(OP op, int spaceId) const
{
switch (op)
{
case UP:
spaceId -= int( sqrt( m_state.size() ) );
break;
case DOWN:
spaceId += int( sqrt( m_state.size() ) );
break;
case LEFT:
--spaceId;
break;
case RIGHT:
++spaceId;
break;
default:
return -1;
}
return spaceId;
}
bool Node::IsWithinRange(OP op, int spaceId) const
{
spaceId = CalIdBasedOP(op, spaceId);
if (spaceId >= 0 && spaceId < m_state.size())
{
return true;
}
return false;
}
4、新建Source File文件:Queue.cpp,
代码:#include "Queue.h"
void Queue::EnQueue(Node* pNode)
{
m_queue.push_back(pNode);
}
Node* Queue::DeQueue()
{
if ( m_queue.size() == 0 )
{
return NULL;
}
Node* pNode = m_queue[0];
m_queue.pop_front();
return pNode;
}
5新建Heard File文件:Queue.h。
代码:
#ifndef PROGECT_1_QUEUE
#define PROGECT_1_QUEUE
#include <deque>
class Node;
class Queue
{
public:
void EnQueue(Node* pNode);
Node* DeQueue();
private:
std::deque<Node*> m_queue;
};
#endif // PROGECT_1_QUEUE
6、新建Source File文件:Search.cpp,
代码:
#include "Search.h"
#include "Node.h"
Search::Search(Node* root)
: m_queue()
, m_pDestNode( NULL )
{
m_queue.EnQueue(root);
}
Node* Search::Select()
{
return m_queue.DeQueue();
}
void Search::Find(Node* destNode)
{
bool isFound = false;
while ( !isFound )
{
Node* pNode = Select();
pNode->Display();
isFound = pNode->Expand(*destNode, *this);
}
}
void Search::DisplayRoute() const
{
m_pDestNode->DisplayRoute();
}
7新建Heard File文件:Search.h。
代码:
#ifndef PROGECT_1_SEARCH
#define PROGECT_1_SEARCH
#include "Queue.h"
class Node;
class Search
{
public:
Search(Node* root);
Queue& GetQueue()
{
return m_queue;
}
void Find(Node* destNode);
Node* Select();
void SetDestPt(Node* pDestNode)
{
m_pDestNode = pDestNode;
}
void DisplayRoute() const;
private:
Queue m_queue;
Node* m_pDestNode;
};
#endif // PROGECT_1_SEARCH
8、新建Source File文件:Tree.cpp,
代码:
#include "Tree.h"
9新建Heard File文件:Tree.h。
代码:
#ifndef PROGECT_1_TREE
#define PROGECT_1_TREE
#endif // PROGECT_1_TREE
五、实验过程原始记录( 测试数据、图表、计算等)
六、实验结果、分析和结论(误差分析与数据处理、成果总结等。其中,绘制曲线图时必须用计算纸或程序运行结果、改进、收获)
通过完成这次八数码难题试验报告,我对编程的理解更深刻了,以前做的很多编程仅仅是
几十行的一个函数的代码,而这次的工作量明显大了很多,需要构建几个
好多文件才能完成,在试验中虽然遇到很多的困难,但在老师同学的帮助下,还是学到了很多知识,这次的试验使我在以后的编程中,思路更加开阔了
利用人工智能技术解决八数码游戏问题1八数码游戏问题简介九宫排字问题又称八数码问题是人工智能当中有名的难题之一问题是在33方格盘上放…
实验一启发式搜索算法姓名徐维坚学号2220xx3484日期20xx629一实验目的熟练掌握启发式搜索A算法及其可采纳性二实验内容使…
华中师范大学计算机学院实验报告书实验题目:八数码问题求解课程名称:人工智能主讲教师:**班级:试验时间:1.问题描述:八数码问题也…
人工智能上机实验基于人工智能的状态空间搜索策略研究八数码问题求解一实验软件TC20或VC60编程语言或其它编程语言二实验目的1熟悉…
人工智能实验一题目实验一启发式搜索算法1实验内容使用启发式搜索算法求解8数码问题编制程序实现求解8数码问题A算法采用估价函数wnf…
人工智能上机实验基于人工智能的状态空间搜索策略研究八数码问题求解一实验软件TC20或VC60编程语言或其它编程语言二实验目的1熟悉…
实验一启发式搜索算法姓名尹鹏飞学号S20xx07031日期20xx1022一实验目的熟练掌握启发式搜索A算法及其可采纳性二实验内容…
基于人工智能的状态空间搜索策略研究八数码问题求解(一)实验软件TC2.0或VC6.0编程语言或其它编程语言(二)实验目的1.熟悉人…
昆明理工大学信息工程与自动化学院学生实验报告(20##20##学年第一学期)课程名称:人工智能导论开课实验室:年月日一、实验内容和…
昆明理工大学信息工程与自动化学院学生实验报告201201学年第一学期课程名称开课实验室年月日一实验内容八数码难题问题描述在33方格…
昆明理工大学信息工程与自动化学院学生实验报告(20XX20XX学年第1学期)课程名称:人工智能开课实验室:信自楼44520XX年1…