前言
这次小学期大作业给了几道编程题让我们自己选,其中就有一道求解八数字问题。刚拿到这个题的时候头疼得不行,还得用c语言写,大一才学了这么点东西哪能做得出来,哈希表,搜索代码一个没学。后来一想可能是想考察我们自主学习的能力?那就开整吧。
本文仅用于记录自己学习过程,仅模拟给和我同进度的同学讲解的语气,并非攻略教程,难免会有错误且代码写得烂,求轻喷。
思路参考:(8条消息) 如何通过编程解决华容道问题?_程序人生的博客-优快云博客
感谢大佬理清做题思路。
一、问题分析
先看看题目要求:
规则:8数码拼图,9宫格中随机放置数字0-8,0作为空位可与上下左右的数字交换,最终排列为:
012
345
678
后面还给了几个函数的函数头,略。
emmm这题干讲了,但又什么都没讲,自由发挥了属于是。
首先得有个棋盘,这个棋盘是自己生成还是输入呢?为了提高难度我选择了自己生成随机棋盘。
其次是需要的算法。华容道其实就像走迷宫问题,0就是我们当前所在位置,0在一个3x3的棋盘里进行上下左右移动,最终目的就是恢复成012345678这样的状态。这道题我们是想要求解所有可能的解还是只要一个解就行呢?我认为是求解出一个就行了,这个解最好是最优解(也就是步骤最少的解),那么胜利的法则就决定了:宽度优先搜索。
百度宽搜可得
广度优先搜索使用队列(queue)来实现,整个过程也可以看做一个倒立的树形:
1、把根节点放到队列的末尾。
2、每次从队列的头部取出一个元素,查看这个元素所有的下一级元素,把它们放到队列的末尾。并把这个元素记为它下一级元素的前驱。
3、找到所要找的元素时结束程序。
4、如果遍历整个树还没有找到,结束程序。
而我们不能只搜索,还要找出解并且把它打印出来。那么就可以参考链表的方法,每次搜索的时候创建一个结点,这个结点存储它的棋盘,以及它的上一步的结点和当前走的方向,就像链表一样把它们连接起来。这样一旦发现了解,只需要顺藤摸瓜,抓住成功的那个结点一个个往回拽出来即可。
那么接下来就是定义需要的数据结构。首先定义一个结构体保存棋盘,再定义一个结构体保存结点,接着是定义一个链表队列。
好像大功告成了?但还有一个问题,如果第一步往右走,第二步又往左走了,这不就回到原来的状态了吗,势必会造成出现许多不必要的步骤的情况。那么如何解决呢?把所有出现过的状态都存起来!用什么来存呢?反观我们这学期学的的基础的数据结构,如果用顺序表链表来存,显然是不行的。因为9宫格有多少种状态……9的9次方387420489种,虽然实际计算时不可能每种状态都经历一遍,但也是个不小的数字!以顺序表链表的搜索的时间复杂度来看,会非常慢。怎么办呢?我们知道顺序表的查询指定下标的时间复杂度是O(1),也就是知道下标就能立刻查询到它。如果我们用一种特殊的方法把要查询的数据转成一个下标值,然后把它存储到下标位置,是不是可以以O(1)的时间复杂度查询到出现过的棋盘状态呢?答案是可以,这种数据结构就叫哈希表。
好,链表队列,哈希表,我们的数据结构就这么决定了。
二、技术准备
首先定义结构体。链式队列的代码大家应该很容易写出来,这次新加了一个之前没接触过的哈希表的代码,其实很好理解,它就是一个有特殊功能,存储时较为特别的顺序表。
typedef struct{ //a来存储棋盘,i,j存储0的当前位置(别问我为什么用char数组,因为题目要求)
char a[N*N];
char i,j;
}G;
typedef struct node{
struct node *last; //存步骤中它的上一个结点
struct node *next; //存链表中它的下一个结点
int status; //存棋盘状态
int dir; //这一步走的方向
}Node;
typedef struct{
Node *head,*tail; //队列的队头和队尾指针
int size; //队列长度
}List;
typedef struct{
unsigned max_size; //哈希表最大容量capacity
unsigned cur_size; //哈希表当前容量
int* table; //存储用的数组
}hash;
接下来是他们的函数。
//队列部分
void init_list(List *lp){ //初始化队列
lp->head=(void*)0;
lp->tail=(void*)0