8皇后问题Python与C++实现

  今天看《Python基础教程(第二版)》,看到生成器部分,作者用生成器给出一种非常精妙的解法:

  非常受启发,想去网上找更多资料,正好找到了这位网友的博客“http://blog.csdn.net/lwj1396/archive/2008/12/31/3663656.aspx”。原来他也正好是看了“8皇后问题”的Python生成器实现有感而发写了一个C++实现,并对两者进行了对比。

  仔细阅读了该网友的C++代码和对比,非常受教。下面是我的几点感受。

  首先,该网友的C++实现只求一个解,并不输出全部的解,所以我想在其基础上写一个C++的实现以输出全部的解,经过折腾总算弄出来了,

  我的代码还算简洁,其中有一点不太舒服的是不仅用了一个goto,而且这个goto直接跳入了循环的内部,可以说与通常结构化程序设计原则相差得太大了。不过没办法,谁让C++没有Python的yield支持呢,我们只能用堆栈和goto去实现之。当然这个goto其实并非必须,但若想将代码写得像Python那样简洁,还是必要的。在日常写程序中goto用得还真不多,但做算法时有时goto用起来反而更方便,忘了是哪本书说的了,说是用goto去直接翻译算法(如TAOCP上的那些成步骤的,又常有转步骤几的那种)是最简单、最直接、最准确的方法。在前几天读TinyScheme的源代码,它使用了TAOCP上的一个垃圾回收算法,就是用的goto直接翻译的,效果非常不错,所以今天我也果断在代码中用了goto。

  其次,我对nqueenes这个函数的C++实现还深有触。Python中有yield语句/表达式支持,代码可以写得很直接且高效,而C++中就没有这好运气了,只能用堆栈自己去做,不过发现其实还有些规律可循。我的这个nqueenes函数其实是一步做起来的,堆栈维护的当前迭代状态不要去轻易改变之,在框架代码改变了这个状态后,立即进行相关判断并采取一定动作,如我先写

这个可以说是最基础的框架代码:如果当前欲插入的位置冲突,我们就增加pos并尝试下一个位置,直到遇到一个不冲突的位置(注意我们忽略了pos过大以致超出了num - 1,这是我们接下来要考虑的),这时将“皇后”填到这个位置,并将尝试的位置恢复成0。下一步我们要对“有副作用”的表达式——即改变状态的表达式作进一步处理。

  首先是state.push_back(pos);这里改变了堆栈的状态,我们需要检测,因为当state.size()==num的时候,我们其实已经获得了一个解,这时我们应“收集”这个解:

  注意,printState之后直接到pos=0直接执行是不对的,这个后面再说。

  下一个有“副作用”的地方是++pos;很明显这个表达式有可能将pos送到0...num-1这个合法的范围之外,我们需要应对这种情况,这样就有了前示的另一个嵌入的循环。当pos==num-1时,pos不能再增加,相反这表明堆栈state维护的当前状态不能获得一个解,我们需要“回溯”,这里即是从栈中弹出一个值:

  注意,弹出一个值要了两条语句,本来一条是很自然的,但STL从效率考虑却不这样做,个人对此耿耿于怀。每弹出一个值就回到了一个之前的状态,pos与state都得到了更新,但pos仍有可能是num - 1,此时就需要继续弹出值。为什么是先判断pos == num - 1,然后++;而不是先++,再判断pos == num - 1?嗯,这个也是经过折腾想到的最简洁的写法。

  经过上面的回溯的加入,基本的框架已经形成,现在我们来完成printState的善后事宜。仔细想容易知道——在获取一个解将其printState后,为了获取下一个解,只须“假装”当前被push的pos是无效的,将程序的流程转向conflict失败之处即可。下面即是它的思路,用了一个“变态”的goto:

  注意,在goto转移流程之前,我们pop掉了刚才push进去的pos,因为我们将这个pos当成“无效”的,因此这样做是显然的。

