C++八数码程序图形化界面[2023-02-02]
问题简介
八数码:是指在3x3的矩阵中,其中有8个格子放置成1-8,剩下一个格子是空格。能够移动和空格相邻的格子到空格,直到这个矩阵满足每一行依次从左到右读取是有序,得到最后得到1-8有序,最后一个格子是空格。下图展示了一个案例:
推广二维N×N的棋盘
对于任意大小的二维N×N的棋盘:
程序界面截屏
工程项目
部分代码(MFC)
UINT Ceight_puzzleDlg::BFSThread(LPVOID lParam)
{
Ceight_puzzleDlg* dlg;
dlg = (Ceight_puzzleDlg*)lParam;
int step;
char** path;
int initial[9];
int target[9];
bool complete;
path = new char*;
dlg->block_a->GetNum(initial);
dlg->block_b->GetNum(target);
dlg->engine = new BFS;
dlg->engine->Initialise(initial, target);
complete = dlg->engine->GetResult(&step, path);
if(complete == true)
{
CString str;
int buf[9];
dlg->block_a->GetNum(buf);
dlg->block_c->SetNum(buf);
dlg->block_c->DemoResult(step, *path);
str.Format(_T("BFS算法\n移动次数:%d"), step);
dlg->MessageBox(str, NULL, MB_ICONINFORMATION);
dlg->engine->ExportFile(_T("BFS"), step, *path);
}
else
{
dlg->MessageBox(_T("移动次数10000步内无解!"), NULL, MB_ICONINFORMATION);
}
free(dlg->engine);
return 0;
}
UINT Ceight_puzzleDlg::DFSThread(LPVOID lParam)
{
Ceight_puzzleDlg* dlg;
dlg = (Ceight_puzzleDlg*)lParam;
int step;
char** path;
int initial[9];
int target[9];
bool complete;
path = new char*;
dlg->block_a->GetNum(initial);
dlg->block_b->GetNum(target);
dlg->engine = new DFS;
dlg->engine->Initialise(initial, target);
complete = dlg->engine->GetResult(&step, path);
if (complete == true)
{
CString str;
int buf[9];
dlg->block_a->GetNum(buf);
dlg->block_c->SetNum(buf);
dlg->block_c->DemoResult(step, *path);
str.Format(_T("DFS算法\n移动次数:%d"), step);
dlg->MessageBox(str, NULL, MB_ICONINFORMATION);
dlg->engine->ExportFile(_T("DFS"), step, *path);
}
else
{
dlg->MessageBox(_T("移动次数10000步内无解!"), NULL, MB_ICONINFORMATION);
}
free(dlg->engine);
return 0;
}
UINT Ceight_puzzleDlg::A1Thread(LPVOID lParam)
{
Ceight_puzzleDlg* dlg;
dlg = (Ceight_puzzleDlg*)lParam;
int step;
char** path;
int initial[9];
int target[9];
bool complete;
path = new char*;
dlg->block_a->GetNum(initial);
dlg->block_b->GetNum(target);
dlg->engine = new A1;
dlg->engine->Initialise(initial, target);
complete = dlg->engine->GetResult(&step, path);
if (complete == true)
{
CString str;
int buf[9];
dlg->block_a->GetNum(buf);
dlg->block_c->SetNum(buf);
dlg->block_c->DemoResult(step, *path);
str.Format(_T("A*算法之不在位数码个数\n移动次数:%d"), step);
dlg->MessageBox(str, NULL, MB_ICONINFORMATION);
dlg->engine->ExportFile(_T("A"), step, *path);
}
else
{
dlg->MessageBox(_T("OPEN表长度10000内无解!"), NULL, MB_ICONINFORMATION);
}
free(dlg->engine);
return 0;
}
UINT Ceight_puzzleDlg::A2Thread(LPVOID lParam)
{
Ceight_puzzleDlg* dlg;
dlg = (Ceight_puzzleDlg*)lParam;
int step;
char** path;
int initial[9];
int target[9];
bool complete;
path = new char*;
dlg->block_a->GetNum(initial);
dlg->block_b->GetNum(target);
dlg->engine = new A2;
dlg->engine->Initialise(initial, target);
complete = dlg->engine->GetResult(&step, path);
if (complete == true)
{
CString str;
int buf[9];
dlg->block_a->GetNum(buf);
dlg->block_c->SetNum(buf);
dlg->block_c->DemoResult(step, *path);
str.Format(_T("A*算法之曼哈顿距离\n移动次数:%d"), step);
dlg->MessageBox(str, NULL, MB_ICONINFORMATION);
dlg->engine->ExportFile(_T("ASTAR"), step, *path);
}
else
{
dlg->MessageBox(_T("OPEN表长度10000内无解!"), NULL, MB_ICONINFORMATION);
}
free(dlg->engine);
return 0;
}
源码
https://pan.baidu.com/s/1pq1Nwwo0hlc_J84F93HM4A?pwd=1111
如何判断问题是否有解?
结论
先说结论:
一个状态表示成一维的形式,求出:除0之外所有数字的逆序数之和,也就是每个数字前面比它大的数字的个数的和,称为这个状态的逆序。
若两个状态的逆序奇偶性相同,则可相互到达,否则不可相互到达。
N是奇数时,当且仅当当前棋盘的逆序对是偶数的时候有解。
N是偶数时,当且仅当当前棋盘的逆序对数加上空格所在的行(行数从0开始算)是奇数的时候有解。
证明
根据棋局的逆序对定义,不论N为奇数或偶数,空格在同一行的左右移动不会修改逆序对数的奇偶性;
不论N为奇数或偶数,空格上下移动,相当于跨过N-1个格子,那么逆序的改变可能为±N-1,±N-3,±N-5 …… ±N-2k-1。
此时,若N为偶数,空格上下移动,逆序对数的奇偶性必然改变:比如N=4时,当上下移动的时候,相当于一个数字跨过了另外三个格子,它的逆序可能±3或±1。
若N为奇数,空格上下移动,逆序对数的奇偶性保持不变:比如N=3时,当上下移动的时候,相当于一个数字跨过了另外三个格子,它的逆序可能±2或0。
所以:
当N为奇数时,空格上下左右移动都不改变奇偶性,当前棋盘的逆序对与目标状态逆序对的奇偶性相同时有解,当八数码问题中,目标状态的逆序对数为0(偶数),所以“N是奇数时,当且仅当当前棋盘的逆序对是偶数的时候有解”。
N为偶数时,空格每上下移动一次,奇偶性改变。称空格位置所在的行到目标空格所在的行步数为空格的距离(不计左右距离),若两个状态的可相互到达,则有,两个状态的逆序奇偶性相同且空格距离为偶数,或者,逆序奇偶性不同且空格距离为奇数数。否则不能。也就是说,当此表达式成立时,两个状态可相互到达:(状态1的逆序数 + 空格距离)的奇偶性==状态2奇偶性。空格距离=N-空格所在行数。
计算逆序对
可以利用归并排序时的“合并操作”来统计逆序对:
双指针i=0,j=0分别指向左半部分left和右半部分right的开始;
判断left[i]和right[j]的大小:
如果left[i]<=right[j],没有逆序对,i+=1;
如果left[i]>right[j],逆序对数为mid-i+1,j+=1;
解法
广度优先遍历穷举:让空格不断和周围位置交换,直到换到棋局变成目标棋局。
A star启发式穷举:优先在队列中从有可能更快达到目标棋局的棋局继续穷举。
广度优先遍历(bfs)
让空格不断和周围位置交换,交换后的棋局加入队列,注意使用哈希集合防止遍历重复的棋局,广度优先遍历结束的树高就是步数。
A star(A*)
A星算法是一种具备启发性策略的算法,优先在队列中从有可能更快达到目标棋局的棋局继续穷举。
更有可能达到目标棋局的当前棋局得分通过设置代价函数实现,为:已有的代价+未来的代价估计(可以使用曼哈顿、汉明距离等进行度量)。
所以只需要在计算当前棋局的代价,使用优先级队列,优先从代价较小的棋局继续穷举,就可能更快到达目标棋局。