C++ 使用A*算法解决八数码问题

主要过程:

  • 通过一个当前最好状态即best矩阵,移动0或者空白的位置,上下左右生成4个方向的子结点(如果0没有越界),把子结点加入到open表中,当前的best加入到closed表。然后在open表中找到fx=gx+hx最小的结点,将其作为当前最好状态,即best矩阵,生成其可能的子结点,直到找到目标状态。
  • 因为我在给子结点加入到open表中时使用插入排序,所以open表的第一个有效结点就是fx最小结点,即open->next结点

目标状态
1,2,3
8,0,4
7,6,5
初始状态
4,8,2
0,7,3
6,1,5

#include<iostream>
using namespace std;
#define M 3
#define N 3
int cc=0;
int id_counter=0;
void print_matrix(int src[M][N])
{//打印传递过来的矩阵
	for(int i=0;i<M;++i)
	{
		for(int j=0;j<N;++j)
		{
			cout<<src[i][j]<<" ";
		}
		cout<<endl;
	}
	cout<<"--------------"<<endl;
}
bool copy_matrix(int des[][N],int src[][N],int a)
{//复制src矩阵内容到des矩阵,a为数量
	int* des_ptr=(int*)des;
	int* src_ptr=(int*)src;
	for(int i=0;i<a;++i)
	{
		*(des_ptr+i)=*(src_ptr+i);
	}
	return true;
}
int cal_reverse_nums(int nums[M][N])
{//计算逆序奇偶性,用于快速判断该起始状态是否有解
    int result = 0;
    int nums_[M*N-1];
    int ptr=0;
    for(int i=0;i<M;++i)
    {
        for(int j=0;j<N;++j)
        {
			if(nums[i][j] == 0)
			{
				continue;
			}
            nums_[ptr++] = nums[i][j];
        }
    }
    for(int i=1;i<M*N;++i)
    {
        int local_sum=0;
        for(int j=0;j<i;++j)
        {
            if(nums_[i]<nums_[j])
                ++local_sum;
        }
        result += local_sum;
    }
    result &= 1;//做位运算,如果result是偶数则它的bit状态最后一位一定是0,反之为1
    return result;
}

struct loc
{//主要用于记录0的位置
	int x;
	int y;
};
class node
{//单个结点,用于存储新生成子节点及其gx hx
public:
	int matrix[M][N];//存储八数码新状态的矩阵
	int hx;//启发函数的值
	int gx;//到达当前结点付出的代价(这里代码为层数,从第一个结点生成到此子节点层数)
	node *next;//链表,用于链接其他结点,便于处理
	int id;//每个结点都有一个自身id,用于父亲结点检索
	int father;//当前结点的父节点id,通过此信息寻找到当前结点的父结点
	node()
	{//默认初始化,只有头结点用到了这个函数,因为头节点不需要存储任何八数码状态,只是用来保持链表
		id = id_counter;
		father = -1;
		memset(matrix,-1,M*N);
		hx = -1;
		next = NULL;
	}
	node(int m[M][N],int hx_,int gx_,int id_,int father_)
	{//每一个新八数码的状态会调用该初始化函数
		this->id = id_;
		this->father = father_;
		copy_matrix(matrix,m,M*N);
		this->hx = hx_;
		this->gx = gx_;
		next = NULL;
	}
};

class matrix
{//八数码类
private:
	int table_src[M][N];//size M*N 	//存储最初状态的矩阵
	int table_des[M][N];			//存储目标状态矩阵,用于对比
	int table_best_now[M][N];		//每次生成新结点后要从open表中找到fx=gx+hx最小的结点
		int id_matrix;	//当前最好结点的id
		int father;		//当前最好结点的父亲id
		int gx;			//当前结点的代价(层数)
	int move_dire[4][2];//下右上左遍历  //通过对0(或者空白)对座标进行+-操作实现上下左右移动
	loc blank_loc;		//存储0(空白)的位置
	node *open;			//open表(链表),用于存储已经生成但未处理的八数码状态
	node *closed;		//closed表,用于存储已经处理过的结点(即八数码状态)

