没有办法,迫于兴趣,只好对以前写过的八数码问题进行重写,这次重写(A*依旧使用原来的估价方式估价)着重考虑 数据结构 的, 来提高执行效率。
通过测试,现在的八数码,执行效率还不错。 在此,把代码提供出来,一起学习~~ 不过,我把其写在同一个.cpp中(虽然这么做,不合理,但是我还是这么做了~) 该程序中:1:采用棋盘压缩 2:hash表判重 3: 路径记录通过hashtable中回溯 4:优先队列 用堆实现
源代码如下(代码调试运行平台为:Microsoft Office 提供的 VC++6.0 编译器):
运行效果为:
为了便于复制代码(因此便有下面的了):
#include
#include
#include
#define HashTableSize 260441 // 562147 460387 400249 360323 260441
#define HeapSize 5000
#define MaxSteps 100
/*** 经过从 1 2 3 4 5 6 7 8 0 到 0 8 7 6 5 4 3 2 1 枚举测试,
各个素数(HashTableSize) 的 hash 查询次数和表使用率如下:
素数 平均查询次数 使用率
562147 1.22 32.3%
460387 1.28 39.4%
400249 1.36 45.3%
360323 1.41 50.4%
260441 1.75 70.0%
*/
/***
这个棋盘采用4个字节表示。
其中 空格, 以及对应的 1~ 8 的数字, 用三个位表示, 然后 用四个位表示 空格的位置
(这共花了 3 * 9 + 4 = 31 个位 )。
注意棋盘是按 从上到下,从左到右, 依次标上编号:
--------------
| 00 | 01 | 02 |
--------------
| 03 | 04 | 05 |
--------------
| 06 | 07 | 08 |
--------------
棋盘表示法:
000 : 1 , 001 : 2 , 010 : 3
011 : 4 , 100 : 5 , 101 : 6
110 : 7 , 111 : 8 , 000 : 空格.
注意: 有的这里 空格的位置 和 数码 1 的编码是一样,这并不矛盾, 因为实际上 空格编码可以删除,
因为 后面要用 4 个位记录空格位置。 那么为什么还给 空格编码呢? 这主要是对压缩棋盘的上下左右
移动更加方便。
例如: 在 对下 棋盘:
6 8 3
0 5 2
4 7 1
那么对应的Reduce_Map 编码是: 101 111 010 000 100 001 011 110 000 0011 0
--- --- --- --- --- --- --- --- --- ---- ---
6 8 3 空格 5 2 4 7 1 3 标志位
*/
/*** 棋盘数据结构 ****/
typedef struct reduce{
public:
unsigned a00: 3 , a01: 3 , a02: 3 ;
unsigned a03: 3 , a04: 3 , a05: 3 ;
unsigned a06: 3 , a07: 3 , a08: 3 ;
unsigned empty: 4 ; // 记录 空格的位置.
unsigned used: 1 ; // 在 HashTable 元素中该位用来表示,是否已经占了。
reduce():used(0){}
bool operator == (const reduce& a);
}Reduce_Map ;
/**棋盘的表示**/
typedef struct {
public:
unsigned __int8 Board[9];
unsigned __int8 empty ;
void print()
{
for(int i =0 ; i < 9 ; i ++ )
{
if(i%3 == 0 ) printf("/n");
printf("%5d",Board[i]);
}
}
}Map ;
/*****搜索的节点的表示******/
struct Nodes{
public:
Reduce_Map RMap ;
unsigned __int8 gx ;
unsigned __int8 fx ;
// unsigned short hx ; // hx = fx - gx ;
unsigned int parent ; // 父节点, 在hash表中的位置
unsigned int index ; // 该节点在hashtable中的位置
Nodes():gx(0),fx(0){}
void Evaluate(){}
};
/***栈:用来记录路径**/
struct stacks{
public:
__int8 length ;
Reduce_Map path[MaxSteps];
stacks():length(-1){}
void push(Reduce_Map& temp){ path[++length] = temp; }
Reduce_Map pop(){ return path[length--] ; }
}Stack;
/****hash表****/
struct hashtable{
Reduce_Map status ;
int PreIndex ; // 用来记录 该节点的父节点在HashTable中对应的位置。
}HashTable[HashTableSize];
/****优先队列******/
struct PriorityQueue
{
public:
Nodes Heap[HeapSize]; // 堆数组
int bear ; // 堆长度
PriorityQueue():bear(-1){}
Nodes pop();
void push(Nodes);
void print();
bool empty(){ return (bear== -1);}
}Queue;
void inline swap(Nodes& , Nodes&);
bool inline compare(const Nodes& , const Nodes&);
/*** 重载 == 运算符*****/
bool reduce::operator ==(const reduce& a)
{
static Reduce_Map temp ;
temp = a ;
temp.used = this->used ;
if( memcmp( this , &temp , 4 ) == 0 ) return true ;
return false ;
}
void PriorityQueue::push(Nodes temp)
{
Heap[++bear] = temp ;
int k = bear, p ;
while(k)
{
p = (k - 1 )>>1;
if( compare( Heap[k],Heap[p]) ) return ;
swap(Heap[k],Heap[p]);
k = p ;
}
return ;
}
Nodes PriorityQueue::pop()
{
int flage , k = 0 , p ;
swap(Heap[0],Heap[bear--]);
while( ( p = ( k << 1 ) ) < bear )
{
flage = 1;
if( ( (p + 1) < bear ) && compare( Heap[ p + 1 ] , Heap[ p + 2 ] ) ) flage ++ ;
if(compare( Heap[ p + flage ] , Heap[k] ) ) break ;
swap(Heap[ p + flage] , Heap[k]);
k = p + flage ;
}
return Heap[bear+1];
}
void PriorityQueue::print()
{
for(int i =0 ; i <= bear; i ++ )
printf("/tfx:%d hx:%d/t/n",Heap[i].fx , Heap[i].fx-Heap[i].gx);
printf("/n");
getchar();
}
/**方格中不同位置之间的距离**/
static unsigned __int8 Steps[9][9]={
{ 0 , 1 , 2 , 1 , 2 , 3 , 2 , 3 , 4 } ,
{ 1 , 0 , 1 , 2 , 1 , 2 , 3 , 2 , 3 } ,
{ 2 , 1 , 0 , 3 , 2 , 1 , 4 , 3 , 2 } ,
{ 1 , 2 , 3 , 0 , 1 , 2 , 1 , 2 , 3 } ,
{ 2 , 1 , 2 , 1 , 0 , 1 , 2 , 1 , 2 } ,
{ 3 , 2 , 1 , 2 , 1 , 0 , 3 , 2 , 1 } ,
{ 2 , 3 , 4 , 1 , 2 , 3 , 0 , 1 , 2 } ,
{ 3 , 2 , 3 , 2 , 1 , 2 , 1 , 0 , 1 } ,
{ 4 , 3 , 2 , 3 , 2 , 1 , 2 , 1 , 0 }
};
/**两中目标状态***/
static unsigned __int8 DlTarget[2][8]={
{ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 },
{ 0 , 1 , 2 , 5 , 8 , 7 , 6 , 3 }
};
static __int8 Derection[4] = { 1 , -1 , 3 , -3 } ;
//static char DeToChar[4] = {'r', 'l','d', 'u'} ;
/******目标状态********/
static unsigned __int8 Target[8] ;
bool inline compare(const Nodes& Nodes1, const Nodes &Nodes2)
{
if(Nodes1.fx > Nodes2.fx) return true ;
if(Nodes1.fx < Nodes2.fx) return false;
return(Nodes1.gx < Nodes2.gx);
}
void inline swap(Nodes& a , Nodes&b)
{
Nodes c = b ;
b = a ;
a = c ;
}
/*** 棋盘的压缩**/
inline void ReduceTheMap(Map& IMap , Reduce_Map& RMap )
{
IMap.Board[IMap.empty] ++ ;
RMap.a00 = IMap.Board[0] - 1 , RMap.a01 = IMap.Board[1] - 1 , RMap.a02 = IMap.Board[2] - 1;
RMap.a03 = IMap.Board[3] - 1 , RMap.a04 = IMap.Board[4] - 1 , RMap.a05 = IMap.Board[5] - 1;
RMap.a06 = IMap.Board[6] - 1 , RMap.a07 = IMap.Board[7] - 1 , RMap.a08 = IMap.Board[8] - 1;
RMap.empty = IMap.empty ;
IMap.Board[IMap.empty] -- ;
return ;
}
/*** 棋盘的解压**/
inline void UnreduceMap(Map& IMap , Reduce_Map& RMap)
{
IMap.Board[0] = RMap.a00 + 1 , IMap.Board[1] = RMap.a01 + 1 , IMap.Board[2] = RMap.a02 + 1 ;
IMap.Board[3] = RMap.a03 + 1 , IMap.Board[4] = RMap.a04 + 1 , IMap.Board[5] = RMap.a05 + 1 ;
IMap.Board[6] = RMap.a06 + 1 , IMap.Board[7] = RMap.a07 + 1 , IMap.Board[8] = RMap.a08 + 1 ;
IMap.empty = RMap.empty ;
IMap.Board[IMap.empty] -- ;
return ;
}
/*** 判断是否查找过了(使用双散列函数探测法),
函数返回该节点在hashtable中的位置,
如果返回值为:-1 表示已经存在
*/
int InsetHashTable( Reduce_Map RMap , int PreIndex = -1 )
{
unsigned int Code;
__asm // 汇编实现同字节不同类型值的赋值: Code = RMap
{
mov eax,dword ptr[RMap]
mov dword ptr[Code] ,eax
}
Code %= HashTableSize;
if(HashTable[Code].status.used == 0 ) // 如果used 为 0 ,表示 该处未被占用
{
HashTable[Code].status = RMap ;
HashTable[Code].status.used = 1 ;
HashTable[Code].PreIndex = PreIndex ; // 记录父节点在hashtable中的位置,便于回溯找路径
return Code ;
}
if( HashTable[Code].status == RMap ) return -1 ;
unsigned int temp = Code | RMap.empty; // 经过测试,这样简单操作后,使得二次探测碰撞减少了些~~
while(HashTable[Code].status.used == 1 ){
Code += temp ;
if(Code >= HashTableSize) Code -= HashTableSize ;
if( HashTable[Code].status == RMap ) return -1 ;
}
HashTable[Code].status = RMap ;
HashTable[Code].status.used = 1 ;
HashTable[Code].PreIndex = PreIndex ;
return Code ;
}
/***对 hx 的估价(使用的是: 每个数码到目标位置的距离 之和 ) ***/
inline void Evaluate(Nodes& one)
{
one.fx = - Steps[ one.RMap.empty ][ Target[ 0 ] ] ;
one.fx += Steps[ 0 ][ Target[ one.RMap.a00 ] ] + Steps[ 1 ][ Target[ one.RMap.a01 ] ] +
Steps[ 2 ][ Target[ one.RMap.a02 ] ] + Steps[ 3 ][ Target[ one.RMap.a03 ] ] +
Steps[ 4 ][ Target[ one.RMap.a04 ] ] + Steps[ 5 ][ Target[ one.RMap.a05 ] ] +
Steps[ 6 ][ Target[ one.RMap.a06 ] ] + Steps[ 7 ][ Target[ one.RMap.a07 ] ] +
Steps[ 8 ][ Target[ one.RMap.a08 ] ] ;
one.fx += one.gx ;
}
/****回溯找出路径***/
void FindFinalPath(unsigned int& ObjIndex )
{
int temp = ObjIndex ;
while(temp != -1 )
{
Stack.push(HashTable[temp].status);
temp = HashTable[temp].PreIndex;
}
return ;
}
void search(Nodes begin ){
Nodes one , now ;
int temp , flage ;
Queue.push(begin);
while( !Queue.empty() ){
now = Queue.pop();
if(now.gx == now.fx )
{
FindFinalPath(now.index);
return ;
}
for(int k =0 ; k < 4; k ++ )
{
temp = now.RMap.empty + Derection[k] ;
if(temp >=0 && temp< 9 && Steps[temp][now.RMap.empty] == 1 )
{
one.RMap = now.RMap ;
one.RMap.empty = temp;
__asm // 由于这里RMap 被定义为struct 类型, 因此移动很不方便,所以只好采用汇编了
{
; 把one.RMap.empty 空格的位置取出(即移动后 空格所在位置)
mov ecx , dword ptr[one.RMap]
shr ecx , 27 ; 0000001bH
;and ecx , 15 ; 0000000fH
; 把one.RMap中的要移动的数码取出,并把该位清零----“空格”~
imul ecx , 3
mov ebx , 7
shl ebx , cl
mov eax , ebx
and ebx , dword ptr[one.RMap] ; 把数码取出放在 ebx 中
shr ebx , cl
not eax
and dword ptr[one.RMap], eax ; 把该位清零----“空格”操作
; 把now.RMap.empty 空格的位置取出(即移动前,空格所在位置)
mov ecx , dword ptr[now.RMap]
shr ecx , 27 ; 0000001bH
;and ecx , 15 ; 0000000fH
; 把取出的数码移到空格位置上去
imul ecx , 3
shl ebx , cl
or dword ptr[one.RMap] , ebx
}
flage = InsetHashTable( one.RMap , now.index );
if( flage >= 0 )
{
one.gx = now.gx +1 ;
Evaluate(one);
one.parent = now.index ;
one.index = flage ;
Queue.push(one);
}
}
}
}
}
void input(Nodes& begin)
{
int i ,j ;
int sum =0 , k ;
Map temp ;
printf("请输入相应八数码的位置:/n");
for(i =0 ;i < 9 ; i ++ ){
scanf("%I8d",&temp.Board[i]);
if(temp.Board[i] == 0 )
{
k = i ;
continue;
}
for(j =0 ; j< i ; j ++ )
if(temp.Board[j] > temp.Board[i]) sum ++ ;
}
temp.empty = k ;
ReduceTheMap(temp,begin.RMap);
if(sum%2) memcpy(Target,DlTarget[1],8);
else memcpy(Target,DlTarget[0],8);
Evaluate(begin);
begin.index = InsetHashTable(begin.RMap);
return ;
}
void output(){
if(Stack.length <= 0 ) printf("已经是目标状态了,你这不是自找麻烦嘛!/n");
else
{
Map temp ;
printf("搞定!/t 供需 %d 步 , 步骤如下:/n",Stack.length );
for(int i =Stack.length ; i >= 0 ; i -- )
{
UnreduceMap( temp , Stack.pop());
temp.print();
getchar();
if(i) printf("/n/n---->/n");
}
}
printf("/n");
return ;
}
int main()
{
Nodes begin ;
input(begin);
long time = GetTickCount();
search(begin);
printf("计算耗时:%dMS/n",GetTickCount()-time);
output();
return 0 ;
}