本节主要讲八皇后问题的基本规则和递归回溯算法的实现以及具体的代码实现和代码分析。

转载请注明出处。http://write.blog.csdn.net/postedit/10813257

一、八皇后问题和递归回溯算法

1.八皇后是一个递归回溯算法的典型问题,问题的由来是这样的,在国际象棋中有8*8个位置,那么我们有8个皇后,我们要把8个皇后分别放在不同的行,不同的列和不同的对角线上,也就是说我们要让这8个皇后不能相互攻击。

2.八皇后问题最好的解决办法是回溯算法,回溯算法的基本思路如下:

①从问题的某一状态出发,搜索可以到达所有状态②当某个状态到达后,可向前回退,并继续搜索其他可达状态③当所有状态都到达后,回溯算法结束

二、八皇后问题的基本解决思路

1.我们首先对棋盘进行规定,为了方便查看我们将棋盘定义边框。因为原来真正可以安置皇后的位置是8*8,再加上棋盘的边框,那么我们就编程了10*10的棋盘了,这样我们可以来定义一个二维数组数组来描述棋盘,这里我们将描述棋盘的二维数组定义如下:

  1. board[10][10];

2.棋盘定义结束后,我们要打印出棋盘的边框,同时真正的8*8棋盘的位置是要空出来的,因为这个时候我们还没有进行回溯算法,所以我们根本不确定到底在哪个位置安放皇后。棋盘的初始化函数我们定义为init(),具体实现代码如下:

  1. void init ()
  2. {
  3.  
  4. int i = 0;
  5. int j = 0;
  6. for (i = 0; i < 10; i++)
  7. {
  8. board[0][i] = '#';
  9. board[i][0] = '#';
  10. board[i][9] = '#';
  11. board[9][i + 1] = '#';
  12. }
  13. for (i = 1; i <= 8; i++)
  14. {
  15. for (j = 1; j <= 8; j++)
  16. {
  17. board[i][j] = ' ';
  18. }
  19. }
  20. }

在棋盘的初始化代码中,我们注意点是:

  1. board[0][i] = '#';
  2. board[i][0] = '#';
  3. board[i][9] = '#';
  4. board[9][i] = '#';

这里我们实际上分别对二维数组的第0行的每一个位置置为‘#’,然后对二维数组的第0列的每一个元素置为‘#’,再对二维数组的第10列中的每一个元素置为‘#’,最后对二维数组的第十行中的每一个元素置为‘#’。

第二个for循环的作用就是把这个棋盘所在的二维数组中的其他元素都置为空,菜鸟就是菜鸟,我在手敲代码的时候居然忽略了数组的开闭空间问题,所以导致打印出错。

3.对棋盘初始化结束后,我们为了方便以后的测试,这里我们先写一下打印函数,打印函数用于打印数组的每一个元素,这里将打印函数定义为output。具体代码如下:

  1. void output()
  2. {
  3. int i = 0;
  4. int j = 0;
  5. for (i = 0; i < 10; i++)
  6. {
  7. for (j = 0; j < 10; j++)
  8. {
  9. printf ("%c", board[i][j]);
  10. }
  11. printf ("\n");
  12. }
  13. }

在打印函数中需要注意的是每一打印完数组中的一行内容以后,我们都要打印一个换行,要不然我们是绝对看不到棋盘的形状的。

4.两个外围函数写完了以后也就真正的进入了主角函数,主角函数用来查找那些位置可以放置皇后。将主角函数命名为find(),具体的实现代码如下:

  1. void find(int i)
  2. {
  3. int ret = 0;
  4. int j = 1;
  5.  
  6. if (i > 8)
  7. {
  8. output();
  9. getchar();
  10. }
  11. else
  12. {
  13. for (j = 1; j <= 8; j++)
  14. {
  15. if (check (i, j))
  16. {
  17. board[i][j] = '*';
  18. find(i + 1);
  19. board[i][j] = ' ';
  20. }
  21. }
  22. }
  23. }

首先我们队传进来的i的值进行判定,当然i的值通常都为1,因为我们都是要从1开始进行遍历的,然后我们开始进行遍历,进入for循环,进行递归和回溯算法。这里的具体程序执行步骤我会在下面继续说,这里暂且不提。

