非递归的八皇后

随手再写写八皇后在编程中的重要性?

八皇后,应该是凡程序员都会知道的经典算法设计题目,这道题作为初步讲述使用递归模式解决的高级问题,作为使用DFS(深度优先搜索)解决的初级问题,以及很多巧妙的位运算操作或者取消递归用stack来代替的实际操练,都是非常不错的经典练习题目,相信很多人对92这个数字已经不再陌生了。

笔者第一次接触八皇后应该是在初二当时开始正式注意NOIP的练习时,遇到的八皇后,最初的代码已经找不见了,肯定是用二维数组傻傻的实验,然后在老师的教导下,不断改正,电脑上保留着最后高中修改过一次的C代码:

#include <stdio.h>
#include <math.h>
//#include <windows.h>
// 八皇后或者n皇后问题
// 三种情况:1.行;2.左对角线;3.右对角线
// 行的问题可以采用1维数组 
int a[15]={0};
int total=0;	
int n=0;
void output()
{
	int i,j,k;
	printf("%d:\n",total);
	for (i=1;i<=n;i++) 
	{
		k=a[i];
	    for (j=1;j<=n;j++)
		{
			if(j==k) 
			  printf("X");
			else printf("O");
		}
		printf("\n");
	}	
}
int judge(int s)
{
	int i;
	for (i=1;i<s;i++)
	  if(a[i]==a[s] || abs(a[s]-a[i])==(s-i)) return 0;
	return 1;
}
void queen(int x)
{
	int i;
	for (i=1;i<=n;i++)
	{
		a[x]=i;
		if(judge(x))
		  if(x==n)
		    { total++; 
			output();
			 }
		  else 
		    queen(x+1);
	}
	//printf("total=",total);
}
int main()
{
	//int ta,tb;

	//printf("本题最多适用于10皇后!!"); 
	scanf("%d",&n);
	//GetLocalTime(&st);
	//ta=st.wSecond*1000+st.wMilliseconds;
    queen(1);
    printf("%d",total);
 	//GetLocalTime(&et);
	//tb=et.wSecond*1000+et.wMilliseconds;
	//printf("\n%u",tb-ta);   
	return 0;
} 
//评语:最先做八皇后问题的时候采用的附加2个数组b,c来负责左右对角线
//      这次通过思考,巧妙运用一维数组,第29行的判断是核心 

后来也是因为八皇后已经是非常基础了,连默写都不用去默写了,难度和后来遇到的Tree完全不在一个层次上了,渐渐也就淡忘了。

大学之后,让学Python,然后一次作业就是写八皇后,即刻可以看出八皇后对于初学者的重要性:

import math
puzzle = [0] * 15
total = 0
queen_number = 0
def display():
    global puzzle
    for i in range(1,queen_number+1):
        k = puzzle[i]
        for j in range(1, queen_number+1):
            if j==k:
                print("|Q",end="")
            else:
                print("| ",end="")
        print("|")
        
def judge(s):
    global puzzle
    for i in range(1,s):
        if puzzle[i] == puzzle[s] or abs(puzzle[s]-puzzle[i]) == s-i:
            return False
    return True

def queen(x):
    global puzzle
    global queen_number, total
    for i in range(1,queen_number+1):
        puzzle[x] = i
        if judge(x):
            if x == queen_number:
                total = total + 1
                if total == 1:
                    display()
            else:
                queen(x+1)
                
def main():
    global total,queen_number
    global puzzle
    queen_number = 4
    queen(1)
    print(total)

main()

简单来看完全就是当年C代码套了一层Python的皮,留下了个人对Python不算很佳的印象。后来其实也相对正式了这个印象,Python不太适合做算法设计的语言,而更适合使用更多外部的链接库辅助Machine Learning,web,easy_game, MatLab或者R等数据科学方面的事情。


做一个程序员,更需要注意的几点就是:1. 诚信问题;2. 耐心问题;3. 速度/时间/效率问题;4. 实践性问题;5. Robust问题。其实个人曾经挺反对设计程序限时什么的,因为这无形中给人了以压力,让人在紧张中编程无疑会有很多BUG出现……但是有的时候如果不是因为显示,可能脑中的细胞并不会活跃起来吧。前几天有人找我说让我再编一个八皇后,非递归的,那个时候我去网上搜了搜,发现非递归版本的竟然很少,搜八皇后出来的竟然很多都是位运算版本:

count = 0
def queen(a, b, c): #left diagonal, down, right diagonal
    global count
    if b == 255: # 2^8 - 1
        count += 1
        return
    d = 255 & (~(a|b|c)) # position of queen
    while(d):
        bit = d & (~d + 1) # one queen
        d -= bit
        queen((a|bit)<<1, b|bit, (c|bit)>>1)

queen(0,0,0)
print(count)

不由得感慨,现在这种只求效率和简短代码所忽视的一些事情,在这里也不方便说(面具这种东西真得对于某些人来说如生命一样,必须存在;同时这类人也特别希望别人也同样拥有一张,从而显得个人并不是那么刻意)。
于是个人,也是仔细想了想,在极不让使用递归模式,也不建议使用栈的时候,采用当时最原始的while循环来解决问题:

