Nクイーン問題(44)第七章 対象解除法 Python編


【参考リンク】Nクイーン問題 過去記事一覧はこちらから

対象解除法について

まず、Nが小さな盤面で考えていきます。
以下、順に見て理解を深めてもらえればと思います。

##対象解除法について(N2版)
ひとつの解には、盤面を90度・180度・270度回転、及びそれらの鏡像の合計8個の対称解が存在します。

  原型   90度  180度   270度
  12   41   34   23
  43   32   21   14
  
      上の行を左右反転                     
  21   14   43   32
  34   23   12   41

上図左上がユニーク解です。

  原型(ユニーク解)
  12 
  43 

1行目はユニーク解を90度、180度、270度回転したものです。

  原型   90度  180度   270度
  12   41   34   23
  43   32   21   14

2行目は1行目のそれぞれを左右反転したものです。

      上の行を左右反転                     
  21   14   43   32
  34   23   12   41

2行目はユニーク解を左右反転、対角反転、上下反転、逆対角反転したものとも解釈できます。
ただし、回転・線対称な解もあるので注意が必要です。

対称的な解を除去し、ユニーク解のみを求める方法はいくつかあります。
ここでは、一つの解には8つのパターンが存在することを覚えておいてください。

対象解を導き出す2つの方法

対象解を導き出す方法として、
1.解が見つかるたびに回転・対象をチェック
2.解のすべてを保存して、保存された解の回転・対象をチェック

最小解選択法
解がひとつみつかるとすべての対称解を生成、 状態を数値とみなして最も小さいもののみを解とする方法。
最も最適と言われています。

最小選択法よりも劣る方法
解全てを保存しておき、新しい解が発見されるたびに対称形が無いかどうかを調べる方法。
保存領域を必要とするし、比較時に対称形を生成する必要があります。

対象解除法(N5版)

全探索によって得られたある1つの解が、回転・反転などによる本質的に変わることのない変換によって他の解と同型となるものが存在する場合、それを別の解とはしないとする解の数え方で得られる解を「ユニーク解」といいます。

ユニーク解とは、全解の中から回転・反転などによる変換によって同型になるものどうしをグループ化することを意味しています。

グループ1:
+-+-+-+-+-+  +-+-+-+-+-+ 
| | | |Q| |  | |Q| | | |
+-+-+-+-+-+  +-+-+-+-+-+
|Q| | | | |  | | | | |Q|
+-+-+-+-+-+  +-+-+-+-+-+
| | |Q| | |  | | |Q| | |
+-+-+-+-+-+  +-+-+-+-+-+
| | | | |Q|  |Q| | | | |
+-+-+-+-+-+  +-+-+-+-+-+
| |Q| | | |  | | | |Q| |
+-+-+-+-+-+  +-+-+-+-+-+

グループ2:
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  
| | | | |Q|  |Q| | | | |  | | |Q| | |  | | |Q| | |  | | | |Q| |  | |Q| | | |  |Q| | | | |  | | | | |Q|  
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  
| | |Q| | |  | | |Q| | |  |Q| | | | |  | | | | |Q|  | |Q| | | |  | | | |Q| |  | | | |Q| |  | |Q| | | |  
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  
|Q| | | | |  | | | | |Q|  | | | |Q| |  | |Q| | | |  | | | | |Q|  |Q| | | | |  | |Q| | | |  | | | |Q| |  
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  
| | | |Q| |  | |Q| | | |  | |Q| | | |  | | | |Q| |  | | |Q| | |  | | |Q| | |  | | | | |Q|  |Q| | | | |  
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  
| |Q| | | |  | | | |Q| |  | | | | |Q|  |Q| | | | |  |Q| | | | |  | | | | |Q|  | | |Q| | |  | | |Q| | |  
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  

ユニーク解はその「個数のみ」に着目され、この解はユニーク解であり、この解はユニーク解ではないという定まった判定方法はありません。
ユニーク解であるかどうかの判断はユニーク解の個数を数える目的の為だけにあります。
どのような定義をしたとしてもユニーク解の個数それ自体は変わりません。

Nクイーン問題は、正方形のボードで形成されるので、回転・反転による変換パターンはぜんぶで8通りあります。

だからといって「全解数=ユニーク解数×8」と単純にはいきません。
ひとつのグループの要素数が必ず8個あるとは限らないのです。

N=5の例では2つのグループがあり、要素数が2個のグループ、要素数が8個のグループがあります。
結論から言うと、N=5の全解は10個で、ユニーク解は2個です。

ユニーク解を判定するための定義

各行のクイーンが左から何番目にあるかを調べて、最上段の行(row)から下の行(row)へ順番に列挙します。
そしてそれをN桁の数値として見た場合に「最大値」となるものをユニーク解とします。
このN桁の数を以後は「ユニーク判定値」と呼ぶことにします。

 0 1 2 3 4 
+-+-+-+-+-+  
| | | | |Q|    4
+-+-+-+-+-+  
| | |Q| | |    2 
+-+-+-+-+-+  
|Q| | | | |    0  ----> 4 2 0 3 1 (ユニーク判定値)
+-+-+-+-+-+             数が大きい方をユニークとみなす 
| | | |Q| |    3
+-+-+-+-+-+  
| |Q| | | |    1 
+-+-+-+-+-+  

 0 1 2 3 4     左右反転!
+-+-+-+-+-+  
|Q| | | | |    0
+-+-+-+-+-+  
| | |Q| | |    2 
+-+-+-+-+-+  
| | | | |Q|    4  ----> 0 2 4 1 3 
+-+-+-+-+-+            数が小さいのでユニーク解とはしません 
| |Q| | | |    1
+-+-+-+-+-+  
| | | |Q| |    3 
+-+-+-+-+-+  

探索によって得られたある1つの解(オリジナル)がユニーク解であるかどうかを判定するには、「8通りの変換を試み、その中でオリジナルのユニーク判定値が最小であるかを調べる」ことになります。

N2の場合の8通りの変換

  原型   90度  180度   270度
  12   41   34   23
  43   32   21   14
  
      上の行を左右反転                     
  21   14   43   32
  34   23   12   41

結論から先にいえば、ユニーク解とは成り得ないことが明確なパターンを探索中に切り捨てる「枝刈り」を組み込むことにより、3通りの変換を試みるだけでユニーク解の判定が可能になります。

枝刈り

先ず最上段の行(row)のクイーンの位置に着目します。
その位置が左半分の領域にあればユニーク解には成り得ません。
何故なら左右反転によって得られるパターンのユニーク判定値の方が確実に小さくなるからです。

 0 1 2 3 4     
+-+-+-+-+-+  
|Q| | | | |    0
+-+-+-+-+-+  
| | |Q| | |    2 
+-+-+-+-+-+  
| | | | |Q|    4  ----> 0 2 4 1 3 
+-+-+-+-+-+            数が小さいのでユニークとはしません
| |Q| | | |    1
+-+-+-+-+-+  
| | | |Q| |    3 
+-+-+-+-+-+  

 0 1 2 3 4     左右反転!
+-+-+-+-+-+  
| | | | |Q|    4
+-+-+-+-+-+  
| | |Q| | |    2 
+-+-+-+-+-+  
|Q| | | | |    0  ----> 4 2 0 3 1 (ユニーク判定値)
+-+-+-+-+-+             数が大きい方をユニークとみなす 
| | | |Q| |    3
+-+-+-+-+-+  
| |Q| | | |    1 
+-+-+-+-+-+  

Nが奇数の場合に中央にあった場合はどうでしょう。

+-+-+-+-+-+  
| | |Q| | |    2
+-+-+-+-+-+  
| | | | | |      
+-+-+-+-+-+  
| | | | | |       
+-+-+-+-+-+             
| | | | | |     
+-+-+-+-+-+  
| | | | | |      
+-+-+-+-+-+  

これもユニーク解には成り得ません。
何故なら仮に中央にあった場合、それがユニーク解であるためには少なくとも他の3辺におけるクイーンの位置も中央になければならず、それは互いの効き筋にあたるので解になりえないからです。

