html八数码问题求解过程的可视化,(转贴)数字拼图问题(八数码)求解过程动态演示...
一、题目说明:在一个3×3的九宫中有1-8这8个数及一个空格随机的摆放在
其中的格子里,如图1-1所示。现在要求实现这个问题:将其调整为
如图1-1右图所示的形式。调整的规则是:每次只能将与空格(上、
下、或左、右)相邻的一个数字平移到空格中。试编程实现这一问题的
求解。
(图1-1)
二、题目分析:这是人工智能中的经典难题之一,问题是在3×3方格棋盘中,放8格
数,剩下的没有放到的为空,每次移动只能是和相邻的空格交换数。程序自
动产生问题的初始状态,通过一系列交换动作将其转换成目标排列(如下图
1-2到图1-3的转换)。
(图1-2) (图1-3)
该问题中,程序产生的随机排列转换成目标共有两种可能,而且这两种
不可能同时成立,也就是奇数排列和偶数排列。可以把一个随机排列的数组
从左到右从上到下用一个一维数组表示,如上图1-2我们就可以表示成{
8,7,1,5,2,6,3,4,0}其中0代表空格。
在这个数组中我们首先计算它能够重排列出来的结果,公式就是:
∑(F(X))=Y,其中F(X)
是一个数前面比这个数小的数的个数,Y为奇数和偶数时各有一种解法。
上面的数组可以解出它的结果。
F(8)=0;
F(7)=0;
F(1)=0;
F(5)=1;
F(2)=1;
F(6)=3;
F(3)=2;
F(4)=3;
Y=0+0+0+1+1+3+2+3=10
Y=10是偶数,所以其重排列就是如图1-3的结果,如果加起来的
结果是奇数重排的结果就是如图1-1最右边的排法。
三、算法分析求解方法就是交换空格(0)位置,直至到达目标位置为止。图形表示就
是:
(图3-1)
要想得到最优的就需要使用广度优先搜索,九宫的所以排列有9!种,
也就是362880种排法,数据量是非常大的,使用广度搜索,需要记住每一个
结点的排列形式,要是用数组记录的话会占用很多的内存,可以把数据进行
适当的压缩。使用DWORD形式保存,压缩形式是每个数字用3位表示,这样就
是3×9=27个字节,由于8的二进制表示形式1000,不能用3位表示,
使用了一个小技巧就是将8表示为000,然后用多出来的5个字表示8所在的
位置,就可以用DWORD表示了。用移位和或操作将数据逐个移入,比乘法速度
要快点。定义了几个结果来存储遍历到了结果和搜索完成后保存最优路径。
类结构如下:
class CNineGird
{
public:
struct PlaceList
{
DWORD Place;
PlaceList* Left;
PlaceList* Right;
};
struct Scanbuf
{
DWORD Place;
int ScanID;
};
struct PathList
{
unsigned char Path[9];
};
private:
PlaceList *m_pPlaceList;
Scanbuf *m_pScanbuf;
RECT m_rResetButton;
RECT m_rAutoButton;
public:
int m_iPathsize;
clock_t m_iTime;
UINT m_iStepCount;
unsigned char m_iTargetChess[9];
unsigned char m_iChess[9];
HWND m_hClientWin;
PathList *m_pPathList;
bool m_bAutoRun;
private:
inline bool AddTree(DWORD place , PlaceList
*& parent);
void FreeTree(PlaceList*& parent);
inline void ArrayToDword(unsigned char *array
, DWORD & data);
inline void DwordToArray(DWORD data , unsigned
char *array);
inline bool MoveChess(unsigned char *array ,
int way);
bool EstimateUncoil(unsigned char *array);
void GetPath(UINT depth);
public:
void MoveChess(int way);
bool ComputeFeel();
void ActiveShaw(HWND hView);
void DrawGird(HDC hDC , RECT clientrect);
void DrawChess(HDC hDC , RECT clientrect);
void Reset();
void OnButton(POINT pnt , HWND hView);
public:
CNineGird();
~CNineGird();
};
计算随机随机数组使用了vector模板用random_shuffle(,)
函数来打乱数组数据,并计算目标结果是什么。代码:
void CNineGird::Reset()
{
if(m_bAutoRun) return;
vector vs;
int i;
for (i = 1 ; i < 9 ; i ++)
vs.push_back(i);
vs.push_back(0);
random_shuffle(vs.begin(), vs.end());
random_shuffle(vs.begin(), vs.end());
for ( i = 0 ; i < 9 ; i ++)
{
m_iChess[i] = vs[i];
}
if (!EstimateUncoil(m_iChess))
{
unsigned char array[9] = {1,2,3,8,0,4,7,6,5};
memcpy(m_iTargetChess , array , 9);
}
else
{
unsigned char array[9] = {1,2,3,4,5,6,7,8,0};
memcpy(m_iTargetChess , array , 9);
}
m_iStepCount = 0;
}
数据压缩函数实现:
inline void
CNineGird::ArrayToDword(unsigned
char *array , DWORD& data)
{
unsigned char night = 0;
for ( int i = 0 ; i < 9 ; i ++)
{
if (array[i] == 8)
{
night = (unsigned char)i;
break;
}
}
array[night] = 0;
data = 0;
data = (DWORD)((DWORD)array[0] << 29 | (DWORD)
array[1] << 26 |
(DWORD)array[2] << 23 | (DWORD)array[3] << 20 |
(DWORD)array[4] << 17 | (DWORD)array[5] << 14 |
(DWORD)array[6] << 11 | (DWORD)array[7]
<
(DWORD)array[8] <
array[night] = 8;
}
解压缩时跟压缩正好相反,解压代码:
inline void CNineGird::DwordToArray(DWORD
data ,
unsigned char
*array)
{
unsigned char chtem;
for ( int i = 0 ; i < 9 ; i ++)
{
chtem = (unsigned char)(data >> (32 - (i + 1) * 3)
& 0x00000007);
array[i] = chtem;
}
chtem = (unsigned char)(data & 0x0000001F);
array[chtem] = 8;
}
由于可扩展的数据量非常的大,加上在保存的时候使用的是DWORD
类型,将每一步数据都记录在一个排序二叉树中,按从小到大从左到有的排
列,搜索的时候跟每次搜索将近万次的形式比较快几乎是N次方倍,把几个
在循环中用到的函数声明为内联函数,并在插入的时候同时搜索插入的数据
会不会在树中有重复来加快总体速度。二叉树插入代码:
inline bool CNineGird::AddTree(DWORD place
,
PlaceList*&
parent)
{
if (parent == NULL)
{
parent = new PlaceList();
parent->Left = parent->Right = NULL;
parent->Place = place;
return true;
}
if (parent->Place == place)
return false;
if (parent->Place > place)
{
return AddTree(place , parent->Right);
}
return AddTree(place , parent->Left);
}
计算结果是奇数排列还是偶数排列的代码:
bool
CNineGird::EstimateUncoil(unsigned
char *array)
{
int sun = 0;
for ( int i = 0 ; i < 8 ; i ++)
{
for ( int j = 0 ; j < 9 ; j ++)
{
if (array[j] != 0)
{
if (array[j] == i +1 )
break;
if (array[j] < i + 1)
sun++;
}
}
}
if (sun % 2 == 0)
return true;
else
return false;
}
移动到空格位的代码比较简单,只要计算是否会移动到框外面
就可以了,并在移动的时候顺便计算一下是不是已经是目标结果,
这是用来给用户手工移动是给与提示用的,代码:
inline bool
CNineGird::MoveChess(unsigned
char *array , int way)
{
int zero , chang;
bool moveok = false;
for ( zero = 0 ; zero < 9 ; zero ++)
{
if (array[zero] == 0)
break;
}
POINT pnt;
pnt.x = zero % 3;
pnt.y = int(zero / 3);
switch(way)
{
case 0 : //up
if (pnt.y + 1 < 3)
{
chang = (pnt.y + 1) * 3 + pnt.x ;
array[zero] = array[chang];
array[chang] = 0;
moveok = true;
}
break;
case 1 : //down
if (pnt.y - 1 > -1)
{
chang = (pnt.y - 1) * 3 + pnt.x ;
array[zero] = array[chang];
array[chang] = 0;
moveok = true;
}
break;
case 2 : //left
if (pnt.x + 1 < 3)
{
chang = pnt.y * 3 + pnt.x + 1;
array[zero] = array[chang];
array[chang] = 0;
moveok = true;
}
break;
case 3 : //right
if (pnt.x - 1 > -1)
{
chang = pnt.y * 3 + pnt.x - 1;
array[zero] = array[chang];
array[chang] = 0;
moveok = true;
}
break;
}
if (moveok && !m_bAutoRun)
{
m_iStepCount ++ ;
DWORD temp1 ,temp2;
ArrayToDword(array , temp1);
ArrayToDword(m_iTargetChess , temp2);
if (temp1 == temp2)
{
MessageBox(NULL , "你真聪明这么快就搞定
了!" , "^_^" , 0);
}
}
return moveok;
}
在进行广度搜索时候,将父结点所在的数组索引记录在
子结点中了,所以得到目标排列的时候,只要从子结点逆向
搜索就可以得到最优搜索路径了。用变量m_iPathsize来记录
总步数,具体函数代码:
void CNineGird::GetPath(UINT depth)
{
int now = 0 , maxpos = 100 ;
UINT parentid;
if (m_pPathList != NULL)
{
delete[] m_pPathList;
}
m_pPathList = new PathList[maxpos];
parentid = m_pScanbuf[depth].ScanID;
DwordToArray(m_pScanbuf[depth].Place ,
m_pPathList[++now].Path);
while(parentid != -1)
{
if (now == maxpos)
{
maxpos += 10;
PathList * temlist = new PathList[maxpos];
memcpy(temlist , m_pPathList , sizeof(PathList)
* (maxpos - 10));
delete[] m_pPathList;
m_pPathList = temlist;
}
DwordToArray(m_pScanbuf[parentid].Place ,
m_pPathList[++now].Path);
parentid = m_pScanbuf[parentid].ScanID;
}
m_iPathsize = now;
}
动态排列的演示函数最简单了,为了让主窗体有及时刷新的机
会,启动了一个线程在需要主窗体刷新的时候,用Slee(UINT)
函数来暂停一下线程就可以了。代码:
unsigned __stdcall MoveChessThread(LPVOID
pParam)
{
CNineGird * pGird = (CNineGird *)pParam;
RECT rect;
pGird->m_iStepCount = 0;
::GetClientRect(pGird->m_hClientWin , &rect);
for ( int i = pGird->m_iPathsize ; i > 0 ; i --)
{
memcpy(pGird->m_iChess , pGird->m_pPathList[i].Path ,
9);
pGird->m_iStepCount ++;
InvalidateRect( pGird->m_hClientWin , &rect , false);
Sleep(300);
}
char msg[100];
sprintf(msg , "^_^ ! 搞定了!\r\n计算步骤用时%d毫秒"
, pGird->m_iTime);
MessageBox(NULL , msg , "~_~" , 0);
pGird->m_bAutoRun = false;
return 0L;
}
最后介绍一下搜索函数的原理,首先得到源数组,将其转换成DWORD型
,与目标比较,如果相同完成,不同就交换一下数据和空格位置,加入二叉树,
搜索下一个结果,直到没有步可走了,在搜索刚刚搜索到的位置的子位置,这样
直到找到目标结果为止,函数:
bool CNineGird::ComputeFeel()
{
unsigned char *array = m_iChess;
UINT i;
const int MAXSIZE = 362880;
unsigned char temparray[9];
DWORD target , fountain , parent , parentID = 0 ,
child = 1;
ArrayToDword(m_iTargetChess , target);
ArrayToDword(array , fountain);
if (fountain == target)
{
return false;
}
if (m_pScanbuf != NULL)
{
delete[] m_pScanbuf;
}
m_pScanbuf = new Scanbuf[MAXSIZE];
AddTree(fountain ,m_pPlaceList);
m_pScanbuf[ 0 ].Place = fountain;
m_pScanbuf[ 0 ].ScanID = -1;
clock_t tim = clock();
while(parentID < MAXSIZE && child < MAXSIZE)
{
parent = m_pScanbuf[parentID].Place;
for ( i = 0 ; i < 4 ; i ++) // 0 :UP , 1:Down
,2:Left,3:Right
{
DwordToArray(parent , temparray);
if (MoveChess(temparray,i)) //是否移动成功
{
ArrayToDword(temparray , fountain);
if (AddTree(fountain, m_pPlaceList)) //加入搜索数
{
m_pScanbuf[ child ].Place = fountain;
m_pScanbuf[ child ].ScanID = parentID;
if (fountain == target) //是否找到结果
{
m_iTime = clock() - tim;
GetPath(child);//计算路径
FreeTree(m_pPlaceList);
delete[] m_pScanbuf;
m_pScanbuf = NULL;
return true;
}
child ++;
}
}
} // for i
parentID++;
}
m_iTime = clock() - tim;
FreeTree(m_pPlaceList);
delete[] m_pScanbuf;
m_pScanbuf = NULL;
return false;
}
重要函数的介绍结束;下面是程序的运行结果和运算结果:
源代码:
文件:
9Ggird_src.zip
大小:
33KB
下载:
html八数码问题求解过程的可视化,(转贴)数字拼图问题(八数码)求解过程动态演示...相关推荐
- 多视图几何总结——基础矩阵、本质矩阵和单应矩阵的求解过程
多视图几何总结--基础矩阵.本质矩阵和单应矩阵的求解过程 多视图几何总结--基础矩阵.本质矩阵和单应矩阵的求解过程 1. 说明--其实求解过程大同小异 2. 单应矩阵求解过程 2.1 基于代数误差的线 ...
- 使用Muduo完成数独和八数码问题求解服务器
在剖析完Muduo网络库源码之后,我们试着完成一个高效的数独和八数码问题求解服务器. 先说说为什么要选择这两个问题?数独问题一直是陈硕老师很喜欢的问题,在muduo网络库中多次提到并有示例.八数码问题 ...
- 数据挖掘机器学习[七]---2021研究生数学建模B题空气质量预报二次建模求解过程:基于Stacking机器学习混合模型的空气质量预测{含码源+pdf文章}
相关文章: 特征工程详解及实战项目[参考] 数据挖掘---汽车车交易价格预测[一](测评指标:EDA) 数据挖掘机器学习---汽车交易价格预测详细版本[二]{EDA-数据探索性分析} 数据挖掘机器学习 ...
- 需求分析的过程是什么?_7大需求分析方法与5大分析过程
面对业务部门层出不穷的需求,如何入手进行需求分析?有没有需求分析的标准方法论可供参考?以下就是为大家推荐的8大类需求分析方法: 流程图 原型 用例图 用户故事(3C原则) 词汇表 实体关系图ERD 分 ...
- 单片机按键控制数码管c语言程序,基于单片机的按键控制LED数码管共阴极动态显示电路设计报告(毕业论文).doc...
基于单片机的按键控制LED数码管共阴极动态显示电路设计报告(毕业论文) 物理与电子工程学院2014级课程设计 PAGE IV 物理与电子工程学院 <单片机原理与接口技术> 课程设计报告书 ...
- 51单片机数码管滚动显示学号_数协微课 | LED数码管与51单片机应用
遇见工学,学在工学 停课不停学 数协电子组教学微课 它又来了 让我们开始学起来吧! 本期学习内容 LED数码管的结构与工作原理及相关原理图介绍 51单片机的应用:静态数码管显示.动态数码管显示 LED ...
- 深度学习笔记(五) 代价函数的梯度求解过程和方法
作为自己的笔记系列,方便自己查阅和理解. 1)什么是梯度 梯度 本意是一个向量(矢量) 当某一函数在某点处沿着该方向的方向导数取得该点处的最大值,即函数在该点处沿方向变化最快,变化率最大(为该梯度的模 ...
- 三阶魔方自动求解及动态可视化matlab代码
三阶魔方自动求解及动态可视化matlab代码 思路与步骤 总结 思考 参考链接 源代码 第一次写博客,想总结分享下以前做过的一些有趣的东西,目的是为了回望过去与展望未来,同时为了提高自己的写作表达能力 ...
- 数值分析——三角矩阵排序向量及求解过程
三角矩阵排序向量及求解过程 问题 对于一个三角矩阵,如何得到排序向量 ppp ?给出算法并用程序实现它: 请实现三角系统的向前带入算法,给出程序并通过算例验证: 请给出三角系统的向后带入算法,给出程序 ...
最新文章
- 不同版本GCC编译器之间的切换
- Boost:不受约束的bimap双图的测试程序
- 2小时撸完代码之后,所有程序员都逃不过的一天... (强共鸣)
- 【C#】获取网页内容及HTML解析器HtmlAgilityPack的使用
- 如何将 Microsoft Bot Framework 链接至微信公共号
- DRF url控制 解析器 响应器 版本控制 分页(常规分页,偏移分页,cursor游标分页)...
- python 多进程 多核_go/node/python 多进程与多核cpu
- iPhone开发之self.的用法
- 【php】 php 的注释和结束符号之间的关系
- ExtJS6-项目创建
- [Bada开发]使用共享库
- 微积分专项----MIT GS老师
- 【哈士奇赠书活动 - 22期】-〖ChatGPT时代:ChatGPT全能应用一本通〗
- linux下搜索文件名,Linux系统中怎么搜索文件命令大全
- OaisimWithS1搭建笔记(2019.5)
- PowerShell自动加载账号密码
- mysql 聚簇索引 和聚簇索引 (二级索引)的 那些事
- HDU-1278-逃离迷宫
- 安装RHEL7.5超详细教程
- redhat linux中文,Redhat 中文解决方案