随手再写写八皇后在编程中的重要性?
八皇后,应该是凡程序员都会知道的经典算法设计题目,这道题作为初步讲述使用递归模式解决的高级问题,作为使用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)
而且除了多加了一个if
和else
,整体和所给的参考代码并没有什么不同,多加了参数?哎呀,不是听说还要改成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)的解法啊,兴不兴奋)
编程序切忌急功近利、漫不经心、耍小聪明、无脑复制。
谨记谨记