	int cal_hx(int now[M][N])
	{//通过计算当前矩阵now和目标矩阵的不重合个数(即M*N-重合个数)来计算hx值,这里M*N主要为9
		//这里计算不重合个数的原因,因为每次处理的八数码状态fx=gx+hx要求最小,所以要求hx和gx都要小
		//而hx代表不重合个数,越小说明越接近目标函数
		int hx = 0;
		for(int i=0;i<M;++i)
		{
			for(int j=0;j<N;++j)
			{
				if(this->table_des[i][j] == now[i][j])
				{
					++hx;//计算重合个数
				}
			}
		}
		return M*N-hx;//总个数-重合个数=不重合个数
	}

public:
	matrix(int src[][N],int des[][N])
	{//初始化函数,存储目标矩阵des和初始矩阵src
		cout<<"des matrix:"<<endl;
			print_matrix(des);
		cout<<"src matrix:"<<endl;
			print_matrix(src);
		int direction[4][2]={{1,0},{0,1},{-1,0},{0,-1}};//方向矩阵,传递给move_dire用于移动0(空白)
		for(int i=0;i<4;++i)
		{
			for(int j=0;j<2;++j)
			{
				this->move_dire[i][j] = direction[i][j];//方向信息拷贝给move dire
			}
		}
		copy_matrix(table_src,src,M*N);//将传递的初始矩阵复制给类成员
		copy_matrix(table_des,des,M*N);//复制给类中的目的矩阵
		copy_matrix(table_best_now,src,M*N);//默认初始矩阵为当前最好的矩阵
		this->get_blank_loc();	//更新0的位置
		open = new node();		//生成open表表头,
		memset(open->matrix,-1,M*N); 
		closed = new node();	//生成closed链表表头
		memset(closed->matrix,-1,M*N);
		//把当前最好结点加入close表中
		node* new_node = new node(table_best_now,cal_hx(table_best_now),0,id_counter++,-1);
			this->id_matrix = new_node->id;
			this->father = new_node->father;
			this->gx = new_node->gx;
		closed->next = new_node;
	}
	~matrix()
	{//析构函数,delete掉new出来open表和closed表内存
		node*ptr = open;
		while(ptr != NULL)
		{
			node *to_del=ptr;
			ptr = ptr->next;
			delete to_del;
			to_del = NULL;
		}
		ptr = closed;
		while(ptr != NULL)
		{
			node *to_del=ptr;
			ptr = ptr->next;
			delete to_del;
			to_del = NULL;
		}
	}
	void get_blank_loc()
	{//取得空白格的位置
		for(int i=0;i<M;++i)
		{
			for(int j=0;j<N;++j)
			{
				if(this->table_best_now[i][j]==0)
				{
					this->blank_loc.x=i;
					this->blank_loc.y=j;
					return;
				}
			}
		}
	}
	bool compare_matrix(int src_mat[M][N],int des_mat[M][N])
	{//比较两个matrix是否相等
		for(int i=0;i<M;++i)
		{
			for(int j=0;j<N;++j)
			{
				if(src_mat[i][j] != des_mat[i][j])
				{
					return false;
				}

			}
		}
		return true;
	}
	bool get_children()
	{
		for(int i=0;i<4;++i)
		{//四个方向移动0(空白),生成2-4个新八数码状态
			int new_matrix[M][N]; //用来存储新生成的子节点
			this->get_blank_loc();//更新0的位置
			this->blank_loc.x += this->move_dire[i][0];//4次循环,每次代表一个新方向
			this->blank_loc.y += this->move_dire[i][1];
			if(this->blank_loc.x<0 || this->blank_loc.x>=N || this->blank_loc.y<0 || this->blank_loc.y>=M)
			{//移动之后越界的情况,什么都不做,准备开始下一个方向的移动
				// this->blank_loc.x -= this->move_dire[i][0];
				// this->blank_loc.y -= this->move_dire[i][1];
			}
			else
			{
				//因为新子节点都是由当前best移动生成,所以best赋值给newmatrix,然后对new matrix元素0进行移动
				copy_matrix(new_matrix,table_best_now,M*N);
				int num = new_matrix[blank_loc.x][blank_loc.y];//0和下一步位置的元素互换,即移动
				new_matrix[blank_loc.x-this->move_dire[i][0]][blank_loc.y-this->move_dire[i][1]] = num;
				new_matrix[blank_loc.x][blank_loc.y] = 0;
				//检查open和close链表中有没有这个新节点matrix ,如果有,舍弃这项,没有则继续
				//性能瓶颈:对比队列太长
				node* tempSearch = open->next;//指向open表第一个有效结点
				bool exist_before=false;//存储是否已经存在
				while(tempSearch!=NULL)
				{
					if(this->compare_matrix(new_matrix,tempSearch->matrix))
					{
						exist_before = true;
						break;
					}		
					tempSearch = tempSearch->next;
				}
				if(exist_before == true)
				{//open表中已存在,直接退出
					continue;
				}
				tempSearch = closed->next;//指向closed表第一个有效结点
				while(tempSearch!=NULL)
				{
					if(this->compare_matrix(new_matrix,tempSearch->matrix))
					{
							exist_before = true;
							break;
					}
					tempSearch = tempSearch->next;
				}
				if(exist_before == true)
				{//已经存在,直接丢弃
					continue;
				}
				++cc;//生成的新矩阵计数器,调试用
				//cout<<"newMatrix:"<<cc++<<endl;
				//print_matrix(new_matrix);
				//计算新matrix的hx值
				int new_hx = this->cal_hx(new_matrix);  //新子节点newmatrix的启发函数hx值
				int new_gx =this->gx + 1;				//其层数gx值
				//准备向open链表中插入子节点,按fx=gx+hx值从小到大顺序插入,便于找到最好的fx值结点
				node *ptr = open;
				if(ptr->next == NULL)
				{//链表为空
					node* node1 = new node(new_matrix,new_hx,new_gx,id_counter++,this->id_matrix);
					ptr->next = node1;
				}
				else
				{//插入排序
					while(ptr->next!=NULL && ptr->next->hx + ptr->next->gx <= new_hx + new_gx )
					{//插入
						ptr = ptr->next;
					}
					node* new_one = new node(new_matrix,new_hx,new_gx,id_counter++,this->id_matrix);
					new_one->next = ptr->next;
					ptr->next = new_one;
				}

			}
		}

		//open->next是当前最好结点,将其复制给best并替换,用这个新的best开始下一轮的生成子结点任务
		node* best = open->next;
			this->id_matrix = best->id;
			this->gx = best->gx;
			this->father = best->father;
		copy_matrix(table_best_now,best->matrix,M*N);
		open->next = best->next;
		best->next = closed->next;
		closed->next = best;//这里是不是应该delete掉这个独立出来的best?
		return true;
	}
	void get_sequence()
	{//找到了目标结点,通过closed表,根据fatherid向前寻找,并生成从初始结点到目标结点的路径,即seq_head链表
		node* seq_head = new node();
		node* ptr = closed;
		node* tmp;
		int seq_father = this->father;
		while(ptr != NULL)
		{
			if(ptr->id == seq_father)
			{//找到ptr的父亲结点
				//ptr = ptr->next;
				tmp = new node(ptr->matrix,ptr->hx,ptr->gx,ptr->id,ptr->father);
				tmp->next = seq_head->next;
				seq_head->next = tmp;
				seq_father = ptr->father;

				//ptr = closed->next;
			}
			else
			{
				ptr = ptr->next;
			}
		}
		ptr = seq_head->next;
		int step=0;
		while(ptr != NULL)
		{//打印完整的生成路线
		//	cout<<"id: "<<ptr->id<<"  father:"<<ptr->father<<endl;
			cout<<"step:"<<step++<<endl;
			print_matrix(ptr->matrix);
			ptr = ptr->next;
		}
		cout<<"step:"<<step<<endl;
		print_matrix(this->table_best_now);//打印最后一个,即目标结点
	
	}
	bool run()
	{//主要函数
		if(cal_reverse_nums(this->table_des) != cal_reverse_nums(this->table_src))
		{//快速无解判断
			cout<<"no result"<<endl;
			return false;
		}
		while(1)
		{
			if(this->compare_matrix(this->table_best_now,this->table_des))
			{//判断是否找到目标状态
				cout<<"find target, search:"<<cc<<" times"<<endl;
				this->get_sequence();
				//print_matrix(this->table_best_now);
				return true;
			}
			//没找到就生成best的四个方向移动的子结点
			this->get_children();
			// if(open->next == NULL) //这是一个注释掉的暴力无解判断,即open表空了,一般无解起始状态到open表空会花10分钟以上才能跑完
			// {
			// 	cout<<"no result"<<endl;
			// 	return false;
			// }
		}
		return false;
	}

};

int main()
{
/* 
dest matrix
1,2,3
8,0,4
7,6,5
source matrix
4,8,2 5 2 8
0,7,3 3 0 1
6,1,5 6 7 4
*/
	int dest_matrix[M][N]={{1,2,3},{8,0,4},{7,6,5}};//目的状态
	int src_matrix[M][N]={4,8,2,0,7,3,6,1,5};		//初始状态
	matrix demo(src_matrix,dest_matrix);
	demo.run();
	return 0;
}

附:测试截图
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章