用sql實現的n王后

lt發表於2016-10-29

利用oracle的遞迴cte

var n number
exec :n:=8
with p(p,r,c,w)
as(select level,ceil(level/:n),mod(level-1,:n)+1,power(2,level-1) from dual connect by level<=:n*:n)
,w as(select q.p,q.r,q.c,q.w,sum(p.w)sw from p,p q where
p.r=q.r or p.c=q.c or q.r-q.c=p.r-p.c or q.r+q.c=p.r+p.c
group by q.p,q.r,q.c,q.w)
,b(board, n_queens,w)as(
    SELECT lpad('-',p-1,'-')||'*', 1 ,w
      FROM p where  p<=:N
    UNION all
    SELECT
      rpad(board,p-1,'-') || '*' ,N_queens + 1 ,b.w+w.w
      FROM b, w 
    WHERE n_queens <:N
      and p >n_queens*:N and p<=(n_queens+1)*:N
      and bitand(b.w,sw)=0
)
select rpad(board,:N*:N,'-')board from b where n_queens =:N
;

利用對稱優化,第一行只取N/2個位置,然後把結果延Y軸翻轉。如果N是奇數,那麼第1行取(N+1)/2,第2行取(N-1)/2。因為1列只允許1個王后,所以從第2行起,中間列都是空的。

with p(p,r,c,w)
as(select level,ceil(level/:n),mod(level-1,:n)+1,power(2,level-1) from dual connect by level<=:n*:n)
,w as(select q.p,q.r,q.c,q.w,sum(p.w)sw from p,p q where
p.r=q.r or p.c=q.c or q.r-q.c=p.r-p.c or q.r+q.c=p.r+p.c
group by q.p,q.r,q.c,q.w)
,b(board, n_queens,w)as(
    SELECT lpad('-',p-1,'-')||'*', 1 ,w
      FROM p where  p<=(case mod(:N,2) when 0 then :N/2 else (:N+1)/2 end)
    UNION all
    SELECT
      rpad(board,p-1,'-') || '*' ,N_queens + 1 ,b.w+w.w
      FROM b, w 
    WHERE n_queens <:N
      and p >n_queens*:N and 
    p<=case when mod(:N,2)=1 and /*N_queens=1 and*/ b.w=power(2,(:N-1)/2) -- mid of row 1 is set
   then (n_queens+1)*:N-(:N+1)/2 
   else (n_queens+1)*:N end
      and bitand(b.w,sw)=0
),
hf as(select rpad(board,:N*:N,'-')board from b where n_queens =:N)
select * from hf
union all
select listagg(reverse(substr(board,(l-1)*:N+1,:N)))
within group(order by l) from hf a,(select level l from dual connect by level<=:N)b
group by board
;

執行時間減少1/3。

相關文章