5.在上面的find()函数中,我们会发现,我们调用了一个check()函数,这个函数的主要作用是用来判定我们当前元素的位置是否合法,然后把判定的结果返回给find,供find在if判定中使用。下面是check函数的源代码:

  1. int check (int i, int j)
  2. {
  3. int ret = 1;
  4. int k = 0;
  5.  
  6. for (k = 0; k < 3; k++)
  7. {
  8. int ni = i;
  9. int nj = j;
  10.  
  11. while (ret && (board[ni][nj] != '#'))
  12. {
  13. ni = ni + pos[k].heng;
  14. nj = nj + pos[k].zong;
  15.  
  16. if (board[ni][nj] == '*')
  17. {
  18. ret = 0;
  19. }
  20. else
  21. {
  22. ret = 1;
  23. }
  24. }
  25. }
  26. return ret;
  27. }

在check函数中,我们首先进入for循环,当然变量k的定义主要是用来作为循环变量了。返回值ret先定义为1,如果可以放置则返回1,如果不可以放置则返回0。我们每一次进入for循环都将传进来的i,j的值赋给ni,nj,这样在进入while中时候我们可以对位置坐标进行偏移操作,我们通过for循环和while大循环可以还有坐标的移位操作可以对当前元素所在的位置相对于纵向所在的线,对角线所在的线。这里通过传进来的元素位置来确定偏移的原理图如下:

假定我们向check函数传递的是i,j.这样我们需要遍历的方向是红色箭头所指的三个方向,分别有①,②,③,④,⑤,⑥三个位置需要遍历,查看是否有*全部都没有星号,那么我们最后向find函数返回1,如果有一个位置有星号,我们向find函数返回0.

因为我们定义的是二维数组board[8][8]的棋盘,所以可以知道①的坐标是(i-1,j-1),②的坐标是(i-2,j-2),③的坐标是(i-1,j),④的坐标是(i-2,j),⑤的坐标是(i-1,j+1),⑥的坐标是(i-1,j+2),因为在check函数中我们使用的是while循环来控制坐标偏移的,代码如下:

  1. while (ret && (board[ni][nj] != '#'));

这样我们不遇见#这个while循环不会终止,所以我们可以使用一个二维数组来控制这个偏移量,为了代码的阅读,我们同时还将偏移量的具体变量定义放在了一个结构体中。代码如下:

  1. typedef struct _tag_pos
  2. {
  3. int heng;
  4. int zong;
  5. }poss;
  6. poss pos[] = {{-1, -1}, {-1, 0}, {-1, 1}} ;

通过上面的定义我们会发现我们执行while大循环,我们都会将现有的坐标位置来和坐标的偏移量进行相加,直至棋盘的边框位置,这样就完成了红色箭头的三个方向所有位置的遍历。

再来说一下,check函数的执行机制,因为主要是两个循环语句在控制,所以这里做了一个excel表格,截图传上来,方便观看。

6.完成了每一个位置的合法性判定以后,我们再来回头看find函数,find函数是真正的递归回溯算法的实现。

我们这里以程序的第一轮判定为例进行说明:

find函数调用check函数并依次执行for循环,以行为单位将皇后安排在合法的位置上。程序指定到第6次调用find函数的时候,通过检测发现已经在第六行已经没有合法的位置的可以安排皇后了,这样find(6)函数返回到函数开始执行的位置,顺序向下执行,执行语句board[i][j] = ' ';这条语句的作用是把第五行的所有位置清空,然后程序继续返回到find(5)函数中执行,重新遍历,然后再依次执行,这样完成了回溯算法的实现。

当每次执行完find(8)函数后程序都会返回到上一层函数,然后再进行依次执行,直到递归调用的结束。

来个动图嗨皮一下,顺便脑海里想着它的遍历的历程。