def init():
    n = int(input(""))
    return n

def my_print(puzzle, queen_number):
    # print(puzzle)
    for i in range(queen_number):
        k = puzzle[i]
        for j in range(queen_number):
            if j==k:
                print("|Q",end="")
            else:
                print("| ",end="")
        print("|")  

def check(puzzle, s, n):
    if puzzle[s] >= n: return False
    for i in range(0,s):
        if puzzle[i] == puzzle[s] or abs(puzzle[s]-puzzle[i]) == s-i:
            return False
    return True


def queen(a, n):
    a[0] = 0
    i = 0
    while True:
        if check(a, i, n):
            if i == n-1:
                my_print(a, n)
                break
            else:
                i += 1
                a[i] = 0
        else:
            if a[i] >= n: 
                i -= 1
                a[i]+=1
            else:
                a[i] += 1

def main():
    n = init()
    a = [0 for row in range(n)]
    queen(a, n)

main()

可以看到虽然判断和打印的函数是直接从原版递归那里复制过来,但是核心已经改变了,单纯的while,虽然看起来很是冗杂,但是思路确实非常的清晰了。


有些东西该用在什么地方,就用在什么地方,不要用在其他地方给自己找麻烦。
在不看懂说明的时候,切记不要随便使用一些函数,不能心急。
最近八皇后又有新的要求出现了,如果确定一个皇后的位置,能不能输出在此情况下的所有解?
这个问题当时听到之后第一反应,在全部收集的解法中进行筛选不就行了吗,然后被告知说不让用这种方法,那简单来讲就是把筛选融入过程中,想了想确实也挺新颖的,自己之前从来没这样编过或者想过,然后发过来一个参考程序,说是让我在这个基础上改:

def queen(A,cur=0):
    if cur==len(A):
        print(A)
    else:
        n=0
        for col in range(len(A)):
            ok=True
            A[cur]=col
            for r in range(cur):
                if A[r]==col or r-A[r]==cur-A[cur] or r+A[r]==cur+A[cur]:
                    n=n+1
                    ok=False
                    break
            if ok:
                queen(A,cur+1)
A=[-1]*4
queen(A,0)

说还要改成N皇后的,我说行,然后就动手了。其实为什么一直强调递归的好处呢,因为递归改起来好改啊,只要思路对,改单一模块就对了。

解题思路:
简单来考虑就是这样的,我们要固定一行之中皇后的位置,相当于在长度为N的list中固定第idx个位置的数不进行改变,一开始我是想在进入递归的时候进行判断是否在idx,但是随后想到如果从下一层退回来的时候我就不能注意到这个问题,所以还是在枚举位置的时候直接进入下一层,不进行赋值操作就可以了,另外仍然将check函数提取出来,去掉原函数中ok的部分,然后在check中加入优化,提前进行与idx位置皇后参数的比较,这样避免前面就放了一个冲突解,完整版代码就不在这里放了,毕竟篇幅已经够长了……

   if cur==len(A):
       if (-1 not in A) and (A not in flag): flag.append(A[:])
   else:
       for col in range(N):
           if cur != i:
               A[cur]=col
               if check(A, cur, i):
                   queen(A,i,j,flag,N,cur+1)
           else:
               queen(A,i,j,flag,N,cur+1)

而且除了多加了一个ifelse,整体和所给的参考代码并没有什么不同,多加了参数?哎呀,不是听说还要改成N皇后,然后又要全部解法吗╮(╯▽╰)╭


然后个人就突发奇想,如果是非递归的能不能也同样完成这种问题,其实在非递归中这种用i,从某种角度上就比递归这边思路更好想一些,因为我们可以随时操纵是否进入下一层或者回来,但是这里就需要额外花精力放到不让下标越界的情况下了,尤其是要考虑idx是头一个或者最后一个的情况:

def queen(a, n, idx):
    if idx != 0:
        a[0] = 0
        i = 0
    else:
        a[1] = 0
        i = 1
    while True:
        print(a)
        if check(a, i, n, idx):
            if i == n-1:
                my_print(a, n)
                if i == idx: i-=1; a[i]+=1
                else: a[i] += 1
            else:
                i += 1
                if i == idx and i + 1 <= n-1: i += 1; a[i] = 0; continue
                if i != idx: a[i] = 0
        else:
            if a[i] >= n:
                if i == 0: break
                i -= 1
                if i == idx and i - 1 >= 0: i -= 1; a[i] += 1; continue
                if i == idx and i == 0: break
                if i != idx: a[i]+=1
            else:
                a[i] += 1

管理越不越界实在是有些烦人,所以很明显可以看到仍然使用递归是一个非常好的方法。


反正个人与八皇后也是有很多渊源,还包括一些讨论课时候的新奇看法,都是源于这个八皇后。

其实如果真的有时间,可以研究一下八皇后背后的数字逻辑,一旦在其他领域出现了的话,也许这些数字内涵的逻辑关系就会浮上水面了。

(然后就会有 O ( 1 ) O(1) O(1)的解法啊,兴不兴奋)

编程序切忌急功近利、漫不经心、耍小聪明、无脑复制。

谨记谨记

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值