8数码问题的广搜在网上可以找到代码,广搜一般可以找到最优解,但是深搜不一定能找到最优解。一般的深搜多是指定最大递归深度的深搜,一般情况下,问题解的深度很难确定。因此深搜会出现三种情况,一是找到最优解,二是在迭代深度内找不到解,三是找到解但不是最优解。第一种情况自然是最好的,不过较经常出现的是第三种,在指定迭代深度不适当的时候也会出现第二种情况。
在迭代深度内找不到解是最糟糕的一种情况,对这个问题没有什么好的解决办法。有时候我们可以用试凑法,多次测试来找到一个比较好的递归深度。对同一输入在不同迭代深度范围内的比较如下:
输入数据
6 2 8
0 1 4
7 3 5
对于上面的测试数据,广搜的搜索深度为17,是最优的结果。下面的表格中列出了深搜的搜索深度,可以看到深搜的搜索深度不是固定的。
| 最大深度 | 17 | 20 | 30 | 100 |
| 求得解时深度 | 17 | 19 | 29 | 97 |
在下面给出的8数码问题的深度求解算法中,采用非递归的深度优先搜索,并且给出了一个较合理的递归深度。对于代码中的hash值得求取,可能有好些朋友看不大懂,这里我简单讲解一些为什么这样计算hash值。
我们设 hash = a * 8! + b * 7! + c * 6! + d * 5! + e * 4! + f * 3! + g * 2! + h * 1! + i * 0! , 其中 a - i 分别是 0 - 8 的逆序数。到了这里,知道逆序数的朋友可以一下子就明白了。我们可以很容易的证明 a <= 8 , b < 8 , c < 7 ...... b < 8 则一定有 b * 7! < 8! 我们可以得到,如果 hash > 8! ( 即 (hash / 8! ) != 0 ) 则一定有 a != 0 ,且 hash / 8! = a,依次类推,我们可以证明 (hash % 8! ) / 7! = b ......这就是一个映射的过程,这个映射可以保证对于一组(a , b , c , d , e , f , g , h , i )一定有唯一的 hash 并且对于 一个 hash 一定有唯一的一组( a , b , c , d , e , f , h , i )相对应。
【EDigital.h】
#pragma once
#include <queue>
using std::queue;
#define HashTableSize 362881
#define DigitalSize 9
#define MaxDeepth 30 //深度优先搜索的最大搜索深度,对有些数据,其最优解可能超过30深度,这时候深搜,程序将不会给出正确结果
class EDigital
{
public:
typedef struct
{
int a[DigitalSize];
}Detail;
private:
typedef struct maps
{
char detail[DigitalSize];
int index; // 记录自己节点在hash表中的位置
char position; // 记录 空格(0)在序列中的位置
}Map, *PMap;
enum Direction
{
UP=0,DOWN=1,LEFT=2,RIGHT=3
};
public:
EDigital(const int a[ DigitalSize ]);
EDigital(const Detail& detail);
~EDigital(){}
public:
queue<Detail> FindPath();
void Bfs();
void Dfs ( int depth = MaxDeepth);
private:
inline int HashValue(Map& Parent , int direct );
void init(const Detail& detail);
private:
int EndIndex;
Map org;
static const int Factorial[ DigitalSize ];//{40320 , 5040 , 720 , 120 , 24 , 6 , 2 , 1 , 1 }; //8!,7!,6!,5!,4!,3!,2!,1!,0!
static const int derection[ 4 ];//{ -3 , 3 , -1 , 1 } ;// 可移动的四个方向,向上下移动空格(0)位置变化3,左右移动变化1
static int hashTable[ HashTableSize ];//{0}
};【EDitital.cpp】
#include "EDigital.h"
#include<iostream>
#include<queue>
#include<stack>
const int EDigital::Factorial[ 9 ] = { 40320, 5040, 720, 120, 24, 6, 2, 1, 1}; //8!,7!,6!,5!,4!,3!,2!,1!,0!
const int EDigital::derection[ 4 ] = { -3, 3, -1, 1} ;// 可移动的四个方向,向上下移动空格(0)位置变化3,左右移动变化1
int EDigital::hashTable[HashTableSize] = { 0 };
EDigital::EDigital(const int a[ DigitalSize ])
{
Detail detail;
for(int i=0;i<DigitalSize;++i)
detail.a[i]=a[i];
init(detail);
}
EDigital::EDigital(const Detail& detail)
{
init(detail);
}
void EDigital::init(const Detail& detail)
{
for( int i = 0 ; i < DigitalSize ; ++ i )
{
org.detail[ i ]=detail.a[ i ];
if( org.detail[ i ] == 0 )
org.position = i ;
}
int sum = 0 ;
for( int i = 0 ; i < DigitalSize ; ++ i ) //计算奇排列 &\ 偶排列
{
if( 0 == org.detail[ i ])
continue;
for( int j = 0 ; j < i; ++ j )
sum += ( 0 != org.detail[ j ] && org.detail[ j ] < org.detail[ i ] );
}
int index = 0 ;
for( int i = 0 ; i < DigitalSize ; ++ i )
// 计算初始状态的hash值
{
int count = 0 ;
for( int j = 0 ; j < i ; ++ j )
count += org.detail[ j ] > org.detail[ i ] ;//逆序数
index += Factorial[ org.detail[ i ] ] * count; //hash索引
}
org.index = index + 1 ;
EndIndex = sum%2 ? 161328:322561; // 目标状态的hash值,八数码存在无解的情况 123456780的hash值为322561
}
/**
*hash值的计算
*Parent:父状态的hash值
*direct:移动的方向
**/
inline int EDigital::HashValue(Map& Parent , int direct )
{
int i = Parent.position ;
int newindex = Parent.index ;
char *p = Parent.detail;
switch(direct)
{
case UP :
{
newindex -= 3*40320;
newindex += ( p[ i - 2 ] > p[ i - 3 ]) ? ( Factorial[ p[ i - 3 ] ] ) : ( - Factorial[ p[ i - 2 ] ] );
newindex += ( p[ i - 1 ] > p[ i - 3 ]) ? ( Factorial[ p[ i - 3 ] ] ) : ( - Factorial[ p[ i - 1 ] ] );
break;
}
case DOWN :
{
newindex += 3*40320;
newindex -= ( p[ i + 2 ] > p[ i + 3 ]) ? ( Factorial[ p[ i + 3 ] ] ) : ( - Factorial[ p[ i + 2 ] ] );
newindex -= ( p[ i + 1 ] > p[ i + 3 ]) ? ( Factorial[ p[ i + 3 ] ] ) : ( - Factorial[ p[ i + 1 ] ] );
break;
}
case LEFT : return newindex - 40320; break;
case RIGHT : return newindex + 40320; break;
}
return newindex;
}
/**
*
* 广度优先搜索
*
**/
void EDigital::Bfs()
{
using std::queue;
queue<Map> Queue;
Queue.push(org);
hashTable[ org.index ] = -1;
while( ! Queue.empty() )
{
Map node = Queue.front();
if( node.index == EndIndex ) return ;
Queue.pop();
for(int k =0 ; k < 4; k ++ )
{
Map tmp = node;
tmp.position = node.position + derection[k];
if(tmp.position < 0 || tmp.position > 8 || ( k > 1 && tmp.position / 3 != node.position /3 ) )
continue;
tmp.index = HashValue( node , k );
if(0 != hashTable[tmp.index] ) continue;//已经存在该状态
tmp.detail[ node.position ] = tmp.detail[ tmp.position ] ;
// 移动空格
tmp.detail[ tmp.position ] = 0 ;
hashTable[tmp.index] = node.index;
// 状态记录到hashtable中
Queue.push( tmp );
}
}
return ;
}
/**
*
* 深度优先搜索
*
**/
void EDigital::Dfs( int depth /*= MaxDeepth*/)
{
using std::stack;
stack<Map> Stack;
stack<char> FStack; //标记栈
Stack.push( org );
FStack.push( 0 );
hashTable[ org.index ] = -1;
while( ! Stack.empty() )
{
Map node = Stack.top();
if( node.index == EndIndex ) //node即为当前结果
return ;
char count = FStack.top(); //扩展标记
FStack.pop();
if(Stack.size() > depth || count >= 4 ) //最大扩展30深度
{
Stack.pop();
hashTable[ node.index ] = 0;
}
else//扩展
{
FStack.push( count + 1 );
Map tmp = node;
tmp.position = node.position + derection[count];
if(tmp.position < 0 || tmp.position > 8 || ( count > 1 && tmp.position / 3 != node.position /3 ) ) //越界 或者 左右移动时不在同一层
continue;
tmp.index = HashValue( node , (int)count );
if(0 != hashTable[tmp.index] )
continue;//已经存在该状态
// 移动空格
tmp.detail[ node.position ] = tmp.detail[ tmp.position ] ;
tmp.detail[ tmp.position ] = 0 ;
// 状态记录到hashtable中
hashTable[tmp.index] = node.index;
Stack.push( tmp );
FStack.push( 0 );
}
}
if(Stack.empty())
hashTable[ EndIndex ] = -1;
return ;
}
/**
*
* 通过hash表中记录的进行查找路径
*
**/
queue<EDigital::Detail> EDigital::FindPath()
{
using std::vector;
vector<int> Stack;
Stack.push_back(EndIndex);
int nowindex = EndIndex;
while( -1 != hashTable[ nowindex ] )
{
Stack.push_back(hashTable[ nowindex ]);
nowindex = hashTable[ nowindex ];
}
int nixu[9];
Detail temp;
queue<Detail> result;
while( ! Stack.empty())
{
nowindex = Stack.back() - 1 ;
Stack.pop_back();
for( int i = 0 ; i < DigitalSize; i ++ ) // 计算出逆序
{
nixu[i] = nowindex / Factorial[ i ] ;
nowindex %= Factorial[ i ];
}
memset( temp.a , -1 , DigitalSize *sizeof(int));
for( int i = 0 ; i < 9 ; ++ i ) // 根据逆序计算排列
{
int j=0;
for( int k = nixu[i] ; j < DigitalSize ; ++ j )
{
if(temp.a[j] == -1 ) k --;
if( k < 0 ) break;
}
temp.a[j] = i ;
}
result.push(temp);
}
return result ;
} 【main.cpp】
#include<iostream>
#include"EDigital.h"
#include<queue>
#include<string>
#include<Windows.h>
using std::cout;
using std::cin;
using std::endl;
using std::string;
using std::queue;
int main()
{
cout << "------输入(例)------" << endl;
cout << "\t1 2 3" << endl;
cout << "\t4 5 6" << endl;
cout << "\t7 8 0" << endl;
cout << "--------------------" << endl;
EDigital::Detail detail;
for( int i = 0 ; i < 9 ; i ++ )
cin >> detail.a[ i ];
cout << "--------------------" << endl;
cout << "1. 深搜 2.广搜" << endl;
cout << "--------------------" << endl;
string op;
cin >> op;
EDigital eight(detail);
long time =GetTickCount();
if( op == "1" )
{
int depth;
cout << "输入迭代深度>";
cin >> depth ;
time = GetTickCount();
eight.Dfs( depth );
}
else
eight.Bfs();
cout << "计算用时:" << GetTickCount()-time << "MS\n";
queue<EDigital::Detail> details = eight.FindPath();
printf("共需: %d 步\n",details.size()-1);
getchar();
int count=0;
while( ! details.empty())
{
EDigital::Detail result = details.front();
details.pop();
for( int i =0 ; i < 9 ; i ++ )
{
cout.width(3);
cout << result.a[i];
if( 2 == i % 3 ) cout << ("\n");
}
if(0 != details.size() )
{
cout << "\n ↓ 第" << ++ count << "步\n";
getchar();
}
}
return 0;
}
整个工程的源代码也可以到这里下载
本文介绍了8数码问题在深度优先搜索(DFS)和广度优先搜索(BFS)中的应用。尽管BFS通常能找到最优解,但DFS可能会在指定深度内找到解但非最优解。在无法确定问题解深度的情况下,DFS可能出现三种情况:找到最优解、未找到解或找到非最优解。对于DFS未找到解的问题,没有直接解决方案,但可以通过试凑法调整递归深度。文中还提供了一个非递归DFS算法,并详细解释了如何计算hash值以确保唯一性。
9069

被折叠的 条评论
为什么被折叠?