+-+-+-+-+-+  
| | |Q| | |    
+-+-+-+-+-+  
| | | | | |      
+-+-+-+-+-+  
|Q| | | |Q|       
+-+-+-+-+-+             
| | | | | |     
+-+-+-+-+-+  
| | |Q| | |      
+-+-+-+-+-+  

最上段の行のクイーンの位置は中央を除く右側の領域に限定されます。(ただし、N ≧ 2)

+-+-+-+-+-+  
| | | |Q|Q|    
+-+-+-+-+-+  
| | | | | |      
+-+-+-+-+-+  
| | | | | |       
+-+-+-+-+-+             
| | | | | |     
+-+-+-+-+-+  
| | | | | |      
+-+-+-+-+-+  

次にその中でも一番右端(右上の角)にクイーンがある場合を考えてみます。

+-+-+-+-+-+  
| | | | |Q|    
+-+-+-+-+-+  
| | | | | |      
+-+-+-+-+-+  
| | | | | |       
+-+-+-+-+-+             
| | | | | |     
+-+-+-+-+-+  
| | | | | |      
+-+-+-+-+-+  

他の3つの角にクイーンを置くことはできないので、

+-+-+-+-+-+  
|Q| | | |Q|    
+-+-+-+-+-+  
| | | | | |      
+-+-+-+-+-+  
| | | | | |       
+-+-+-+-+-+             
| | | | | |     
+-+-+-+-+-+  
|Q| | | |Q|      
+-+-+-+-+-+  

ユニーク解であるかどうかを判定するには、右上角から左下角を通る斜軸で反転させたパターンとの比較だけになります。

+-+-+-+-+-+  
| | | | |Q|    
+-+-+-+-+-+  
| | | |/| |      
+-+-+-+-+-+  
| | |/| | |       
+-+-+-+-+-+             
| |/| | | |     
+-+-+-+-+-+  
|/| | | | |      
+-+-+-+-+-+  

突き詰めれば、上から2行目(row1)のクイーンの位置が右から何番目にあるかと、

+-+-+-+-+-+  
| | | | |Q|    
+-+-+-+-+-+  
| |Q| |/| |  ←
+-+-+-+-+-+  
| | |/| | |       
+-+-+-+-+-+             
| |/| | | |     
+-+-+-+-+-+  
|/| | | | |      
+-+-+-+-+-+  

右から2列目のクイーンの位置が上から何番目にあるかを比較するだけで判定することができます。

+-+-+-+-+-+  
| | | | |Q|    
+-+-+-+-+-+  
| |Q| |/| |      
+-+-+-+-+-+  
| | |/| | |       
+-+-+-+-+-+             
| |/| |Q| |  ←    
+-+-+-+-+-+  
|/| | | | |      
+-+-+-+-+-+  

この2つの値が同じになることはないからです。

       3 0
+-+-+-+-+-+  
| | | | |Q| 0
+-+-+-+-+-+  
| |Q| |/| | 3    上から2行目のクイーンの位置が右から4番目にある。
+-+-+-+-+-+  
| | |/| | |       
+-+-+-+-+-+             
| |/| |Q| |  ←  右から2列目のクイーンの位置が上から4番目にある。
+-+-+-+-+-+      しかし、互いの効き筋にあたるのでこれは有り得ない。 
|/| | | | |      
+-+-+-+-+-+  

結局、再帰探索中において下図の X への配置を禁止する枝刈りを入れておけば、得られる解は総てユニーク解であることが保証されます。

+-+-+-+-+-+  
| | | |X|Q| 
+-+-+-+-+-+  
| |Q| |X| | 
+-+-+-+-+-+  
| | | |X| |       
+-+-+-+-+-+             
| | | |Q| | 
+-+-+-+-+-+ 
| | | | | |      
+-+-+-+-+-+  

次に右端以外にクイーンがある場合を考えてみます。
オリジナルがユニーク解であるためには先ず下図の X への配置は禁止されます。
よって、その枝刈りを先ず入れておきます。

+-+-+-+-+-+-+-+-+  
|X|X| | | |Q|X|X| 
+-+-+-+-+-+-+-+-+  
|X| | | | | | |X| 
+-+-+-+-+-+-+-+-+  
| | | | | | | | |       
+-+-+-+-+-+-+-+-+             
| | | | | | | | | 
+-+-+-+-+-+-+-+-+ 
| | | | | | | | |      
+-+-+-+-+-+-+-+-+  
| | | | | | | | |
+-+-+-+-+-+-+-+-+
|X| | | | | | |X|
+-+-+-+-+-+-+-+-+
|X|X| | | | |X|X|
+-+-+-+-+-+-+-+-+

次にクイーンの利き筋を辿っていくと、結局、オリジナルがユニーク解ではない可能性があるのは、下図の A,B,C の位置のどこかにクイーンがある場合に限られます。

+-+-+-+-+-+-+-+-+  
|X|X| | | |Q|X|X| 
+-+-+-+-+-+-+-+-+  
|X| | | |x|x|x|X| 
+-+-+-+-+-+-+-+-+  
|C| | |x| |x| |x|       
+-+-+-+-+-+-+-+-+             
| | |x| | |x| | | 
+-+-+-+-+-+-+-+-+ 
| |x| | | |x| | |      
+-+-+-+-+-+-+-+-+  
|x| | | | |x| |A|
+-+-+-+-+-+-+-+-+
|X| | | | |x| |X|
+-+-+-+-+-+-+-+-+
|X|X|B| | |x|X|X|
+-+-+-+-+-+-+-+-+

従って、90度回転、180度回転、270度回転の3通りの変換パターンだけを調べれはよいことになります。

ユニーク解を数える

これまでの考察はユニーク解の個数を求めるためのものでした。
全解数を求めるにはユニーク解を求めるための枝刈りを取り除いて全探索する必要があります。
したがって探索時間を犠牲にしてしまうことになります。
そこで「ユニーク解の個数から全解数を導いてしまおう」という試みが考えられます。
これは、左右反転(前章のミラー)によるパターンの探索を省略して最後に結果を2倍するというアイデアの拡張版といえるものです。
そしてそれを実現させるには「ユニーク解が属するグループの要素数はいくつあるのか」という考察が必要になってきます。

最初に、クイーンが右上角にあるユニーク解を考えます。

+-+-+-+-+-+  
| | | | |Q| 
+-+-+-+-+-+  
| | | | | | 
+-+-+-+-+-+  
| | | | | |       
+-+-+-+-+-+             
| | | | | | 
+-+-+-+-+-+ 
| | | | | |      
+-+-+-+-+-+  

斜軸で反転したパターンがオリジナルと同型になることは有り得ないことと(×2)、

+-+-+-+-+-+  +-+-+-+-+-+ 
| | | | |Q|  | | | | |Q| 
+-+-+-+-+-+  +-+-+-+-+-+ 
| | |Q|/| |  | | | |/| | 
+-+-+-+-+-+  +-+-+-+-+-+ 
| | |/| | |  | | |/|Q| | 
+-+-+-+-+-+  +-+-+-+-+-+            
| |/| | | |  | |/| | | | 
+-+-+-+-+-+  +-+-+-+-+-+ 
|/| | | | |  |/| | | | |     
+-+-+-+-+-+  +-+-+-+-+-+ 

右上角のクイーンを他の3つの角に写像させることができるので(×4)、

+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| | | | |Q|  | | | | | |  | | | | | |  |Q| | | | | 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| | | | | |  | | | | | |  | | | | | |  | | | | | | 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| | | | | |  | | | | | |  | | | | | |  | | | | | |      
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+            
| | | | | |  | | | | | |  | | | | | |  | | | | | | 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| | | | | |  | | | | |Q|  |Q| | | | |  | | | | | |     
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 

このユニーク解が属するグループの要素数は必ず8個(=2×4)になります。

次に、クイーンが右上角以外にある場合は少し複雑になります。

