数据结构—递归运用之八皇后问题(回溯算法)

1.回溯法

  • 回溯法,又被称为“试探法”。解决问题时,每进行一步,都是抱着试试看的态度,如果发现当前选择并不是最好的,或者这么走下去肯定达不到目标,立刻做回退操作重新选择。这种走不通就回退再走的方法就是回溯法

2.递归与回溯

  • 很多人认为回溯和递归是一样的,其实不然。在回溯法中可以看到有递归的身影,但是两者是有区别的。
  • 回溯法从问题本身出发,寻找可能实现的所有情况。和穷举法的思想相近,不同在于穷举法是将所有的情况都列举出来以后再一一筛选,而回溯法在列举过程如果发现当前情况根本不可能存在,就停止后续的所有工作,返回上一步进行新的尝试。
  • 递归是从问题的结果出发,例如求 n!,要想知道 n!的结果,就需要知道 n*(n-1)! 的结果,而要想知道 (n-1)! 结果,就需要提前知道 (n-1)*(n-2)!。这样不断地向自己提问,不断地调用自己的思想就是递归。
  • 回溯和递归唯一的联系就是,程序设计中可利用函数的活动对象保存回溯算法的状态数据,因此可以利用递归完成回溯算法。

3. 八皇后问题的由来

  • 八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言可以解决此问题。

4.问题的解决思想

  • 先来打印一个棋盘,这里举例用八行八列来说。首先,每一行就只有一个皇后,而且各行的皇后不能同时出现在一个米字型的表格中。 

数据结构—递归运用之八皇后问题(回溯算法)_第1张图片

  • 假如图中的 * 是一个皇后,那么所有粉色的圆圈都不能有皇后。 看图。 

数据结构—递归运用之八皇后问题(回溯算法)_第2张图片

这也是一种回溯算法。 下面来摆上代码,我来一步一步的解释代码。

  • 首先初始化棋盘,8*8的棋盘,再增加一圈,给最外圈加一圈 ‘#’。棋盘中全部赋值为空格。
#define N 8
static char board[N + 2][N + 2];

void init()
{
	int i = 0;
	int j = 0;

	for (i = 0; i < N + 2; i++)   // 给最外圈加一圈 ‘#’
	{
		
        board[0][i] = '#';
		board[N + 1][i] = '#';
		board[i][0] = '#';
		board[i][N + 1] = '#';
	}

	for (i = 1; i <= N; i++)   // 棋盘中全部赋值为空格。
	{
		for (j = 1; j <= N; j++)
		{
			board[i][j] = ' ';
		}
	}
}
  • 展示函数,用于展示所放的 ‘*’ ,用来展示棋盘。
void display()
{
	int i = 0;
	int j = 1;

	for (i = 0; i < N + 2; i++)
	{
		for (j = 0; j < N + 2; j++)
		{
			printf("%c", board[i][j]);
		}

		printf("\n");
	}
}
  • 检查函数,判断棋盘中那个位置能否放皇后,看某一个位置能否放皇后,需看3个方向,直到边界位置,如图:

数据结构—递归运用之八皇后问题(回溯算法)_第3张图片

  • 首先需要定义一个结构体
typedef struct _tag_Pos
{
	int ios;   // 行偏移量
	int jos;   // 列偏移量
}Pos;
  • 方向数组:static Pos pos[ ] = { {-1,-1},{-1,0},{-1,1} };
int check(int i, int j)
{
	int ret = 1;
	int p = 0;

	for (p = 0; p < 3; p++)   // 检查3个方向
	{
		int ni = i;
		int nj = j;

		while (ret && (board[ni][nj]) != '#')
		{
			ni = ni + pos[p].ios;
			nj = nj + pos[p].jos;

			ret = ret && (board[ni][nj] != '*');
		}
	}

	return ret;
}
  • 给棋盘中放 ‘*’ ,调用检查函数,看看是否可以放进去,如果哪一行都不能放,那么就调换上一行中的位置,(回溯算法),也用到了递归的思想。
void find(int i)
{
	int j = 0;

	if (i > N)
	{
		count++;    // 统计有多少种结果

		printf("Solution: %d\n", count);

		display();

		//getchar();
	}
	else
	{
		for (j = 1; j <= N; j++)
		{
			if (check(i, j))
			{
				board[i][j] = '*';

				find(i + 1);

				board[i][j] = ' ';  // 如果不能放,清空
			}
		}
	}
}
  • 最后附上所有源代码。
  • main.c
#include 
#include 

#define N 8

typedef struct _tag_Pos
{
	int ios;   // 行偏移量
	int jos;   // 列偏移量
}Pos;

static char board[N + 2][N + 2];
static Pos pos[] = { {-1,-1},{-1,0},{-1,1} };
static int count = 0;

void init()
{
	int i = 0;
	int j = 0;

	for (i = 0; i < N + 2; i++)   // 给最外圈加一圈 ‘#'
	{
		board[0][i] = '#';
		board[N + 1][i] = '#';
		board[i][0] = '#';
		board[i][N + 1] = '#';
	}

	for (i = 1; i <= N; i++)   // 棋盘中全部赋值为空格
	{
		for (j = 1; j <= N; j++)
		{
			board[i][j] = ' ';
		}
	}
}

void display()
{
	int i = 0;
	int j = 1;

	for (i = 0; i < N + 2; i++)
	{
		for (j = 0; j < N + 2; j++)
		{
			printf("%c", board[i][j]);
		}

		printf("\n");
	}
}

int check(int i, int j)
{
	int ret = 1;
	int p = 0;

	for (p = 0; p < 3; p++)   // 检查3个方向
	{
		int ni = i;
		int nj = j;

		while (ret && (board[ni][nj]) != '#')
		{
			ni = ni + pos[p].ios;
			nj = nj + pos[p].jos;

			ret = ret && (board[ni][nj] != '*');
		}
	}

	return ret;
}

void find(int i)
{
	int j = 0;

	if (i > N)
	{
		count++;    // 统计有多少种结果

		printf("Solution: %d\n", count);

		display();

		//getchar();
	}
	else
	{
		for (j = 1; j <= N; j++)
		{
			if (check(i, j))
			{
				board[i][j] = '*';

				find(i + 1);

				board[i][j] = ' ';  // 如果不能放,清空
			}
		}
	}
}

int main()
{
	init();

	find(1);

	system("pause");

	return 0;
}
  • 运行结果

数据结构—递归运用之八皇后问题(回溯算法)_第4张图片

  • 一共有92种解法

你可能感兴趣的:(数据结构)