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;
}

附:測試截圖
在這裏插入圖片描述

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