经过上面的处理,程序还有最后一个地方需要处理,那就是在内层回溯弹出元素时,有可能将堆栈弹空,这是一个极端情况,意味着我们已经找完了所有的解,不能再找到解,此时应结束算法,下面是完成了的nqueenes函数:

  上面对这个简单的函数说了一大通,可能你已经烦了,其实我自己都烦了。一言以蔽之——留意状态改变的地方。这一条是写这个程序最大的感受,只要我们留意程序状态的改变,一个最基本的算法框架就可以将我们带到算法的完整实现

  曾写过一个类似于printf的函数format,这个函数像printf一样格式化字符串,并将结果用动态分配的字符串返回(就像gcc中的asprintf一样)。format的实现实际是一个手写的递归下降分析器,它们的本质也是要维护好程序的“状态”,一定不要轻易使用“赋值”语句(++也是赋值),在“语法分析”过程中我们可以用各种判断,但是改变状态(使用++改变状态,将语法分析的当前token——格式字符串的当前字符移至下一个)一定发生在已经确定了语法结构的时候!

  还想到了更远的地方,在SICP这本书中甚至到了第三章(总共五章)才正式引入赋值,而且真正的赋值其实就是一个set!(即是说任何一种程序设计语言,一旦引入赋值——只需要引入最简单的一种赋值运算,语言就将发生根本的、彻底的改变),所以不要小看了赋值,赋值与否是一个根本的问题。函数语言独树一帜,就是因为它们限制,甚至完全消除了赋值。

  最后一点我想说,Python的“生成器”所实现的程序设计方法,实则与Lisp(包括Scheme)中用惰性计算实现的“流”异曲同工。第一次接触到“流”这个概念正是在SICP这本书中,使用“生成器”或“流”我们可以造出概念上的无穷序列,这样某些程序的思路将变得无比简单。Scheme这个Lisp方言虽小,但它五脏俱全,Python费好大劲才建立起来了“生成器”这个好用的工具,而Scheme一个简单的宏就可以搞定这一切,这不得不令人对Scheme/Lisp的强大叹为观止,所以Lisp语言的确非常值得学习,强力推荐《Structure and Interpretation of Computer Programs》与《Practical Common Lisp》。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
皇后问题是一个经典的算法问题,目标是在一个8x8的棋盘上放置8个皇后,使得它们互相之间不能相互攻击。这里给出一种常见的解法,使用回溯算法实现: 1. 创建一个8x8的二维数组作为棋盘,初始化所有元素为0,表示空格。 2. 从第一行开始,逐行放置皇后。对于每一行,从左到右遍历每个列,尝试将皇后放置在当前位置。 3. 如果当前位置可以放置皇后(即不会与已经放置的皇后相互攻击),将当前位置标记为1,并进入下一行。 4. 如果当前位置无法放置皇后,则回溯到上一行,继续尝试下一个列。 5. 当所有皇后都放置完毕时,得到一个解。可以将解存储起来或进行其他操作。 6. 继续回溯,寻找其他解法。 下面是一个简单的示例代码: ```python def is_safe(board, row, col): # 检查当前位置是否与已经放置的皇后冲突 # 检查列 for i in range(row): if board[i][col] == 1: return False # 检查左上方对角线 i, j = row - 1, col - 1 while i >= 0 and j >= 0: if board[i][j] == 1: return False i -= 1 j -= 1 # 检查右上方对角线 i, j = row - 1, col + 1 while i >= 0 and j < 8: if board[i][j] == 1: return False i -= 1 j += 1 return True def solve_n_queens(): board = [[0] * 8 for _ in range(8)] solutions = [] def backtrack(row): if row == 8: # 找到一个解,将棋盘状态添加到结果列表中 solutions.append([row[:] for row in board]) return for col in range(8): if is_safe(board, row, col): board[row][col] = 1 # 放置皇后 backtrack(row + 1) # 继续下一行 board[row][col] = 0 # 撤销放置的皇后 backtrack(0) return solutions # 调用函数并打印所有解法 solutions = solve_n_queens() for solution in solutions: for row in solution: print(row) print() ``` 这段代码会输出所有的八皇后问题解法,每个解法是一个8x8的棋盘,其中1表示皇后的位置,0表示空格。希望这可以帮助到你!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值