针对在校大学生的C语言入门学习——扫雷算法讨论
- 在上一篇文章《针对在校大学生的C语言入门学习——扫雷》我使用分治算法作为扫雷扩散的解决方案。分治算法一般都使用递归的写法。如果有些同学感觉递归实在是抽象不好理解,那么今天我使用广度遍历的算法作为解决方案给大家再做演示。
- 这次我只写算法,也就是上一篇文章中的open函数。其他部分代码不做任何修改。
队列
- 既然使用广度遍历,那我就不能免俗的使用队列。学习过计算机的同学应该都知道,队列是一种先进先出的数据结构。说的国际化一些就叫FIFO。这次我使用一个一维数组作为队列的内存。使用两个变量分别表示队列的头和队列的尾。头是用来出队的,尾是用来入队的。队列的每个元素是一对坐标。
typedef struct Coord
{
int x;
int y;
}Coord;
void open(int x, int y)
{
Coord queue[WIDTH*HEIGHT];
int begin = 0, end = 0;
}
- queue是我定义的数组,用来做队列的内存。begin和end分别表示队列的头和尾,分别用来做出队和入队的逻辑。我这个队列写的比较粗糙。数组的大小我取的是极限状态下的值,也就是屏幕的所有点数。
算法分析
- 所谓的广度遍历,就是以open函数的参数xy坐标位置为中心,一圈一圈的向外扩散。如何保证这一圈一圈的逐渐扩大呢?大家可以找一张纸画一画,我们是不是可以把第N圈的所有点入队,然后再逐个出队。根据FIFO的原则,先出的肯定就是第N圈中的点。如果出队的点周围雷数是0,就把它周围还没有被点开过的点入队。入队的点一定是第N+1圈中的点。这样当第N圈的点全部出队之后,就该出队第N+1圈的点。
- 可能还是有点懵懵的,结合我下面的代码就容易理解了。
void open(int x, int y)
{
Coord queue[WIDTH*HEIGHT];
int begin = 0, end = 0;
Coord c;
c.x = x;
c.y = y;
queue[end++] = c;
do{
Coord pc = queue[begin++];
int count = countMine(pc.x, pc.y);
map[pc.y][pc.x].show = count+'0';
if(count == 0)
{
}
}while(begin != end);
}
- do-while中的代码应该很熟悉,我们上一次使用分治算法的时候也写过同样的逻辑。只不过这次判断的点是从队列中获取的。既然使用广度遍历,那我们就要把所有需要遍历的点都放在队列中,然后一个一个出队做判断。begin和end相等说明队列已经空了,没有需要遍历的点,循环结束。countMine函数在上一篇文章已经封装完毕,这里没有任何修改。二维数组map也没做任何修改。
- 接下来就是周围的点入队的逻辑,在countMine函数中我们已经写好了考虑边界条件的遍历方式,这里可以直接复制。
void pushRound(Coord* queue, int* end, int x, int y)
{
int i,j;
for(i = (y==0?0:y-1);i <= (y==HEIGHT-1?HEIGHT-1:y+1);i++)
{
for(j = (x==0?0:x-1);j <= (x==WIDTH-1?WIDTH-1:x+1);j++)
{
if(map[i][j].show == '*')
{
Coord n;
n.x = j;
n.y = i;
queue[(*end)++] = n;
}
}
}
}
- 参数end必须定义成指针,因为在pushRound函数中要对open函数中的end变量进行修改。
- 这里有没有觉得这个双重for循环已经写过很多遍了,很烦?发现只是循环逻辑不一样而已,提醒大家可以使用函数指针传递逻辑,这里我就不做修改了。
- 接下来open函数中调用即可。
void open(int x, int y)
{
Coord queue[WIDTH*HEIGHT];
int begin = 0, end = 0;
Coord c;
c.x = x;
c.y = y;
queue[end++] = c;
do{
Coord pc = queue[begin++];
int count = countMine(pc.x, pc.y);
map[pc.y][pc.x].show = count+'0';
if(count == 0)
{
pushRound(queue, &end, pc.x, pc.y);
}
}while(begin != end);
}
- 扫雷就到这里吧,因为代码很少,就不给源码下载了。下一次是学生管理系统,代码量将远超扫雷。