(1) 90度回転させてオリジナルと同型になる場合、さらに90度回転(オリジナルから180度回転)させても、さらに90度回転(オリジナルから270度回転)させてもオリジナルと同型になる。

90度回転させても同じ場合は

+-+-+-+-+-+  +-+-+-+-+-+ 
| | | |Q| |  | | | |Q| | 
+-+-+-+-+-+  +-+-+-+-+-+ 
|Q| | | | |  |Q| | | | | 
+-+-+-+-+-+  +-+-+-+-+-+ 
| | |Q| | |  | | |Q| | | 
+-+-+-+-+-+  +-+-+-+-+-+            
| | | | |Q|  | | | | |Q| 
+-+-+-+-+-+  +-+-+-+-+-+ 
| |Q| | | |  | |Q| | | |     
+-+-+-+-+-+  +-+-+-+-+-+ 

さらに90度回転(原型から180度回転)させても

                 さらに90度回転
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| | | |Q| |  | | | |Q| |  | | | |Q| | 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
|Q| | | | |  |Q| | | | |  |Q| | | | | 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| | |Q| | |  | | |Q| | |  | | |Q| | | 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+            
| | | | |Q|  | | | | |Q|  | | | | |Q| 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| |Q| | | |  | |Q| | | |  | |Q| | | |     
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 

さらに90度回転(原型から270度回転)させても同じ!

                               さらに90度回転
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| | | |Q| |  | | | |Q| |  | | | |Q| |  | | | |Q| | 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
|Q| | | | |  |Q| | | | |  |Q| | | | |  |Q| | | | | 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| | |Q| | |  | | |Q| | |  | | |Q| | |  | | |Q| | | 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+            
| | | | |Q|  | | | | |Q|  | | | | |Q|  | | | | |Q| 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| |Q| | | |  | |Q| | | |  | |Q| | | |  | |Q| | | |     
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 


(2) 90度回転させてオリジナルと異なる場合は、270度回転させても必ずオリジナルとは異なる。
ただし、180度回転させた場合はオリジナルと同型になることも有り得る。

ユニーク数から全解を求める

1.クイーンが右上角にある場合、ユニーク解が属するグループの要素数は必ず8個(=2×4)

2.クイーンが右上角以外にある場合、
(1) 90度回転させてオリジナルと同型になる場合、さらに90度回転(オリジナルから180度回転)させても、
さらに90度回転(オリジナルから270度回転)させてもオリジナルと同型になる。
こちらに該当するユニーク解が属するグループの要素数は、左右反転させたパターンを加えて2個しかありません。

(2) 90度回転させてオリジナルと異なる場合は、270度回転させても必ずオリジナルとは異なる。
ただし、180度回転させた場合はオリジナルと同型になることも有り得る。
こちらに該当するユニーク解が属するグループの要素数は、180度回転させて同型になる場合は4個(左右反転×縦横回転)

(3)180度回転させてもオリジナルと異なる場合は、8個(左右反転×縦横回転×上下反転)

1の場合は、解の数x8
2−(1)の場合は、解の数x2
2−(2)の場合は、解の数x4
2−(3)の場合は、解の数x8
それぞれのグループの解の数にグループのパターン数をかけ合わせた数の総計が全解となります。
ひとつひとつのユニーク解が上のどの種類に該当するのかを調べることにより全解数を計算で導き出すことができるのです。

対象解除法について

対象解除法のソースは4つの関数で構成されています。

1.printRecord()
解を発見するたびにボードレイアウトを表示します。
以降、新しい枝刈りポイントを発見するためにも使います。
board[]を使ってQの位置を確認するのに役立ちます。

2.symmetryOps()
対象解除を行うロジックメソッドです。
以下のロジックが同梱されています。

1.クイーンが右上角にある場合、ユニーク解が属するグループの要素数は必ず8個(=2×4)
2.クイーンが右上角以外にある場合、

(1) 90度回転させてオリジナルと同型になる場合、さらに90度回転(オリジナルから180
度回転)させても、さらに90度回転(オリジナルから270度回転)させてもオリジナルと同
型になる。
こちらに該当するユニーク解が属するグループの要素数は、左右反転させたパター ンを
加えて2個しかありません。
(2) 90度回転させてオリジナルと異なる場合は、270度回転させても必ずオリジナルとは
  異なる。ただし、180度回転させた場合はオリジナルと同型になることも有り得る。
こちらに該当するユニーク解が属するグループの要素数は、180度回転させて同型になる
  場合は4個(左右反転×縦横回転)
(3)180度回転させてもオリジナルと異なる場合は、8個(左右反転×縦横回転×上下反転)

3.backTrack()
再帰ロジックで作られています。
右上角にQが配置されているかいないかで判定処理が分岐されます。
その判定処理に従って、見合った枝刈りが行われます。

4.symmetry()
本体メソッドです。
こちらのメソッドでは、Qが角に配置されている場合、そうでない場合を分岐して3のbackTrack()に渡します。

symmetry()メソッド

角にQがある時の処理

symmetry()メソッドは、Qが角にあるかどうかを判定し、適切にbackTrack()に処理を渡します。

では上から順番に見ていきます。
とても大切なことなのですが、グローバル変数の初期化を行います。

  TOTAL=UNIQUE=COUNT2=COUNT4=COUNT8=0

角にQがある時のボードのイメージは以下のとおりです。

  角にQがある時の処理
    +-+-+-+-+-+  
    | | | | |Q| 
    +-+-+-+-+-+  
    | | | | | | 
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

MASKについてですが、こちらはrow全体にビットを立てて配置済みとするフィルタのことです。

N5の場合、 10進数では31、2進数では 11111 となります。

