前言
本文中将会详细描述有关函数递归的知识,并会使用扫雷中的递归运用来进一步说明使用递归中的关键部分。
基础知识将会参考《C primer Plus》中的相关内容以及案例。
一、递归
C语言允许函数调用自己,这种调用过程称为递归(recursion)。递归有时难以捉摸,有时缺很方便使用。结束递归是使用递归的难点,因为递归代码中如果没有中止递归的条件测试部分,一个调用自己的函数会无线递归。
可以使用循环的地方通常可以使用递归。有时用循环解决问题比较好,但有时用递归更好。
递归的方案更简洁,但是效率缺没有循环高。(例如求第N个斐波那契数)
1、递归的演示
我们通过一个简单代码来看看递归是如何实现的:
int multiply(int A, int B){
if(A > 0)
return B + multiply(A-1,B);
else
return 0;
}
上述代码时通过加法运算来完成 A 和 B乘法运算的递归代码.
首先我们先分析以下这个递归函数的运行过程:
分析上面的流程图可以分析出该递归函数中A>0为递归的条件,当不满足递归的条件时就会结束递归开始返回参数。
2.递归的基本原理
1、每级别的函数调用都用自己的变量。也就是说第一级的参数A和第二级别的参数A不同。
所以在上面演示的程序中一共出现了4个相同名称的变量’A‘但是他们的值各不相同。
当程序返回到第一次调用的程序时,变量A的值依然还是他的初值3。
2、每次函数调用都会返回依次。当函数执行完毕后,控制权将被传回上一级递归。程序必须按顺序逐级返回。
3、递归函数中位于递归调用之前的语句,均按被调函数的顺序执行。比如:第一级、第二级、第三级。
4、递归函数中位于递归调用之后的语句,均按被调函数相反的顺序执行。比如:第三级、第二级、第一级。
5、虽然每级别递归都有自己的变量,但是并没有拷贝函数的代码。程序按照顺序顺序执行函数中的代码,而递归调用就相当于从头开始执行函数的代码。
6、递归函数必须包括能让递归调用停止的语句,并且每一级的递归都会接近停止的条件。
3.递归的优缺点
递归既有优点也有缺点。优点是递归为某些编程问题提供了最简单的解决方案。缺点是一些递归算法会快速消耗计算机的内存资源。另外,递归不方便阅读和维护。
二、扫雷中递归的运用
下图是扫雷游戏中递归的逻辑:
当你选择到一个无雷区域时,他会自动扫描周围的区域,并将无雷区域标志出来。
我自己的扫雷游戏代码如下:
void ClearSafeZone(char board[ROWS][COLS], char mineBoard[ROWS][COLS], int x, int y, int row, int col)
{
int count = 0;
int i = 0;
int j = 0;
//检查参数是否合法
if (x < 1 || y > col || x > row ||y < 1)
return;
//检查目标位置周围的坐标 横纵坐标 -1 到 1
for (i = -1; i <= 1; i++)
{
for (j = -1; j <= 1; j++)
{
if (i == 0 && j == 0)
j = 1;
if ((x+i >= 1 && y+j <= col && x+i <= row && y+j >= 1))
{
if (mineBoard[x + i][y + j] == '0')
return;
count = GetCount(board, x+i, y+j, ROW, COL);
mineBoard[x+i][y+j] = count + 48;//将显示的地雷存放在另一个数组中,以区分地雷 1 和周围雷数的区别
//如果有雷,就跳过不在进行递归。
if (count != 0)
break;
如果没有雷,进入递归再次搜索
if (count == 0)
{
ClearSafeZone(board, mineBoard, x + i, y + j, ROW, COL);
}
}
}
}
//清除坐标附近的无雷区域
}
在扫雷中,递归的边界必须要严格把控,比如不能将自身的坐标算入递归。
在递归中超出棋盘边界的坐标需要判断排除.
下面是我的扫雷全部代码:
头文件的定义:棋盘的大小 雷的多少都在这里定义成常量
#game.h
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2//以防周围雷数边缘计算,增加一圈'0'(+2),实际棋盘为9*9
#define MINES 10
void game();
void InitBoard(char board[ROWS][COLS], char mineBoard[ROWS][COLS], int rows, int cols, int mine);
void DisplayBoard(char board[ROWS][COLS], int row, int col);
void FindMines(char board[ROWS][COLS], char mineBoard[ROWS][COLS], int row, int col,int* flag, int mine);
void InEnd(char board[ROWS][COLS], char mineBoard[ROWS][COLS], int row, int col, int* flag, int mine);
void ClearSafeZone(char board[ROWS][COLS], char mineBoard[ROWS][COLS], int x, int y, int row, int col);
扫雷游戏相关的函数
#game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void InitBoard(char board[ROWS][COLS], char mineBoard[ROWS][COLS], int rows, int cols, int mine)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = '0';
mineBoard[i][j] = '*';
}
}
for (i = 0; i < mine;)
{
int x = rand() % (rows -2);
int y = rand() % (cols -2);
if (board[x + 1][y + 1] == '0')
{
board[x + 1][y + 1] = '1';
mine--;
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i <= col; i++)
{
printf(" %d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("% d ", i);
for (j = 1; j <= col; j++)
{
printf(" %c ", board[i][j]);
}
printf("\n");
}
}
static int GetCount(char board[ROWS][COLS], int x, int y, int row, int col)
{
/*return (board[x][y - 1] +
board[x][y + 1] +
board[x - 1][y - 1] +
board[x - 1][y + 1] +
board[x + 1][y - 1] +
board[x + 1][y + 1] +
board[x + 1][y] +
board[x - 1][y])-48*8;*/
int i = 0;
int j = 0;
int count = 0;
for (i = -1; i <= 1; i++)
{
for (j = -1; j <= 1; j++)
{
if (x+i >= 1 && x+i <= row && y+j >= 1 && y+j <= col)
{
if (board[x + i][y + j] == '1')
{
count++;
}
}
}
}
return count;
}
void ClearSafeZone(char board[ROWS][COLS], char mineBoard[ROWS][COLS], int x, int y, int row, int col)
{
int count = 0;
int i = 0;
int j = 0;
if (x < 1 || y > col || x > row ||y < 1)
return;
for (i = -1; i <= 1; i++)
{
for (j = -1; j <= 1; j++)
{
if (i == 0 && j == 0)
j = 1;
if ((x+i >= 1 && y+j <= col && x+i <= row && y+j >= 1))
{
if (mineBoard[x + i][y + j] == '0')
return;
count = GetCount(board, x+i, y+j, ROW, COL);
mineBoard[x+i][y+j] = count + 48;//将显示的地雷存放在另一个数组中,以区分地雷 1 和周围雷数的区别
if (count != 0)
break;
if (count == 0)
{
ClearSafeZone(board, mineBoard, x + i, y + j, ROW, COL);
}
}
}
}
//清除坐标附近的无雷区域
}
void FindMines(char board[ROWS][COLS], char mineBoard[ROWS][COLS], int row, int col, int* flag, int mine)
{
int x = 0;
int y = 0;
int count = 0;
int endCount = 0;
while(1)
{
printf("请选择坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col )
{
if( board[x][y] != '1')
{
if (mineBoard[x][y] == '*')
{
endCount++;
}
if (endCount == (row * col - mine))
{
printf("游戏胜利!!!");
*flag = 0;
break;
}
count = GetCount(board, x, y, ROW, COL);
mineBoard[x][y] = count + 48;//将显示的地雷存放在另一个数组中,以区分地雷 1 和周围雷数的区别
printf("在坐标[%d %d]旁边有%d个地雷\n", x, y, count);
if (count == 0)
{
ClearSafeZone(board, mineBoard, x, y, ROW, COL);
}
DisplayBoard(mineBoard, ROW, COL);
}
else if (board[x][y] == '1')
{
DisplayBoard(mineBoard, ROW, COL);
printf("中雷了!!游戏结束!!\n");
*flag = 0;
break;
}
}
else
{
printf("坐标出错!!请重新选择");
}
}
}
//void InEnd(char board[ROWS][COLS], char mineBoard[ROWS][COLS], int row, int col, int* flag, int mine) //扫描两个棋盘找到已经选择过的坐标判断数量是否已经选择除地雷外的坐标
//{
// int i = 0;
// int j = 0;
// int count = 0;
// for (i = 1; i <= row; i++)
// {
// for (j = 1; j <= col; j++)
// {
// if (board[i][j] != '1' && mineBoard[i][j] != '*')
// {
// count++;
// }
// }
// }
// if (count == row * col - mine)
// {
// printf("游戏胜利!!!");
// *flag = 0;
// }
//}
void game()
{
int flag = 1;
int* pf = &flag;
char board[ROWS][COLS];
char mineBoard[ROWS][COLS];
InitBoard(board, mineBoard, ROWS, COLS, MINES);
//初始化棋盘
DisplayBoard(board, ROW, COL);
printf("\n===========================================\n");
DisplayBoard(mineBoard, ROW, COL);
//显示棋盘
while (flag)
{
//排查雷
FindMines(board, mineBoard, ROW, COL, pf, MINES);
/*InEnd(board, mineBoard, ROW, COL, pf, MINES);*/
}
}
主函数以及菜单
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("***************************\n");
printf("***** 1.game *****\n");
printf("***** 0.exit *****\n");
printf("***************************\n");
printf("请输入选择:>");
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do {
menu();
while(scanf("%d", &input) != 1)
{
scanf("%*s");
printf("请输入整数:>");
}
switch ((int)input)
{
case 1:
game();
break;
case 0:
break;
default:
printf("请输入正确选项!\n");
break;
}
} while (input);
return 0;
}