0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

数独のBacktrackingによる解法

Posted at

#概要
ネットに幾つか解法がありますので,無意味かもしれませんが,自己満足です.

https://news.mynavi.jp/article/zeropython-54/
での,下記の方法が一番簡単だろう
(1) 左上から右下へと順に空白マスを調べていく
(2) 空白マスがあれば、その時点で配置可能な数字を調べる
(3) 配置可能な数字を仮に配置して、次のマスを調べていく
(4) もし配置がうまくいかなければ(3)に戻る
(5) 最後のマスに到達するまで、(2)以降の処理を繰り返す
(6) 最後に到達したら結果を出力する

やっていることは,総当たりである.


2*2の時(ありえないが),
2.png
Aの場所には{1,2,4}のどれかが入るとする.このとき,
3.png

このように,A,B,C,Dの順に可能性のある数値を入れていきます.入れるたびに,矛盾がないか確認します.矛盾が発生した時点で,ほかの可能性のある数値を入れていきます.
これだけだと,各セルすべて試してダメだった時に戻らないので,すべて試してダメだった場合,ひとつ前のセルに戻らせます.
4.png

「すべて試した」「矛盾あり」で戻る場合は,そのセル以降の状態は初期状態に戻す必要があります.

#プログラム

import numpy as np		#	Numpyライブラリ
import copy


box = np.full((9,9,9),[1,2,3,4,5,6,7,8,9])

box = box.tolist()

"""
問題の定義
"""
problem = [[0,3,0,0,5,9,0,0,0],[0,5,4,0,0,0,0,9,0],[6,2,0,8,0,0,1,4,0],[4,0,0,0,0,2,0,0,8],[0,0,0,0,4,0,0,6,0],[0,0,0,1,0,6,0,0,0],[0,0,0,0,0,0,0,2,6],[0,6,0,5,0,0,0,0,0],[8,0,0,0,9,0,7,0,4]]
#0は空箱という意味

for i in range(9):
    for j in range(9):
        if problem[i][j] != 0:
            box[i][j] =[problem[i][j]]
            

            """
            可能性のある数値を除く
            """
            #列
            for k in range(9):
                if box[i][k].count(problem[i][j]) != 0 and k != j:
                    box[i][k].remove(problem[i][j])
            #行
            for k in range(9):
                if box[k][j].count(problem[i][j]) != 0 and k != i:
                    box[k][j].remove(problem[i][j])
                    
            #箱
            box_x = int(i/3)
            box_y = int(j/3)
            for x in range(3):
                for y in range(3):
                    if x + box_x*3 != i and y + box_y*3 != j:    #(i,j)での削除は除く
                        if box[x + box_x*3][y + box_y*3].count(problem[i][j]) != 0: #removeはないとエラーになるため
                            box[x + box_x*3][y + box_y*3].remove(problem[i][j])
            
            
            

        
class Cell:
    def  __init__( self,position,box,before,origin ) :
        self.position = position                     #[1,2]のような形式
        self.box = copy.deepcopy(box)                #popでのみ,変化していく
        self.before = before
        self.origin = origin
        self.result = []                             #回答が出たら,保存するための変数.
        
    def make_next_cell(self):
        while True:
            if len(self.box[self.position[0]][self.position[1]]) == 0: #popできないとき
                #beforeのインスタンスに戻る
                if self.before != -1:
                    return 0      
                    
                else:
                    #全選択肢チェック完了    (0,0)でpopできないため,これ以上はない
                    return 1
                    
                
            else:    #popできるとき
                self.box2 = copy.deepcopy(self.box)          #次のインスタンス生成用
                x = self.position[0]
                y = self.position[1]
                fixed_value = self.box[x][y].pop()  #確定値にする      
                self.box2[x][y] = [fixed_value]
                
                #check
                self.check(self.box2,self.position,fixed_value)
                
                #次の場所のインスタンスを生成する.
                x = self.position[0]
                y = self.position[1]
                if x == 8:
                    x=0
                    if y != 8:
                        y=y+1
                    else:
                        #正解が求まったことになる. originに保存し終了
                        self.origin.result =  copy.deepcopy(self.box2)
                        return 1
                else:
                    x=x+1
                
                self.next_cell = Cell([x,y],self.box2,self,self.origin) 
                flag = self.next_cell.make_next_cell()
                
                
                if flag == 1:  #これ以上の探索は必要ない・これ以上探索できない   while ループを抜ける
                    break
                  
        
        
        
    def check(self,box,position,fixed_value):   #今回は,入れた数値は必ず正しく,他のセルの可能性のある数値を除く
        i = position[0]
        j = position[1]
        #列
        for k in range(9):
            if box[i][k].count(fixed_value) != 0 and k != j:
                box[i][k].remove(fixed_value)
        #行
        for k in range(9):
            if box[k][j].count(fixed_value) != 0 and k != i:
                box[k][j].remove(fixed_value)
        
        #箱
        box_x = int(i/3)
        box_y = int(j/3)
        for x in range(3):
            for y in range(3):
                if x + box_x*3 != i and y + box_y*3 != j:    #(i,j)での削除は除く
                    if box[x + box_x*3][y + box_y*3].count(fixed_value) != 0: #removeはないとエラーになるため
                        box[x + box_x*3][y + box_y*3].remove(fixed_value)
                        
    def disp(self):
        for i in range(9):
            print(origin.result[i])
        

origin =  Cell([-1,-1],box,-1,[]) 
cell = Cell([0,0],box,-1,origin)  
cell.make_next_cell()           #一つ目のセルを呼び出すことで,以降のセルもどんどん作られる

print(origin.result)

#連立方程式的解法
変数は81個
列9拘束,行9拘束,箱9拘束 で合計27個の拘束条件になります.
さらに,列,箱,行それぞれで同じ数字は入らないという拘束.これは,一つの列に対し8個の不等号拘束.よって合計8*27=216個の不等号拘束.
さらに,全変数は1~9の整数のみが入る

という問題になります.一般逆行列のような形で,等号拘束$Ax=y$から,$x=By+\alpha$の形に変形し,$By$は一定値になるので,あとは,不等号拘束・全整数1~9を成立させるように$\alpha$を探索する方法が考えられます.

表の,要素を$x_{ij}$とすると,$A$行列は

import numpy as np		#	Numpyライブラリ
import copy

A=[]


#列
for j in range(9):
    retu = []
    for i in range(81):
        if int(i/9) == j:
            retu.append(1)
        else:
            retu.append(0)
     
    A.append(retu)
    
#行
for j in range(9):
    retu = []
    for i in range(81):
        if i%9 == j:
            retu.append(1)
        else:
            retu.append(0)
            
            
    A.append(retu)

#箱
for k in range(3):
    for m in range(3):
        retu = []
        for i in range(81):
            x=i%9
            y=int(i/9)
            
            if int(x/3) == k and int(y/3):
                retu.append(1)
                
            else:
                retu.append(0)
        
        A.append(retu)
            
    

A=np.array(A)
invA = np.linalg.pinv(A)
    
print(A)

で計算できます.

ちょっと一般逆行列の知識が足らないので,もう少し知識を付けたら更新します.

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?