九宫格重排问题 代码及通俗讲解

是数据结构的课设(@.@)
课设要求以下为全部代码:

//编译运行环境: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

猜你喜欢

转载自blog.csdn.net/qq_42851946/article/details/106769086