主要過程:
- 通過一個當前最好狀態即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;
}
附:測試截圖