是数据结构的课设(@.@)
以下为全部代码:
//编译运行环境: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