8数码问题-深搜-广搜

本文介绍了8数码问题在深度优先搜索(DFS)和广度优先搜索(BFS)中的应用。尽管BFS通常能找到最优解,但DFS可能会在指定深度内找到解但非最优解。在无法确定问题解深度的情况下,DFS可能出现三种情况:找到最优解、未找到解或找到非最优解。对于DFS未找到解的问题,没有直接解决方案,但可以通过试凑法调整递归深度。文中还提供了一个非递归DFS算法,并详细解释了如何计算hash值以确保唯一性。

8数码问题的广搜在网上可以找到代码,广搜一般可以找到最优解,但是深搜不一定能找到最优解。一般的深搜多是指定最大递归深度的深搜,一般情况下,问题解的深度很难确定。因此深搜会出现三种情况,一是找到最优解,二是在迭代深度内找不到解,三是找到解但不是最优解。第一种情况自然是最好的,不过较经常出现的是第三种,在指定迭代深度不适当的时候也会出现第二种情况。

在迭代深度内找不到解是最糟糕的一种情况,对这个问题没有什么好的解决办法。有时候我们可以用试凑法,多次测试来找到一个比较好的递归深度。对同一输入在不同迭代深度范围内的比较如下:

输入数据

6 2 8
0 1 4
7 3 5

对于上面的测试数据,广搜的搜索深度为17,是最优的结果。下面的表格中列出了深搜的搜索深度,可以看到深搜的搜索深度不是固定的。

最大深度172030100
求得解时深度17192997

在下面给出的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;
}

整个工程的源代码也可以到这里下载

http://download.csdn.net/detail/han6771306/7384227

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值