php 数独求解,高效算法求解数独(示例代码)
title: 高效算法求解數獨
date: 2019-12-26 17:55:16
tags: 數據結構與算法
categories: 數據結構與算法
背景
??之前上python課的時候,有一次實驗是求解數獨,要求時間復雜度要低;為此老師講解了一個高效的數獨算法,我覺得算法挺有意思的,寫篇博客記錄一下。
描述
首先需要知曉數獨的兩個規則:
若某個位置的值已經確定,那么,和這個位置在同一行,同一列,同一個3×3的格子,都不能填寫這個值,比如,九宮格(1,1)位置的值為2,那么,第一行,第一列,以及第一個3×3的格子里,都不能在填2了;
若某一行,或者某一列,或者某一個3×3里面,只有一個位置可能填1(假如是1),那么1一定是填寫在這個位置,因為沒有其他位置可以填它了;、
求解步驟
創建一個三維數組,假設就叫“可能值數組”,記錄數獨9×9的81個位置中,每個位置可能填寫的值,初始情況下,每個位置的可能值都是1到9,表示每個位置都可能填寫1-9中任何一個數字;
遍歷數獨的每一個位置,若某個位置已經有值,則將這個位置的可能值更新為這個值,比如,九宮格上,(1,1)的值已經確定是2了,那就將三維數組中(1,1)位置的可能值從[1-9]更新為[2],直到所有的位置更新完畢;
使用上述規則1進行剪枝:
(1):從第一個位置開始遍歷九宮格,若當前遍歷到的位置(i,j),它的值已經知曉,那么就更新可能值數組,將第i行,第j列,以及其對應的3×3(【i/3×3 , j/3×3】就是這個3×3的第一個點)的所有位置,它們的可能值都要去除(i,j)位置的值;
(2):若某個位置在經過上一步的剪枝后,可能值只剩下一個了,那這個位置的值就確定了,比如說,位置(1,1)的初始可能值是1到9,經過上面的一步步去除,只剩下一個3了,那這個(1,1)位置填寫的值必定就是3了。此時我們可以再次使用規則1,即第一行,第一列,以及其對應的3×3中,所有的格子的可能值不能有3;
(3):依次遍歷每一個位置,使用上面的規則1,直到最后一格子,第一次剪枝便完成了;
使用上面的規則2進行剪枝:
(1):統計每一行,每一列,以及每一個3×3中,每個數出現的次數,比如統計第一行每個格子的可能值,看1-9各出現幾次,若某個可能值只出現一次,那出現這個值的位置,就是填寫這個值,比如說,在第一行,3這個數字,只有(1,1)這個位置可能填寫,那(1,1)就是填3,因為其他位置的可能值當中都不包含3,也就是都不能填寫3;
(2):根據上一步確定了某個位置的值后,那我們此時又可以使用規則1了,比如,上一步確定了(1,1)是填寫3,那么第一行,第一列,以及第一個3×3中其余的格子, 都不能在寫3了,我們從可能值數組中,將這些位置的可能,值刪除3這個數;
(3):而此時,又可能出現上面的第3步中的(3)的情況;
規則2剪枝完畢后,數獨還沒有解決完畢,那我們只能通過枚舉每一個位置的值,來一一嘗試,直到找到最后的解決辦法:
(1):我們在最開始創建了一個三維數組,存儲每一個位置的可能值,初始情況下,每個位置的可能值都是1-9,但是經過上面兩個規則的剪枝后,已經去除了很多;
(2):此時我們使用DFS深度優先搜索,嘗試為每一個位置填值。經過上面的剪枝,每個位置的可能值的數量應該不一樣了,而為了減少DFS搜索的次數,我們應該從可能值最少的位置開始搜索;
(3):遍歷9宮格,找出還未填寫值,且可能值最少的那個位置(可能有多個,找出第一個),嘗試將他的第一個可能值填寫在這個位置,然后再次調用規則1和規則2進行剪枝,剪枝完畢后,判斷當前的九宮格中,是否有不和規則的地址,比如同一行出現兩個一樣的數。若沒有不合法的地方,則再次進行一次(3),若有,表示這個位置不能填這個值,則從這個位置的可能值中再選擇另外一個;
(4):一直使用步驟(3),直到所有的位置都確定,則表示成功解出數獨,若有某個位置,它的任何一個可能值填上去,都不能得到最終結果,那數獨就是無解的;
經過上面這些步驟,就能快速的解出數獨,因為主要通過規則1,2進行剪枝,大大減少了枚舉的次數,提升了效率;
所需計算
已知位置(i,j),則這個位置所在的3*3,其第一個點是(i/3×3 , j/3×3),i/3×3表示先作除法,去除了小數部分,再乘3,就是3的倍數了;
已知位置(i,j),如何計算這個位置屬于第幾個3×3,那就是(i/3×3 + j/3),每個3*3都占3行,且3列,i/3得到這個位置在第幾個3行,j/3得到這個位置在第幾個3列,每三行有三個3×3,所以i/3×3 + j/3就可以得到這個位置在第幾個3×3;
代碼
因為是為了完成python實驗,所以代碼是用python寫的:
# 此類用來表示搜索時,需要搜索的一個位置
# x,y為此位置的坐標,size為此位置的可能值的數量
class Node:
def __init__(self, x, y, size):
self.x = x
self.y = y
self.size = size
# 讀取方式2,讀取top95
def read_way2():
# 從文件中讀取初始數獨
value = [[0] * 10 for i in range(10)]
# 讀取文件2,3
s = infile.readline();
if s == '':
return
for i in range(9):
for j in range(9):
value[i][j] = int(s[i * 9 + j])
return value
# 初始化函數
def init():
value = read_way2() # 讀取top95
# 初始化possibleValue,若當前位置有值,則其可能值就是這個值本身
# 若沒有值,則初始的可能值就是1-9
possibleValue = [[[] for j in range(10)] for i in range(10)]
for i in range(9):
for j in range(9):
if value[i][j] != 0:
possibleValue[i][j] = [value[i][j]]
else:
possibleValue[i][j] = [1, 2, 3, 4, 5, 6, 7, 8, 9]
return possibleValue
#####################################################################################################################
# 根據規則1進行剪枝
# 遍歷所有的位置,找到已經確定的位置進行剪枝
def pruningByRule1(possibleValue):
for i in range(9):
for j in range(9):
if len(possibleValue[i][j]) == 1:
removeValueByRule1(i, j, possibleValue) # 以當前位置為起點,移除重復的可能值
# 在規則1剪枝中,將同一區域中,已經確定的數移除
# 以(i,j)位置為起點,移除重復的可能值
def removeValueByRule1(i, j, possibleValue):
# 與當前值在同一行或同一列的位置,可能值減去當前位置的值
for k in range(9):
# 從第i行中的可能值列表中,移除當前值
confirmOneValueInRule1(i, k, possibleValue[i][j][0], possibleValue)
# 從第i列中的可能值列表中,移除當前值
confirmOneValueInRule1(k, j, possibleValue[i][j][0], possibleValue)
# 與當前值在同3*3的位置,可能值減去當前位置的值
for k in range(int(i / 3) * 3, int(i / 3) * 3 + 3):
for l in range(int(j / 3) * 3, int(j / 3)* 3 + 3):
confirmOneValueInRule1(k, l, possibleValue[i][j][0], possibleValue)
# 移除某個位置的可能值,并在移除后判斷能否得到確定值
def confirmOneValueInRule1(i, j, num, possibleValue):
if len(possibleValue[i][j]) == 1:
return
# 從當前位置的可能值中,移除已經確定的數
if num in possibleValue[i][j]:
possibleValue[i][j].remove(num)
# 判斷移除后,當前位置能否確定
if len(possibleValue[i][j]) == 1:
# 若當前位置確定,則以當前位置為基準進行移除操作
removeValueByRule1(i, j, possibleValue)
###########################################################################################
# 根據規則2剪枝,判斷同一個區域每個值可能出現的次數
# 若某個值可能出現的位置只有一個,表示這個值就在此位置
def pruningByRule2(possibleValue):
# 統計第i行,數字j可能值出現了幾次
countX = [[0] * 10 for i in range(12)]
# 統計第i列,數字j可能值出現了幾次
countY = [[0] * 10 for i in range(12)]
# 統計第i個3*3,數字j可能值出現了幾次
countZ = [[0] * 10 for i in range(12)]
# 統計各個區域可能值出現的次數
for i in range(9):
for j in range(9):
for num in possibleValue[i][j]:
countX[i][num] += 1
countY[j][num] += 1
countZ[i // 3 * 3 + j // 3][num] += 1
# 判斷哪些數字只出現了一次, 若只出現了一次的數字
# 表示這個數字就是那個位置的答案
for i in range(9):
for j in range(1,10):
# 若第i行數字j只出現了一次
if countX[i][j] == 1:
for k in range(9): # 遍歷第i行的每一列,判斷這個唯一值出現在哪
confirmValueInRule2(i, k, j, possibleValue)
# 若第i列數字j只出現了一次
if countY[i][j] == 1:
for k in range(9): # 遍歷第i列的每一列,判斷這個唯一值出現在哪
confirmValueInRule2(k, i, j, possibleValue)
# 若第i個3 * 3中,數字j的可能值只有一個
if countZ[i][j] == 1:
# 遍歷第i個3*3的所有位置,判斷這個唯一值出現在哪
for k in range(i//3*3, i//3*3+3):
for l in range(i%3*3, i%3*3+3):
confirmValueInRule2(k, l, j, possibleValue)
# 判斷當前位置是否包含某個數,包含則為此位置的答案
def confirmValueInRule2(i, j, singleNum, possibleValue):
# 若當前位置已經確定值了, 直接返回
if len(possibleValue[i][j]) ==1:
return
# 若當前位置包含唯一可能值,則這個位置的確定值就是它
if singleNum in possibleValue[i][j]:
possibleValue[i][j] = [singleNum]
# 重新調用規則1
removeValueByRule1(i, j, possibleValue)
###########################################################################################
# 遞歸搜索
def searchForPruning(node, possibleValue):
# 若沒有需要填值的點了,表示搜索結束,答案已出
if node is None:
return possibleValue
# 獲取當前位置的x,y坐標
x = node[0]
y = node[1]
for num in possibleValue[x][y]:
# 復制一份當前狀態
tempPossibleValue = copy.deepcopy(possibleValue)
# 更新數據
tempPossibleValue[x][y] = [num]
# 調用規則1,2
removeValueByRule1(x, y, tempPossibleValue)
pruningByRule2(tempPossibleValue)
# 調用規則1,2后,判斷當前結果是否合法,若合法,則進行遞歸下一層
if judge_result(tempPossibleValue):
# 遞歸求解
tempPossibleValue = searchForPruning(get_lowest_node(tempPossibleValue), tempPossibleValue)
# 判斷遞歸結果,若結果有返回值,則表示求解成功
if tempPossibleValue is not None:
return tempPossibleValue
# 獲取當前可能值最小的位置
def get_lowest_node(possibleValue):
minn = 100
node = None
for i in range(9):
for j in range(9):
# 若當前位置沒有確定值,并且可能值的數量更少,則更新記錄,
if 1 < len(possibleValue[i][j]) < minn:
minn = len(possibleValue[i][j])
node = (i, j)
return node
# 判斷某個位置是否可以放某個值
def judge_result(possibleValue):
# 標記某個數字是否出現
countX = [[False] * 10 for i in range(12)]
countY = [[False] * 10 for i in range(12)]
countZ = [[False] * 10 for i in range(12)]
# 統計各個區域可能值出現的次數
for i in range(9):
for j in range(9):
if len(possibleValue[i][j]) == 1:
# 若當前狀態不合法,返回false
if countX[i][possibleValue[i][j][0]] or countY[j][possibleValue[i][j][0]] or countZ[i // 3 * 3 + j // 3][possibleValue[i][j][0]]:
return False
# 若合法,則標記已經確定的數字
countX[i][possibleValue[i][j][0]] = True
countY[j][possibleValue[i][j][0]] = True
countZ[i // 3 * 3 + j // 3][possibleValue[i][j][0]] = True
return True
# 判斷某個位置是否可以放某個值
def judge_now_number(possibleValue, i, j, num):
# 判斷num在這一行和這一列是否被使用
for k in range(9):
if len(possibleValue[i][k]) == 1 and possibleValue[i][k][0] == num:
return False
if len(possibleValue[k][j]) == 1 and possibleValue[k][j][0] == num:
return False
# 判斷num在這個3*3是否被使用
for k in range(int(i / 3) * 3, int(i / 3) * 3 + 3):
for l in range(int(j / 3) * 3, int(j / 3) * 3 + 3):
if len(possibleValue[k][l]) == 1 and possibleValue[k][l][0] == num:
return False
return True
###########################################################################################
# 輸出展示可能值列表
def display(possibleValue):
for i in range(9):
for j in range(9):
print(possibleValue[i][j], end="---")
print()
print()
###########################################################################################
# 主函數
def main():
start = time.time()
c = 0
# 主邏輯
while True:
# 調用初始化函數
possibleValue = init()
# 調用規則1剪枝
pruningByRule1(possibleValue)
# 調用規則2剪枝
pruningByRule2(possibleValue)
# display(possibleValue)
possibleValue = searchForPruning(get_lowest_node(possibleValue), possibleValue)
# 判斷是否有解
if possibleValue is not None:
display(possibleValue)
else:
print("無解")
if not judge_result(possibleValue):
print("結果異常")
c += 1
if c >= 90:
break
end = time.time()
print(end - start)
# 讀取本地存儲文件
infile = open("D:/top95.txt")
main()
擴展
??數獨求解的算法,上面這種并不是最快的,還有一種叫做舞蹈鏈(Dancing Links)的算法,效率更高,有興趣的可以了解一下;
總結
以上是生活随笔為你收集整理的php 数独求解,高效算法求解数独(示例代码)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于 jQuery支持移动触摸设备的Li
- 下一篇: jdbc获取结果行数_java – 如何