是數據結構的課設(@.@)
以下爲全部代碼:
//編譯運行環境:VC++ 6.0
//win10 10.0.18362
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
long int fac[10] = { 1,1,2,6,24,120,720,5040,40320,362880 };//階乘表,康託展開定位位置時會用到
char step[363880] = { 0 }; // 9!=362880,記錄每一種情況是否被走過
char method[4][2] = { {-1,0},{0,1},{1,0},{0,-1} }; //4種移動方法
struct node {
char data[9];
int step_p;
int way;
};
struct node save_step[363880]; //保存移動數據,最多有 9!個移動數據
char endnode[9]; //終點座標
long int end_location; //記錄終點的location
long int locate(char s[], int n)
{
int i, temp;
long int num = 0;
for (i = 0; i < n; i++)
{
temp = 0;
for (int j = i + 1; j < n; j++)
if (s[j] < s[i]) //判斷幾個數小於它
temp++;
num += fac[n - i - 1] * temp;
}
return num;
}
//檢查移動後的情況
int check(int i, char data[])
{
int x, y;
long int num;
for (int j = 0; j < 9; j++)
if (data[j] == 0)
{
//計算空格移動後的座標
x = j % 3 + method[i][0];
y = j / 3 + method[i][1];
//判斷移動後的位置是否越界
if (x < 0 || x>2 || y < 0 || y > 2)
return 0;
//判斷移動後的位置是否已走過
data[j] = data[x + y * 3];
data[x + y * 3] = 0;
num = locate(data, 9);
if (step[num] == 1) //已被走過,也不是終點
{
return 0;
}
if (memcmp(endnode, data, 9) == 0) //是終點
{
return 2;
}
step[num] = 1;
return 1;
}
}
long int start = 0, end = 0;
long int bfs()
{
long int next_end = end; //next_end是走完下一步的位置,end是現在的位置
int flag;
char temp[9];
for (; start <= end; start++)
{
for (int i = 0; i < 4; i++) //4種走法,依次走
{
memcpy(temp, (char*)save_step[start].data, 9);
flag = check(i, temp); //檢查這一步走的結果
if (flag) //如果沒被走過,也沒越界
{
memcpy((char*)save_step[++next_end].data, temp, 9);
save_step[next_end].way = i; //記錄移動方向
save_step[next_end].step_p = start; //記錄步數位置
if (flag == 2) //找到終點
{
end_location = next_end; //記錄終點步數位置.
return 1; //找到終點,返回1,依次遞加,看一共遞歸了多少層
}
}
}
}
start = end + 1;
end = next_end;
return (1 + bfs());
}
int getmode(char num[])
{
int count;
for(int i=0; i<9; i++)
{
for(int j=i; j<9; j++)
{
if(num[i] > num[j])
count++;
}
}
count = count%2;
return count;
}
int solvableornot()
{
int startstate, endstate;
startstate = getmode(endnode);
endstate = getmode(save_step[0].data);
if(startstate == endstate)
return 1;
else return 0;
}
int main()
{
long int count = 0; //步數
system("color a");
//輸入
int i;
printf("請輸入起點九宮格:\n");
for (i = 0; i < 9; i++)
{
scanf("%1d", &endnode[i]);
}
printf("請輸入目標九宮格:\n");
for (i = 0; i < 9; i++)
{
scanf("%1d", &save_step[0].data[i]);
}
//判斷是否有解
int solvable = solvableornot();
if(!solvable)
{
printf("unsoloved\n");
exit(1);
}
//求解
count = bfs();
printf("共需要%d步\n依次爲:\n", count);
long int tempend=end_location;
int tempcount;
for (tempcount = 0; tempcount < count; tempcount++)
{
switch (save_step[tempend].way)
{
case 0:printf("右"); break;
case 1:printf("上"); break;
case 2:printf("左"); break;
case 3:printf("下");
}
tempend = save_step[tempend].step_p;
}
printf("\n按任意鍵開始動態演示");
system("pause >nul");
//依次輸出每一步
for (tempcount = 0; tempcount <= count; tempcount++)
{
system("cls");
printf("第%d/%d步\n", tempcount,count);
printf("\t+———————+\n");
printf("\t| %d | %d | %d |\n", save_step[end_location].data[0], save_step[end_location].data[1], save_step[end_location].data[2]);
printf("\t|———————|\n");
printf("\t| %d | %d | %d |\n", save_step[end_location].data[3], save_step[end_location].data[4], save_step[end_location].data[5]);
printf("\t|———————|\n");
printf("\t| %d | %d | %d |\n", save_step[end_location].data[6], save_step[end_location].data[7], save_step[end_location].data[8]);
printf("\t+———————+\n");
end_location = save_step[end_location].step_p;
printf("\n");
system("pause");
}
printf("演示結束,按任意鍵退出");
system("pause >nul");
return 0;
}
以下從主函數開始對每個部分進行詳細講解:
變量count用來存儲求解出的步數
system(“color a”);是終端命令,將字題顏色變成淡綠色
i用於循環
下面的兩個循環分別用於輸入起點和終點,(爲了顯示路徑方便)其中endnode是終點,save_step[0].data[]是起點。
save_step[]數組的定義可以在前面找到,這是一個結構體數組,用於存儲每一個步的信息。其中每一個數據存儲的內容包括:data[9]:此步的九宮格;step_p:此步的位置;way:此步的移動方向;
輸入結束後,判斷此九宮格問題通過移動是否可解:
solvable用於表示問題可解與否;
調用solvableornot()函數進行判斷;
solvableornot()的原理涉及到高等代數中排列的相關知識,詳細請參考:https://wenku.baidu.com/view/d68955a0aef8941ea76e05ed.html
在solvableornot()函數中,startstate變量表示起始點爲奇排列還是偶排列,endstate表示終點爲奇排列還是偶排列。分別經getmode()函數判斷後,根據排列的對換相關知識,若兩者相同,則有解,返回1。反之則無解,返回0;
回到主函數,無解則輸出無解並退出,有解則進入bfs()函數進行計算。
bfs()函數上面定義的start和end用於表示位置。函數中的next_end用於表示
第一層循環依次取需要遍歷但還未遍歷的步的位置,第二層循環走出上下左右這四步。
memcpy函數將走出一步前九宮格的內容傳遞給temp[];然後用check()函數判斷這一步的情況.
跟進check()函數;首先用x,y來表示移動後0(即空格)的座標,然後判斷此次移動是否出界。若出界,直接返回0,表示此次移動不合法。之後的幾行,判斷此位置是否已經走過了,
跟進locate函數;此函數使用康託展開,用於定位傳入的參數在所有0~8九個數字的排列組合中排的順序,也就是在step[]數組中應占的位置(step[]數組的定義可以在前面找到,用於表示對應位置是否已被訪問)。並將這個位置返回。
康託展開可參考:https://baike.baidu.com/item/%E5%BA%B7%E6%89%98%E5%B1%95%E5%BC%80/7968428?fr=aladdin
回到check()函數;判斷step[num]是否爲1,若爲1則表示該結點已被訪問過,此路徑可拋棄,並返回0;若未訪問過,則判斷此節點是否爲終點,是則返回2;不是則將該結點的標誌置1,表示已訪問,並返回1;
回到bfs()函數;若此次移動合法,則將此次移動後的信息記錄到存儲每一步信息的結構體數組save_step[]中;若找到終點,則返回1,每次遞歸都返回1,最終主函數中調用的bfs()就將返回遞歸的層數,也就是步數。
若不是終點,就將start和next_end都置爲下一個需要遍歷但還未遍歷的結點,然後遞歸調用bfs(),進行下一次移動。
最終找到終點,返回1,並在每一次返回時+1,最終返回到主函數,就是一共走的步數。
PS
step[]數組用於存儲對應的結點是否已經走過,由於0-8所有數的排列組合一共有9!種情況,所以要有9!個元素。
如果我有任何理解錯誤的地方,還請大佬不吝指出!
向大佬低頭.gif