三、八皇后问题的代码实现

  1. #include <stdio.h>
  2.  
  3. typedef struct _tag_pos
  4. {
  5. int heng;
  6. int zong;
  7. }poss;
  8.  
  9. /*定义偏移量的数组*/
  10. poss pos[] = {{-1, -1}, {-1, 0}, {-1, 1}} ;
  11.  
  12. /*用来描述棋盘的数组*/
  13. static char board[10][10];
  14.  
  15. /***********************************************************************************************
  16. *函数名: init
  17. *参数:void
  18. *返回值:void
  19. *功能:创建棋盘 同时创建边界
  20. ***********************************************************************************************/
  21. void init ()
  22. {
  23.  
  24. int i = 0;
  25. int j = 0;
  26. for (i = 0; i < 10; i++)
  27. {
  28. board[0][i] = '#';
  29. board[i][0] = '#';
  30. board[i][9] = '#';
  31. board[9][i] = '#';
  32. }
  33. for (i = 1; i <= 8; i++)
  34. {
  35. for (j = 1; j <= 8; j++)
  36. {
  37. board[i][j] = ' ';
  38. }
  39. }
  40. }
  41.  
  42. /***********************************************************************************************
  43. *函数名:output
  44. *参数:void
  45. *返回值:void
  46. *功能:输出棋盘中每一个元素
  47. ***********************************************************************************************/
  48. void output()
  49. {
  50. int i = 0;
  51. int j = 0;
  52. for (i = 0; i < 10; i++)
  53. {
  54. for (j = 0; j < 10; j++)
  55. {
  56. printf ("%c", board[i][j]);
  57. }
  58. printf ("\n");
  59. }
  60. }
  61.  
  62. /***********************************************************************************************
  63. *函数名:check
  64. *参数:void
  65. *返回值:合法返回1,不合法返回0
  66. *功能:传进来的是现在位置,然后根据偏移量,判定这个位置是否合法
  67. ***********************************************************************************************/
  68. int check (int i, int j)
  69. {
  70. int ret = 1;
  71. int k = 0;
  72.  
  73. for (k = 0; k < 3; k++)
  74. {
  75. int ni = i;
  76. int nj = j;
  77.  
  78. while (ret && (board[ni][nj] != '#'))
  79. {
  80. ni = ni + pos[k].heng;
  81. nj = nj + pos[k].zong;
  82.  
  83. if (board[ni][nj] == '*')
  84. {
  85. ret = 0;
  86. }
  87. else
  88. {
  89. ret = 1;
  90. }
  91. }
  92. }
  93. return ret;
  94. }
  95.  
  96. /***********************************************************************************************
  97. *函数名:find
  98. *参数:void
  99. *返回值:int i 从主函数传进来的参数,用于规定从哪一行开始
  100. *功能:查找共有多少种方法
  101. ***********************************************************************************************/
  102. void find(int i)
  103. {
  104. int ret = 0;
  105. int j = 1;
  106.  
  107. if (i > 8)
  108. {
  109. output();
  110. getchar();
  111. }
  112. else
  113. {
  114. for (j = 1; j <= 8; j++)
  115. {
  116. if (check (i, j))
  117. {
  118. board[i][j] = '*';
  119. find(i + 1);
  120. /*
  121. 假如上面的find(i + 1)调用以后,执行到if (check (i, j) )
  122. 这个时候不满足条件,程序跳出,然后find(i + 1)返回,因为这个位置的条件不满足,
  123. 所以程序继续向下执行,在for循环里继续循环,直到递归到最里层。然后每一个函数在
  124. 返回的时候都会执行board[i][j] = ' '
  125. */
  126. board[i][j] = ' ';
  127. }
  128. }
  129. }
  130. }
  131.  
  132. int main()
  133. {
  134. init();
  135. find(1);
  136. return 0;
  137. }

