八數碼簡介
八數碼問題也稱為九宮問題。在3×3的棋盤,擺有八個棋子,每一個棋子上標有1至8的某一數字,不同棋子上標的數字不同樣。棋盤上另一個空格,與空格相鄰的棋子能夠移到空格中。要求解決的問題是:給出一個初始狀態和一個目標狀態,找出一種從初始轉變成目標狀態的移動棋子步數最少的移動步驟。所謂問題的一個狀態就是棋子在棋盤上的一種擺法。棋子移動后,狀態就會發生改變。解八數碼問題實際上就是找出從初始狀態到達目標狀態所經過的一系列中間過渡狀態。
求解八數碼問題要懂得的知識
1.康托展開,八數碼在交換的過程中狀態會改變,康托展開用於求出某一格局的狀態數。唐托展開公式:X=a[n](n-1)!+a[n-1](n-2)!+...+a[i](i-1)!+...+a[2]1!+a[1]*0!其中a[i]為當前未出現的元素中是排在第幾個(從0開始),並且0<=a[i]<i(1<=i<=n)
2.逆序數,通過求初始格局和目標格局逆序數,然后在比較兩者的逆序數的奇偶性是否相同,如果奇偶性相同,則可以從初始格局變到目標格局。否則,不可達。逆序數求法:假設有n個元素的排列,a[i]為排列中的元素(0<=i<n),求在排列中比a[i]小的數的和。比如有這么一個排列:54120,5后面比它小的數有3個(0不算,后同);4后面比它小的數有2個;1后面比它小的數有0個;2后面比它的小的數有0個;所以,該排列的逆序數=3+2+0+0=5.
3.在八數碼中0位置的數與它相鄰的上下左右的位置的數交換不會影響這個格局的逆序數的奇偶性。比如有以下格局:
4 | 6 | 7 |
---|---|---|
5 | 8 | 1 |
2 | 3 | 0 |
如果2這個數和5這個數交換就會導致格局的逆序數的奇偶性的變化,而如果0和1交換就不會導致奇偶性的變化。我們要保證移動前和移動后逆序數的奇偶性不改變,用一維數組來存儲格局,要注意索引。
廣度優先搜索求解八數碼問題代碼
#include <iostream>
#include <string>
#include <cstring>
#include <cmath>
#include <vector>
#include <queue>
#include <set>
using namespace std;
#define N 9
int jc[N + 1] = { 1,1,2,6,24,120,720,5040,40320,362880 };//0-9的階乘
typedef struct data
{
int arr[N];//格局
int hash;//存儲某一格局的哈希
int pos;//0當前位置
int step;//記錄步數
}Node;
int dir[4][2] = {
{0,1},
{1,0},
{0,-1},
{-1,0}
};
/************************************************************************/
// 函數名稱:cantor
// 函數描述:康托展開
/************************************************************************/
int cantor(int arr[N])
{
int i, j;
int sum = 0;
for (i = 0; i < N; i++)
{
int nmin = 0;
for (j = i + 1; j < N; j++)
{
if (arr[i] > arr[j])
nmin++;
}
sum += (nmin*jc[N - i - 1]);
}
return sum;
}
/************************************************************************/
// 函數名稱:swap
// 函數描述:數據交換
/************************************************************************/
void swap(int *arr, int i, int j)
{
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
/************************************************************************/
// 函數名稱:printArray
// 函數描述:打印數組,測試用
/************************************************************************/
void printArray(int * arr)
{
int i, j;
for (i = 0; i < N; i++)
{
if (i % 3 == 0)
cout << "\n";
cout << arr[i] << " ";
}
cout << endl;
}
/************************************************************************/
// 函數名稱:copyArray
// 函數描述:復制數組
/************************************************************************/
void copyArray(int src[N], int target[N])
{
int i;
for (i = 0; i < N; i++)
{
target[i] = src[i];
}
}
/************************************************************************/
// 函數名稱:bfs
// 函數描述:廣搜
/************************************************************************/
int bfs(int arr[N], int sHash, int tHash)
{
if (sHash == tHash)
return 0;
int i, j;
queue<Node> q;
set<int> setHash;
Node now, next;
copyArray(arr, now.arr);
int pos = 0;
for (i = 0; i < N; i++)
{
if (arr[i] == 0)
break;
pos++;
}
now.hash = sHash;
now.step = 0;
next.step = 0;
now.pos = pos;
q.push(now);
setHash.insert(now.hash);
while (!q.empty())
{
now = q.front();
q.pop();
for (i = 0; i < 4; i++)
{
int offsetX = 0, offsetY = 0;
offsetX = (now.pos % 3 + dir[i][0]);
offsetY = (now.pos / 3 + dir[i][1]);
if (offsetX >= 0 && offsetX < 3 && offsetY < 3 && offsetY >= 0)
{
copyArray(now.arr, next.arr);//每次換方向,就復制
next.step = now.step;
next.step++;
swap(next.arr, now.pos, offsetY * 3 + offsetX);
next.hash = cantor(next.arr);
next.pos = (offsetY * 3 + offsetX);
int begin = setHash.size();
setHash.insert(next.hash);
int end = setHash.size();
if (next.hash == tHash) {
return next.step;
}
if (end > begin)
{
q.push(next);
}
}
}
}
return -1;
}
/************************************************************************/
// 函數名稱:inversion
// 函數描述:求逆序數
/************************************************************************/
int inversion(int arr[N])
{
int sum = 0;
for (int i = 0; i < N; ++i)
{
for (int j = i + 1; j < N; ++j)
{
if (arr[j] < arr[i] && arr[j] != 0)//不與0比較
{
sum++;
}
}
}
return sum;
}
int main(int argc, char **argv)
{
int i, j;
string s = "123456780";
string t = "123456078";
int is[N], it[N];//源int數組和目標int數組
for (i = 0; i < 9; i++)
{
if (s.at(i) >= '0'&&s.at(i) <= '8')
{
is[i] = s.at(i) - '0';
}
else
{
is[i] = 0;
}
if (t.at(i) >= '0'&&t.at(i) <= '8')
{
it[i] = t.at(i) - '0';
}
else
{
it[i] = 0;
}
}
int sHash, tHash;//源哈希和目標哈希
sHash = cantor(is);
tHash = cantor(it);
int inver1 = inversion(is);//求初始格局的逆序數
int inver2 = inversion(it);//求目標格局的逆序數
if ((inver1 + inver2) % 2 == 0)
{
int step = bfs(is, sHash, tHash);
cout << step << endl;
}
else
{
cout << "無法從初始狀態到達目標狀態" << endl;
}
return 0;
}