日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > php >内容正文

php

php 数独求解,高效算法求解数独(示例代码)

發布時間:2023/12/10 php 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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 数独求解,高效算法求解数独(示例代码)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。