C语言数据结构----递归的应用(八皇后问题的具体流程)的更多相关文章

  1. C语言数据结构----递归的应用(斐波拉契数列、汉诺塔、strlen的递归算法)

    本节主要说了递归的设计和算法实现,以及递归的基本例程斐波拉契数列.strlen的递归解法.汉诺塔和全排列递归算法. 一.递归的设计和实现 1.递归从实质上是一种数学的解决问题的思维,是一种分而治之的思 ...

  2. C语言:试探算法解决“八皇后”问题

    #include <stdio.h> #define N 4 int solution[N], j, k, count, sols; int place(int row, int col) ...

  3. C#中八皇后问题的递归解法——N皇后

    百度测试部2015年10月份的面试题之——八皇后. 八皇后问题的介绍在此.以下是用递归思想实现八皇后-N皇后. 代码如下: using System;using System.Collections. ...

  4. java递归求八皇后问题解法

    八皇后问题 八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例.该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处 ...

  5. 八皇后问题详细分析与解答(递归法解答,c#语言描述)

    八皇后问题,是一个古老而著名的问题,是回溯算法的典型例题.该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一列或 ...

  6. 八皇后问题 递归实现 C语言 超详细 思路 基础

    八皇后问题 :假设 將八个皇后放到国际象棋盘上,使其两两之间无法相互攻击.共有几种摆法? 基础知识: 国际象棋里,棋盘为8X8格. 皇后每步可以沿直线.斜线 走任意格. 思路: 1.想把8个皇后放进去 ...

  7. 数据结构之递归Demo(走迷宫)(八皇后)(汉诺塔)

    递归 顾名思义,递归就是递归就是递归就是递归就是递归......就是递归 Google递归:

  8. C#数据结构与算法系列(十四):递归——八皇后问题(回溯算法)

    1.介绍 八皇后问题,是一个古老而著名的问题,是回溯算法的经典案例,该问题是国际西洋棋棋手马克斯.贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即 任意两个皇后都不能处 ...

  9. 7, java数据结构和算法: 八皇后问题分析和实现 , 递归回溯

    什么是八皇后问题: 指的是,在一个8 * 8的棋盘中, 放置8个棋子, 保证这8个棋子相互之间, 不在同一行,同一列,同一斜线, 共有多少种摆法? 游戏连接: http://www.4399.com/ ...

随机推荐

  1. 符号文件(.pdb)——Windows 应用程序调试必备

    最近在做项目需求过程中,时不时会遇到崩溃,总是异常中断,于是学习了windbg进行调试的一些基础,windbg在接下来文章进行更新,先介绍在windbg调试中一个重要文件(符号文件) 一.符号文件定义 ...

  2. Mina airQQ聊天 服务端篇(二)

    Mina聊天服务端实现思路:在用户登录的时候.连接服务端而且验证登录用户,假设成功,则将IoSession保存到map<账号,IoSession>中,而且通知该用户的好友上线,然 后再请求 ...

  3. 支付宝打造公共账号业务网关, RSA密钥对生成

    作者: 玉龙      版权全部,同意转载. 请注明出处(创建金融_玉龙  http://www.weibo.com/u/1872245125) 原文地址: http://blog.csdn.net/ ...

  4. HDU 4882 ZCC Loves Codefires(贪心)

     ZCC Loves Codefires Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/O ...

  5. Ubuntu Crontab

    Ubuntu启用Crontab 启动cron服务: service cron start 如果需要设置为开机时自动启动,则执行 sysv-rc-conf --level 35 cron on 另外,u ...

  6. qt学习笔记(七)之数据库简介(所有支持数据库类型的列表)

    笔者最近用Qt写公司的考勤机.本来要求是要基于frameBuffer下用自己开发的easyGUI来进行上层应用开发,但是考虑到easyGUI提供的接口不是很多,就考虑用Qt来开发,顺带练练手. 废话不 ...

  7. Qt之多线程

    源地址:http://blog.csdn.net/liuhongwei123888/article/details/6072320 Qt 是一种基于 C++ 的跨平台 GUI 系统,能够提供给用户构造 ...

  8. 如何安装(装载)axure组件(部件)

    我们在网络上经常可以下载到很多网友精心设计的axure组件,这些组件给我们节省了很多的时间,方便了我们进行原型设计,可是对于很多刚刚接触axure的朋友就遇到了2个问题: 第一:到哪里去下载这些组件 ...

  9. Apache 服务器

    1.介绍 Apache原来用于小型或试验性Internet网络,后来逐步扩展到各种系统中,对Linux的支持几乎完美.Apache可以支持SSL技术,支持多台虚拟主机.Apache是以进程为基础的结构 ...

  10. MyEclipse-6.5注冊码生成器源代码

    打开MyEclipse新建一个Javaproject,然后新建类,粘贴例如以下代码,就可以生成MyEclipse的注冊码 import java.io.BufferedReader; import j ...