mask=(1<<size)-1
    +-+-+-+-+-+  
    |M|M|M|M|M|  mask=(1<<size)-1
    +-+-+-+-+-+  $(( (1<<5)-1 )):31
    | | | | | |  $(( 2#11111 )) 
    +-+-+-+-+-+  $ 31
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

bashでは以下のようにすると2進数を10進数に変換して表示することができます。

$ echo $(( 2#11111 ))
$ 31

TOPBITは左上角にビットを立てたものです。

  TOPBIT=1<<(size-1)
    +-+-+-+-+-+  
    |T| | | | |  TOPBIT=1<<(size-1)
    +-+-+-+-+-+  $(( (1<<(5-1) )):16 
    | | | | | |  $(( 2#10000 )) 
    +-+-+-+-+-+  $ 16 
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

ENDBITはTOPBITの1つ右にビットを立てたものです。現在はまだ初期値0のままです。
後述しますがENDBITは、

ENDBIT=LASTMASK=SIDEMASK=0

となります。

  ENDBIT=LASTMASK=SIDEMASK=0
    は何も置かないなにもはじまってない
    +-+-+-+-+-+  
    |T|E| | | |  0#01000
    +-+-+-+-+-+  
    | | | | | | 
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

配列BOUND1とBOUND2を初期化して
Qを右上角に配置します。

  BOUND1=2
  BOUND2=0
    +-+-+-+-+-+  右角から1つ左から開始
    | | | |B| |  BOUND1は右から左へシフト
    +-+-+-+-+-+   
    | | | | | | 
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  
    +-+-+-+-+-+  左角から1つ右から開始
    | |B| | | |  BOUND2は左から右へシフトします。
    +-+-+-+-+-+  
    | | | | | | 
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

ここはすごく重要で、row[0]の1というのは右上の角のことを意味します。ですので、「Qを右上角に配置した」ということになります。

  board[0]=1       # Qを右上角に配置

ここで、rowは2行目の処理に入ります。
というのも、1行目は右上角にQを配置したので次に進んだわけです。

その後、以下の行は「2行目の真ん中にQを配置する」という意味となります。

      bit=1<<BOUND1

イメージでは以下のとおりです。

       N5の場合BOUND1は2と3になる
        1<<BOUND1  行目は真ん中に置く
        +-+-+-+-+-+  
        | | | | |Q|  1#00001
        +-+-+-+-+-+  
        | | |B| | |  BOUND1=2 4#00100
        +-+-+-+-+-+  $(( 1<<2 ))
        | | | | | |  $ 4
        +-+-+-+-+-+  1->2->4               
        | | | | | | 
        +-+-+-+-+-+ 
        | | | | | |      
        +-+-+-+-+-+  

さらに、処理の中でBOUND1は左に1つシフトします。

        1<<BOUND1    行目は真ん中の次に置く
        +-+-+-+-+-+  
        | | | | |Q|  1#00001
        +-+-+-+-+-+  
        | |B| | | |  BOUND1=3 8#01000
        +-+-+-+-+-+  $(( 1<<3 ))
        | | | | | |  $ 8
        +-+-+-+-+-+  1->2->4->8             
        | | | | | | 
        +-+-+-+-+-+ 
        | | | | | |      
        +-+-+-+-+-+  

角にQがない時の処理

TOPBITは先程も書いたとおり、左上角にビットを立てた状態を示します。

  TOPBIT=1<<(size-1)
    +-+-+-+-+-+  
    |T| | | | | TOPBIT=1<<(size-1)
    +-+-+-+-+-+ $(( 2#10000 )) 
    | | | | | | $ 16 
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

ENDBITは、TOPBITの1つ右にビットを立てた状態です。

  ENDBIT=TOPBIT>>1
    +-+-+-+-+-+  
    |T|E| | | | ENDBIT=TOPBIT>>1
    +-+-+-+-+-+ $(( 2#01000 )) 
    | | | | | | $ 8
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

SIDEMASKは行の両サイドの角にビットを立てた状態を示します。

  SIDEMASK=TOPBIT|1
    +-+-+-+-+-+  
    |S| | | |S| SIDEMASK=TOPBIT|1
    +-+-+-+-+-+ $(( 2#10001 )) 
    | | | | | | $ 17
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

LASTMASKもSIDEMASK同様左右の角のビットを立てた状態を示します。ただ使いみちとしては、上記の状態でも使えるわけですが、

  LASTMASK=TOPBIT|1
    +-+-+-+-+-+  
    |L| | | |L| LASTMASK=TOPBIT|1
    +-+-+-+-+-+ $(( 2#10001 )) 
    | | | | | | $ 17
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

こういうシチュエーションで使います。

  LASTMASK=$(( TOPBIT|1 )); 
    +-+-+-+-+-+  
    | | | | | | LASTMASK=$(( TOPBIT|1 ))
    +-+-+-+-+-+ $(( 2#10001 )) 
    | | | | | | $ 17
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    |L| | | |L|      
    +-+-+-+-+-+  

ENDBITは左角の1つ手前から右へシフトするビットです。

    ENDBIT=ENDBIT>>1
      +-+-+-+-+-+  
      |T|E| | | | ENDBIT=TOPBIT>>1
      +-+-+-+-+-+ $(( 2#01000 )) 
      | | | | | | $ 8
      +-+-+-+-+-+  
      | | | | | |       
      +-+-+-+-+-+             
      | | | | | | 
      +-+-+-+-+-+ 
      | | | | | |      
      +-+-+-+-+-+  
           
      +-+-+-+-+-+  
      |T| |E| | | ENDBIT=ENDBIT>>1
      +-+-+-+-+-+ $(( 2#00100 )) 
      | | | | | | $ 4
      +-+-+-+-+-+  
      | | | | | |       
      +-+-+-+-+-+             
      | | | | | | 
      +-+-+-+-+-+ 
      | | | | | |      
      +-+-+-+-+-+  

LASTMASKに代入されるビットは以下のイメージとなります。使いみちはLASTMASKと似ていますが、

    +-+-+-+-+-+  
    |L|L| |L|L| LASTMASK=LASTMASK<<1 | LASTMASK | LASTMASK>>1
    +-+-+-+-+-+ $(( 2#11011 )) 
    | | | | | | $ 27
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

こういったシチュエーションにも使います。

    +-+-+-+-+-+  
    | | | | | | LASTMASK=LASTMASK<<1 | LASTMASK | LASTMASK>>1
    +-+-+-+-+-+ $(( 2#11011 )) 
    | | | | | | $ 27
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    |L|L| |L|L|      
    +-+-+-+-+-+  

backTrack()メソッド

角にQがある時の処理

このメソッドでは、角にQがある場合の処理、ない場合の処理の分岐があり、その分岐の中で、((COUNT8++))であったり、symmetryOpsといった対象解除の処理へ進んだり、はたまた「枝刈り」をしたりと、濃厚なメソッドです。

では、順番に見ていきます。

ここは、Qが角にある場合の処理で、rowが最終行までたどり着いた(配置できた)場合の処理となります。グループの要素数は必ず8あるわけですので、あとから8倍するCOUNT8をインクリメントします。

  """
  1.クイーンが右上角にある場合、ユニーク解が属する
  グループの要素数は必ず8個(=2×4)
  """
  if row==(size-1):
    if bitmap :
      board[row]=bitmap
      if DISPLAY :
        printRecord_bitmap(size,1)
      COUNT8+=1

次は、Qが角にある場合の処理で、
上から2行目のクイーンの位置が左から何番目にあるかと、
右から2列目のクイーンの位置が上から何番目にあるかを、
比較するだけで判定します。
具体的には、2行目と2列目の位置を数値とみなし、
2行目<2列目という条件を課せばよい
という枝仮ルールを実現している処理となります。

  else:
    if row<BOUND1:    # 枝刈り
      bitmap=bitmap|2
      bitmap=bitmap^2
    while bitmap:
    """
    上から2行目のクイーンの位置が左から何番目にあるかと、
    右から2列目のクイーンの位置が上から何番目にあるかを、
    比較するだけで判定します。
    具体的には、2行目と2列目の位置を数値とみなし、
    2行目<2列目という条件を課せばよい
    結論: 以下の図では、1,2,4を枝刈りを入れる
      
      +-+-+-+-+-+  
      | | | |X|Q| 
      +-+-+-+-+-+  
      | |Q| |X| |  8(左から数えて1,2,4,8)
      +-+-+-+-+-+  
      | | | |X| |       
      +-+-+-+-+-+             
      | | | |Q| |  8(上から数えて1,2,4,8) 
      +-+-+-+-+-+ 
      | | | | | |      
      +-+-+-+-+-+  
    """

たったこれだけの2行ですごいですね。

      bitmap=bitmap|2
      bitmap=bitmap^2

角にQがない時の処理

ここは少しわかりにくいですね。丁寧に説明文を入れましたので、じっくりと理解してみてください。

    else                            # Qが角にない
      : '
      オリジナルがユニーク解であるためには先ず
      前提symmetryOpsは回転鏡像変換により得られる状態の
      ユニーク値を比較し最小のものだけがユニーク解となるようにしている
      Qができるだけ右に置かれている方がユニーク値は小さい
      例えば1行目の2列目にQが置かれている方が3列目に置かれているより
      ユニーク値は小さくユニーク解に近い
      1行目のクイーンの位置が同じなら2行目のクイーンの位置がより右の
      列におかれているものがユニーク値は小さくユニーク解に近い

      下図の X への配置は禁止されます
      Qの位置より右位置の8対象位置Xにクイーンを置くことはできない
      置いた場合回転鏡像変換したユニーク値が最小にならなくなりsymmetryOps
      で負けるので枝刈りをする


      1行目のクイーンが3列目に置かれている場合
      +-+-+-+-+-+-+-+-+  
      |X|X| | | |Q|X|X| 
      +-+-+-+-+-+-+-+-+  
      |X| | | | | | |X| 
      +-+-+-+-+-+-+-+-+  
      | | | | | | | | |       
      +-+-+-+-+-+-+-+-+             
      | | | | | | | | | 
      +-+-+-+-+-+-+-+-+ 
      | | | | | | | | |      
      +-+-+-+-+-+-+-+-+  
      | | | | | | | | |
      +-+-+-+-+-+-+-+-+
      |X| | | | | | |X|
      +-+-+-+-+-+-+-+-+
      |X|X| | | | |X|X|
      +-+-+-+-+-+-+-+-+

      1行目のクイーンが4列目に置かれている場合
      +-+-+-+-+-+-+-+-+  
      |X|X|X| |Q|X|X|X| 
      +-+-+-+-+-+-+-+-+  
      |X| | | | | | |X| 
      +-+-+-+-+-+-+-+-+  
      |X| | | | | | |X|       
      +-+-+-+-+-+-+-+-+             
      | | | | | | | | | 
      +-+-+-+-+-+-+-+-+ 
      | | | | | | | | |      
      +-+-+-+-+-+-+-+-+  
      |X| | | | | | |X|
      +-+-+-+-+-+-+-+-+
      |X| | | | | | |X|
      +-+-+-+-+-+-+-+-+
      |X|X|X| | |X|X|X|
      +-+-+-+-+-+-+-+-+

      プログラムではこの枝刈を上部サイド枝刈り下部サイド枝刈り最下段枝刈り
      の3か所で行っている
      それぞれ1,2,3の数字で表すと以下の通り

      1行目のクイーンが3列目に置かれている場合
      +-+-+-+-+-+-+-+-+  
      |X|X| | | |Q|X|X| 
      +-+-+-+-+-+-+-+-+  
      |1| | | | | | |1| 
      +-+-+-+-+-+-+-+-+  
      | | | | | | | | |       
      +-+-+-+-+-+-+-+-+             
      | | | | | | | | | 
      +-+-+-+-+-+-+-+-+ 
      | | | | | | | | |      
      +-+-+-+-+-+-+-+-+  
      | | | | | | | | |
      +-+-+-+-+-+-+-+-+
      |2| | | | | | |2|
      +-+-+-+-+-+-+-+-+
      |2|3| | | | |3|2|
      +-+-+-+-+-+-+-+-+
      1行目にXが残っているが当然Qの効き筋なので枝刈する必要はない
      ';
    if row<BOUND1:          # 上部サイド枝刈り
      bitmap=bitmap|SIDEMASK
      bitmap=bitmap^SIDEMASK
    else:
      if row==BOUND2:       # 下部サイド枝刈り
        if not down&SIDEMASK:
          return
        if down&SIDEMASK!=SIDEMASK:
          bitmap=bitmap&SIDEMASK

symmetryOps()メソッド

ここもロジックを理解するところから初めて見ると良いと思います。
あまり細かいことを考えるよりも、大枠でどんなときにこのメソッドが呼ばれ、カウンターがいつインクリメントするのか、などを掴んだほうが良いと思います。

symmetryOps()では以下を実現しています。

2.クイーンが右上角以外にある場合、
(1) 90度回転させてオリジナルと同型になる場合、さらに90度回転(オリジナルか
ら180度回転)させても、さらに90度回転(オリジナルから270度回転)させてもオリ
ジナルと同型になる。
こちらに該当するユニーク解が属するグループの要素数は、左右反転させたパター
ンを加えて2個しかありません。
2.クイーンが右上角以外にある場合、
(2) 90度回転させてオリジナルと異なる場合は、270度回転させても必ずオリジナル
とは異なる。ただし、180度回転させた場合はオリジナルと同型になることも有り得
る。こちらに該当するユニーク解が属するグループの要素数は、180度回転させて同
型になる場合は4個(左右反転×縦横回転)
2.クイーンが右上角以外にある場合、
(3)180度回転させてもオリジナルと異なる場合は、8個(左右反転×縦横回転×上下反転)

クイーンの利き筋を辿っていくと、オリジナルがユニーク解ではない可能性があり、
それは下図の A,B,C の位置のどこかにクイーンがある場合に限られます。
symmetryOpsは、以下の図のA,B,CにQが置かれた場合にユニーク解かを判定します。
原型と、90,180,270回転させたもののユニーク値を比較します。

     0 1 2 3 4 
    +-+-+-+-+-+  
    | | | | |Q|    4
    +-+-+-+-+-+  
    | | |Q| | |    2 
    +-+-+-+-+-+  
    |Q| | | | |    0  ----> 4 2 0 3 1 (ユニーク判定値)
    +-+-+-+-+-+             数が大きい方をユニークとみなす 
    | | | |Q| |    3
    +-+-+-+-+-+  
    | |Q| | | |    1 
    +-+-+-+-+-+  

     0 1 2 3 4     左右反転!
    +-+-+-+-+-+  
    |Q| | | | |    0
    +-+-+-+-+-+  
    | | |Q| | |    2 
    +-+-+-+-+-+  
    | | | | |Q|    4  ----> 0 2 4 1 3 
    +-+-+-+-+-+            数が小さいのでユニーク解とはしません 
    | |Q| | | |    1
    +-+-+-+-+-+  
    | | | |Q| |    3 
    +-+-+-+-+-+  

Qができるだけ右に置かれている方がユニーク値は大きくなります。
例えば1行目の2列目にQが置かれている方が、
3列目に置かれているよりユニーク値は大きくユニーク解に近い。
1行目のクイーンの位置が同じなら2行目のクイーンの位置がより右の列におかれてい
るものがユニーク値は大きくユニーク解に近くなります。
それ以外はユニーク解なのでCOUNT8にする

   +-+-+-+-+-+-+-+-+  
   |X|X| | | |Q|X|X| 
   +-+-+-+-+-+-+-+-+  
   |X| | | |x|x|x|X| 
   +-+-+-+-+-+-+-+-+  
   |C| | |x| |x| |x|       
   +-+-+-+-+-+-+-+-+             
   | | |x| | |x| | | 
   +-+-+-+-+-+-+-+-+ 
   | |x| | | |x| | |      
   +-+-+-+-+-+-+-+-+  
   |x| | | | |x| |A|
   +-+-+-+-+-+-+-+-+
   |X| | | | |x| |X|
   +-+-+-+-+-+-+-+-+
   |X|X|B| | |x|X|X|
   +-+-+-+-+-+-+-+-+

Aの場合 右90度回転 board[BOUND2]==1
Bの場合 右180度回転 board[size-1]==ENDBIT
Cの場合 右270度回転 board[BOUND1]==TOPBIT

ソースコード

ソースコードは以下のとおりです。

#!/usr/bin/env python3

# -*- coding: utf-8 -*-

"""
対象解除版 Nクイーン

詳細はこちら。
【参考リンク】Nクイーン問題 過去記事一覧はこちらから
https://suzukiiichiro.github.io/search/?keyword=Nクイーン問題

エイト・クイーンのプログラムアーカイブ
Bash、Lua、C、Java、Python、CUDAまで!
https://github.com/suzukiiichiro/N-Queens

# 実行 
$ python <filename.py>

# 実行結果
1
 0 2 4 1 3
+-+-+-+-+-+
|O| | | | |
+-+-+-+-+-+
| | |O| | |
+-+-+-+-+-+
| | | | |O|
+-+-+-+-+-+
| |O| | | |
+-+-+-+-+-+
| | | |O| |
+-+-+-+-+-+

2
 0 3 1 4 2
+-+-+-+-+-+
|O| | | | |
+-+-+-+-+-+
| | | |O| |
+-+-+-+-+-+
| |O| | | |
+-+-+-+-+-+
| | | | |O|
+-+-+-+-+-+
| | |O| | |
+-+-+-+-+-+

3
 1 3 0 2 4
+-+-+-+-+-+
| |O| | | |
+-+-+-+-+-+
| | | |O| |
+-+-+-+-+-+
|O| | | | |
+-+-+-+-+-+
| | |O| | |
+-+-+-+-+-+
| | | | |O|
+-+-+-+-+-+

4
 1 4 2 0 3
+-+-+-+-+-+
| |O| | | |
+-+-+-+-+-+
| | | | |O|
+-+-+-+-+-+
| | |O| | |
+-+-+-+-+-+
|O| | | | |
+-+-+-+-+-+
| | | |O| |
+-+-+-+-+-+

5
 2 4 3 1 4
+-+-+-+-+-+
| | |O| | |
+-+-+-+-+-+
| | | | |O|
+-+-+-+-+-+
| | | |O| |
+-+-+-+-+-+
| |O| | | |
+-+-+-+-+-+
| | | | |O|
+-+-+-+-+-+

size: 5 TOTAL: 10 COUNT2: 5

"""

#
# グローバル変数
MAX=21                          # ボードサイズ最大値
TOTAL=0                         # 解
UNIQUE=0
COUNT2=0                        # ミラー
COUNT4=0
COUNT8=0
BOUND1=0
BOUND2=0
TOPBIT=0
ENDBIT=0
LASTMASK=0
SIDEMASK=0
board=[0 for i in range(MAX)]   # ボード配列格納用
down=[0 for i in range(MAX)]    # 効き筋チェック
left=[0 for i in range(MAX)]    # 効き筋チェック
right=[0 for i in range(MAX)]   # 効き筋チェック
#
# ビットマップ版ボードレイアウト出力
def printRecord_bitmap(size,flag):
  global TOTAL
  global board
  print(TOTAL)
  sEcho=""
  """
  ビットマップ版
     ビットマップ版からは、左から数えます
     上下反転左右対称なので、これまでの上から数える手法と
     rowを下にたどって左から数える方法と解の数に変わりはありません。
     0 2 4 1 3 
    +-+-+-+-+-+
    |O| | | | | 0
    +-+-+-+-+-+
    | | |O| | | 2
    +-+-+-+-+-+
    | | | | |O| 4
    +-+-+-+-+-+
    | |O| | | | 1
    +-+-+-+-+-+
    | | | |O| | 3
    +-+-+-+-+-+
  """
  if flag:
    for i in range(size):
      for j in range(size):
        if board[i]&1<<j:
          sEcho+=" " + str(j)
  else:
    """
    ビットマップ版以外
    (ブルートフォース、バックトラック、配置フラグ)
    上から数えます
       0 2 4 1 3 
      +-+-+-+-+-+
      |O| | | | |
      +-+-+-+-+-+
      | | | |O| |
      +-+-+-+-+-+
      | |O| | | |
      +-+-+-+-+-+
      | | | | |O|
      +-+-+-+-+-+
      | | |O| | |
      +-+-+-+-+-+
    """
    for i in range(size):
      sEcho+=" " + str(board[i])
  print(sEcho)

  print ("+",end="")
  for i in range(size):
    print("-",end="")
    if i<(size-1):
      print("+",end="")
  print("+")
  for i in range(size):
    print("|",end="")
    for j in range(size):
      if flag:
        if board[i]&1<<j:
          print("O",end="")
        else:
          print(" ",end="")
      else:
        if i==board[j]:
          print("O",end="")
        else:
          print(" ",end="")
      if j<(size-1):
        print("|",end="")
    print("|")
    if i in range(size-1):
      print("+",end="")
      for j in range(size):
        print("-",end="")
        if j<(size-1):
          print("+",end="")
      print("+")
  print("+",end="")
  for i in range(size):
    print("-",end="")
    if i<(size-1):
      print("+",end="")
  print("+")
  print("")
# 
# ボードレイアウト出力
def printRecord(size):
  global TOTAL
  global board
  print(TOTAL)
  sEcho=""
  for i in range(size):
    sEcho+=" " + str(board[i])
  print(sEcho)
  print ("+",end="")
  for i in range(size):
    print("-",end="")
    if i<(size-1):
      print("+",end="")
  print("+")
  for i in range(size):
    print("|",end="")
    for j in range(size):
      if i==board[j]:
        print("O",end="")
      else:
        print(" ",end="")
      if j<(size-1):
        print("|",end="")
    print("|")
    if i in range(size-1):
      print("+",end="")
      for j in range(size):
        print("-",end="")
        if j<(size-1):
          print("+",end="")
      print("+")
  print("+",end="")
  for i in range(size):
    print("-",end="")
    if i<(size-1):
      print("+",end="")
  print("+")
  print("")
#
# 対象解除法 対象解除ロジック
def symmetryOps(size):
  global BOUND1,BOUND2
  global TOPBIT,ENDBIT
  global COUNT2,COUNT4,COUNT8
  """
  2.クイーンが右上角以外にある場合、
  (1) 90度回転させてオリジナルと同型になる場合、さらに90度回転(オリジナルか
  ら180度回転)させても、さらに90度回転(オリジナルから270度回転)させてもオリ
  ジナルと同型になる。
  こちらに該当するユニーク解が属するグループの要素数は、左右反転させたパター
  ンを加えて2個しかありません。
  """
  if board[BOUND2]==1:
    ptn=2
    own=1
    while own<size:
      bit=1
      you=size-1
      while (board[you]!=ptn) and (board[own]>=bit):
        bit<<=1
        you-=1
      if board[own]>bit :
        return 
      if board[own]<bit :
        break
      own+=1
      ptn<<=1 
    #90度回転して同型なら180度回転も270度回転も同型である
    if own>size-1:
      COUNT2+=1
      if DISPLAY==1:
        printRecord_bitmap(size,1)
      return 
  """
  2.クイーンが右上角以外にある場合、
    (2) 90度回転させてオリジナルと異なる場合は、270度回転させても必ずオリジナル
    とは異なる。ただし、180度回転させた場合はオリジナルと同型になることも有り得
    る。こちらに該当するユニーク解が属するグループの要素数は、180度回転させて同
    型になる場合は4個(左右反転×縦横回転)
  """
  # 180度回転
  if board[size-1]==ENDBIT:
    you=size-1-1
    own=1
    while own<size:
      bit=1
      ptn=TOPBIT
      while (ptn!=board[you]) and (board[own]>=bit):
        bit<<=1
        ptn>>=1
      if board[own]>bit:
        return 
      if board[own]<bit:
        break
      own+=1
      you-=1
    #90度回転が同型でなくても180度回転が同型であることもある
    if own>size-1:
      COUNT4+=1
      if DISPLAY==1:
        printRecord_bitmap(size,1)
      return
  """
  2.クイーンが右上角以外にある場合、
    (3)180度回転させてもオリジナルと異なる場合は、8個(左右反転×縦横回転×上下反転)
  """
  # 270度回転
  if board[BOUND1]==TOPBIT:
    ptn=TOPBIT>>1
    own=1
    while own<=size-1:
      bit=1
      you=0
      while (board[you]!=ptn) and (board[own]>=bit):
        bit<<=1
        you+=1
      if board[own]>bit:
        return 
      if board[own]<bit:
        break
      own+=1
      ptn>>=1
    COUNT8+=1
    if DISPLAY==1:
      printRecord_bitmap(size,1)
#
# 対象解除法 角にQがないときのバックトラック
def symmetry_backTrack(size,row,left,down,right):
  mask=(1<<size)-1
  bitmap=mask&~(left|down|right)
  if row==(size-1):
    if bitmap:
      if not  bitmap&LASTMASK:
        board[row]=bitmap   # Qを配置
        symmetryOps(size)   # 対象解除
  else:
    if row<BOUND1:          # 上部サイド枝刈り
      bitmap=bitmap|SIDEMASK
      bitmap=bitmap^SIDEMASK
    else:
      if row==BOUND2:       # 下部サイド枝刈り
        if not down&SIDEMASK:
          return
        if down&SIDEMASK!=SIDEMASK:
          bitmap=bitmap&SIDEMASK
    while bitmap:
      bit=-bitmap&bitmap
      bitmap=bitmap^bit
      board[row]=bit        # Qを配置
      symmetry_backTrack(size,row+1,(left|bit)<<1,down|bit,(right|bit)>>1)
#
# 対象解除法 角にQがあるときのバックトラック
def symmetry_backTrack_corner(size,row,left,down,right):
  global BOUND1
  global COUNT8
  mask=(1<<size)-1
  bitmap=mask&~(left|down|right)
  if row==(size-1):
    """
    1.クイーンが右上角にある場合、ユニーク解が属する
    グループの要素数は必ず8個(=2×4)
    """
    if bitmap :
      board[row]=bitmap
      if DISPLAY :
        printRecord_bitmap(size,1)
      COUNT8+=1
  else:
    if row<BOUND1:    # 枝刈り
      bitmap=bitmap|2
      bitmap=bitmap^2
    while bitmap:
      bit=-bitmap&bitmap
      bitmap=bitmap^bit
      board[row]=bit
      symmetry_backTrack_corner(size,row+1,(left|bit)<<1,down|bit,(right|bit)>>1)
#
# 対象解除法
def symmetry(size):
  global TOTAL,UNIQUE,COUNT2,COUNT4,COUNT8
  global BOUND1,BOUND2
  global TOPBIT,ENDBIT,LASTMASK,SIDEMASK
  TOTAL=UNIQUE=COUNT2=COUNT4=COUNT8=0
  mask=(1<<size)-1
  TOPBIT=1<<(size-1)
  ENDBIT=LASTMASK=SIDEMASK=0
  BOUND1=2
  BOUND2=0
  board[0]=1
  while BOUND1>1 and BOUND1<size-1 :
    if BOUND1<size-1:
      bit=1<<BOUND1
      board[1]=bit    # 2行目にQを配置
      # 角にQがあるときのバックトラック
      symmetry_backTrack_corner(size,2,(2|bit)<<1,1|bit,(2|bit)>>1)
    BOUND1+=1
  TOPBIT=1<<(size-1)
  ENDBIT=TOPBIT>>1
  SIDEMASK=TOPBIT|1
  LASTMASK=TOPBIT|1
  BOUND1=1
  BOUND2=size-2
  while BOUND1>0 and BOUND2<size-1 and BOUND1<BOUND2:
    if BOUND1<BOUND2:
      bit=1<<BOUND1
      board[0]=bit    # Qを配置
      # 角にQがないときのバックトラック
      symmetry_backTrack(size,1,bit<<1,bit,bit>>1)
    BOUND1+=1
    BOUND2-=1
    ENDBIT=ENDBIT>>1
    LASTMASK=LASTMASK<<1|LASTMASK|LASTMASK>>1
  UNIQUE=COUNT8+COUNT4+COUNT2
  TOTAL=COUNT8*8 + COUNT4*4 + COUNT2*2
  print("TOTAL:" + str(TOTAL) + " COUNT2:"+str(COUNT2)+ " COUNT4:"+str(COUNT4)+" COUNT8:"+str(COUNT8))
#
# ミラーソルバー
def mirror_solver(size,row,left,down,right):
  global COUNT2
  global TOTAL
  mask=(1<<size)-1
  bit=0
  bitmap=0
  if row==size:
    COUNT2+=1
    TOTAL=TOTAL+1 # ボードレイアウト出力用
    printRecord_bitmap(size,1)
  else:
    # Qが配置可能な位置を表す
    bitmap=mask&~(left|down|right)
    while bitmap:
      bit=-bitmap&bitmap  # 一番右のビットを取り出す
      bitmap=bitmap^bit   # 配置可能なパターンが一つずつ取り出される
      board[row]=bit      # Qを配置
      mirror_solver(size,row+1,(left|bit)<<1,down|bit,(right|bit)>>1)
#
# ミラー
def Mirror(size):
  global COUNT2
  mask=(1<<size)-1
  bit=0
  """
  if size%2 :
    limit=size//2-1
  else:
    limit=size//2
  """
  limit=size%2 if size/2-1 else size/2
  # pythonでは割り算の切り捨ては`//`です
  for i in range(size//2):  # 奇数でも偶数でも通過
    bit=1<<i
    board[0]=bit            # 1行目にQを配置
    mirror_solver(size,1,bit<<1,bit,bit>>1)
  if size%2:                # 奇数で通過
    # Pythonでの割り算の切り捨ては`//`です
    bit=1<<(size-1)//2
    board[0]=1<<(size-1)//2 # 1行目の中央にQを配置
    left=bit<<1
    down=bit
    right=bit>>1
    for i in range(limit):
      bit=1<<i
      mirror_solver(size,2,(left|bit)<<1,down|bit,(right|bit)>>1)
  TOTAL=COUNT2<<1          # 倍にする
  print("size:",size,"TOTAL:",TOTAL,"COUNT2:",COUNT2)
#
# ビットマップ
def Bitmap(size,row,left,down,right):
  global TOTAL
  bitmap=0
  bit=0
  col=0
  mask=(1<<size)-1
  if row==size:
    TOTAL=TOTAL+1
    printRecord_bitmap(size,1) # 1:bitmap版 0: それ以外
  else:
    bitmap=mask&~(left|down|right)
    while bitmap:
      bit=-bitmap&bitmap
      bitmap=bitmap&~bit
      board[row]=bit
      Bitmap(size,row+1,(left|bit)<<1, (down|bit),(right|bit)>>1)
#
# ポストフラグ
def postFlag(row,size):
  global TOTAL
  col=0
  if row==size:
    TOTAL=TOTAL+1
    printRecord(size)
  else:
    for col in range(size):
      board[row]=col
      if(down[col]==0 and 
          right[row-col+size-1]==0 and
          left[row+col]==0):
        down[col]=1
        right[row-col+(size-1)]=1
        left[row+col]=1
        postFlag(row+1,size)
        down[col]=0
        right[row-col+(size-1)]=0
        left[row+col]=0;
#
# バックトラック版効き筋をチェック
def check_backTracking(row):
  global board
  for i in range(row):
    if board[i]>=board[row]:
      val=board[i]-board[row]
    else:
      val=board[row]-board[i]
    if board[i]==board[row] or val==(row-i):
      return 0
  return 1
#
# バックトラック
def backTracking(row,size):
  global TOTAL
  col=0
  if row==size:
    TOTAL=TOTAL+1
    printRecord(size)
  else:
    for col in range(size):
      board[row]=col
      if check_backTracking(row)==1:
        backTracking(row+1,size)
#
# ブルートフォース版効き筋チェック
def check_bluteForce(size):
  global board
  for r in range(1,size,1):
    for i in range(r):
      if board[i]>=board[r]:
        val=board[i]-board[r]
      else:
        val=board[r]-board[i]
      if board[i]==board[r] or val==(r-i):
        return 0
  return 1
#
# ブルートフォース
def bluteForce(row,size):
  col=0
  global TOTAL
  global board
  if row==size:
    if check_bluteForce(size)==1:
      TOTAL=TOTAL+1
      printRecord(size)
  else:
    for col in range(size):
      board[row]=col
      bluteForce(row+1,size)
#
# 実行
# bluteForce(0,5)   # 1.ブルートフォース
# backTracking(0,5) # 2.バックトラッキング
# postFlag(0,5)     # 3.ポストフラグ
# Bitmap(5,0,0,0,0) # 4.ビットマップ
# Mirror(5)         # 5.ミラー
DISPLAY=1;          # 表示 1:出力する 0: 出力しない
symmetry(5)         # 6.対象解除法
#

実行結果

実行結果は以下のとおりです。

0
 0 2 4 1 3
+-+-+-+-+-+
|O| | | | |
+-+-+-+-+-+
| | |O| | |
+-+-+-+-+-+
| | | | |O|
+-+-+-+-+-+
| |O| | | |
+-+-+-+-+-+
| | | |O| |
+-+-+-+-+-+

0
 1 4 2 0 3
+-+-+-+-+-+
| |O| | | |
+-+-+-+-+-+
| | | | |O|
+-+-+-+-+-+
| | |O| | |
+-+-+-+-+-+
|O| | | | |
+-+-+-+-+-+
| | | |O| |
+-+-+-+-+-+

TOTAL:10 COUNT2:1 COUNT4:0 COUNT8:1

参考リンク

以下の詳細説明を参考にしてください。
【参考リンク】Nクイーン問題 過去記事一覧
【Github】エイト・クイーンのソース置き場 BashもJavaもPythonも!

Nクイーン問題(50)第七章 マルチプロセス Python編
https://suzukiiichiro.github.io/posts/2023-06-21-04-n-queens-suzuki/
Nクイーン問題(49)第七章 マルチスレッド Python編
https://suzukiiichiro.github.io/posts/2023-06-21-03-n-queens-suzuki/
Nクイーン問題(48)第七章 シングルスレッド Python編
https://suzukiiichiro.github.io/posts/2023-06-21-02-n-queens-suzuki/
Nクイーン問題(47)第七章 クラス Python編
https://suzukiiichiro.github.io/posts/2023-06-21-01-n-queens-suzuki/
Nクイーン問題(46)第七章 ステップNの実装 Python編
https://suzukiiichiro.github.io/posts/2023-06-16-02-n-queens-suzuki/
Nクイーン問題(45)第七章 キャリーチェーン Python編
https://suzukiiichiro.github.io/posts/2023-06-16-01-n-queens-suzuki/
Nクイーン問題(44)第七章 対象解除法 Python編
https://suzukiiichiro.github.io/posts/2023-06-14-02-n-queens-suzuki/
Nクイーン問題(43)第七章 ミラー Python編
https://suzukiiichiro.github.io/posts/2023-06-14-01-n-queens-suzuki/
Nクイーン問題(42)第七章 ビットマップ Python編
https://suzukiiichiro.github.io/posts/2023-06-13-05-n-queens-suzuki/
Nクイーン問題(41)第七章 配置フラグ Python編
https://suzukiiichiro.github.io/posts/2023-06-13-04-n-queens-suzuki/
Nクイーン問題(40)第七章 バックトラック Python編
https://suzukiiichiro.github.io/posts/2023-06-13-03-n-queens-suzuki/
Nクイーン問題(39)第七章 バックトラック準備編 Python編
https://suzukiiichiro.github.io/posts/2023-06-13-02-n-queens-suzuki/
Nクイーン問題(38)第七章 ブルートフォース Python編
https://suzukiiichiro.github.io/posts/2023-06-13-01-n-queens-suzuki/
Nクイーン問題(37)第六章 C言語移植 その17 pthread並列処理完成
https://suzukiiichiro.github.io/posts/2023-05-30-17-n-queens-suzuki/
Nクイーン問題(36)第六章 C言語移植 その16 pthreadの実装
https://suzukiiichiro.github.io/posts/2023-05-30-16-n-queens-suzuki/
Nクイーン問題(35)第六章 C言語移植 その15 pthread実装直前版完成
https://suzukiiichiro.github.io/posts/2023-05-30-15-n-queens-suzuki/
Nクイーン問題(34)第六章 C言語移植 その14
https://suzukiiichiro.github.io/posts/2023-05-30-14-n-queens-suzuki/
Nクイーン問題(33)第六章 C言語移植 その13
https://suzukiiichiro.github.io/posts/2023-05-30-13-n-queens-suzuki/
Nクイーン問題(32)第六章 C言語移植 その12
https://suzukiiichiro.github.io/posts/2023-05-30-12-n-queens-suzuki/
Nクイーン問題(31)第六章 C言語移植 その11
https://suzukiiichiro.github.io/posts/2023-05-30-11-n-queens-suzuki/
Nクイーン問題(30)第六章 C言語移植 その10
https://suzukiiichiro.github.io/posts/2023-05-30-10-n-queens-suzuki/
Nクイーン問題(29)第六章 C言語移植 その9
https://suzukiiichiro.github.io/posts/2023-05-30-09-n-queens-suzuki/
Nクイーン問題(28)第六章 C言語移植 その8
https://suzukiiichiro.github.io/posts/2023-05-30-08-n-queens-suzuki/
Nクイーン問題(27)第六章 C言語移植 その7
https://suzukiiichiro.github.io/posts/2023-05-30-07-n-queens-suzuki/
Nクイーン問題(26)第六章 C言語移植 その6
https://suzukiiichiro.github.io/posts/2023-05-30-06-n-queens-suzuki/
Nクイーン問題(25)第六章 C言語移植 その5
https://suzukiiichiro.github.io/posts/2023-05-30-05-n-queens-suzuki/
Nクイーン問題(24)第六章 C言語移植 その4
https://suzukiiichiro.github.io/posts/2023-05-30-04-n-queens-suzuki/
Nクイーン問題(23)第六章 C言語移植 その3
https://suzukiiichiro.github.io/posts/2023-05-30-03-n-queens-suzuki/
Nクイーン問題(22)第六章 C言語移植 その2
https://suzukiiichiro.github.io/posts/2023-05-30-02-n-queens-suzuki/
Nクイーン問題(21)第六章 C言語移植 その1
N-Queens問://suzukiiichiro.github.io/posts/2023-05-30-01-n-queens-suzuki/
Nクイーン問題(20)第五章 並列処理
https://suzukiiichiro.github.io/posts/2023-05-23-02-n-queens-suzuki/
Nクイーン問題(19)第五章 キャリーチェーン
https://suzukiiichiro.github.io/posts/2023-05-23-01-n-queens-suzuki/
Nクイーン問題(18)第四章 エイト・クイーンノスタルジー
https://suzukiiichiro.github.io/posts/2023-04-25-01-n-queens-suzuki/
Nクイーン問題(17)第四章 偉人のソースを読む「N24を発見 Jeff Somers」
https://suzukiiichiro.github.io/posts/2023-04-21-01-n-queens-suzuki/
Nクイーン問題(16)第三章 対象解除法 ソース解説
https://suzukiiichiro.github.io/posts/2023-04-18-01-n-queens-suzuki/
Nクイーン問題(15)第三章 対象解除法 ロジック解説
https://suzukiiichiro.github.io/posts/2023-04-13-02-nqueens-suzuki/
Nクイーン問題(14)第三章 ミラー
https://suzukiiichiro.github.io/posts/2023-04-13-01-nqueens-suzuki/
Nクイーン問題(13)第三章 ビットマップ
https://suzukiiichiro.github.io/posts/2023-04-05-01-nqueens-suzuki/
Nクイーン問題(12)第二章 まとめ
https://suzukiiichiro.github.io/posts/2023-03-17-02-n-queens-suzuki/
Nクイーン問題(11)第二章 配置フラグの再帰・非再帰
https://suzukiiichiro.github.io/posts/2023-03-17-01-n-queens-suzuki/
Nクイーン問題(10)第二章 バックトラックの再帰・非再帰
https://suzukiiichiro.github.io/posts/2023-03-16-01-n-queens-suzuki/
Nクイーン問題(9)第二章 ブルートフォースの再帰・非再帰
https://suzukiiichiro.github.io/posts/2023-03-14-01-n-queens-suzuki/
Nクイーン問題(8)第一章 まとめ
https://suzukiiichiro.github.io/posts/2023-03-09-01-n-queens-suzuki/
Nクイーン問題(7)第一章 ブルートフォース再び
https://suzukiiichiro.github.io/posts/2023-03-08-01-n-queens-suzuki/
Nクイーン問題(6)第一章 配置フラグ
https://suzukiiichiro.github.io/posts/2023-03-07-01-n-queens-suzuki/
Nクイーン問題(5)第一章 進捗表示テーブルの作成
https://suzukiiichiro.github.io/posts/2023-03-06-01-n-queens-suzuki/
Nクイーン問題(4)第一章 バックトラック
https://suzukiiichiro.github.io/posts/2023-02-21-01-n-queens-suzuki/
Nクイーン問題(3)第一章 バックトラック準備編
https://suzukiiichiro.github.io/posts/2023-02-14-03-n-queens-suzuki/
Nクイーン問題(2)第一章 ブルートフォース
https://suzukiiichiro.github.io/posts/2023-02-14-02-n-queens-suzuki/
Nクイーン問題(1)第一章 エイトクイーンについて
https://suzukiiichiro.github.io/posts/2023-02-14-01-n-queens-suzuki/

書籍の紹介

Python入門 if文で条件を否定できませんか?

Python入門 if文で条件を否定できませんか?

Nクイーン問題(43)第七章 ミラー Python編

Nクイーン問題(43)第七章 ミラー Python編