程序员的资源宝库

网站首页 > gitee 正文

数独解法___从暴力搜索到部分优化

sanyeah 2024-04-05 13:11:14 gitee 7 ℃ 0 评论

数独介绍:https://baike.baidu.com/item/数独/74847

数独解法:http://www.llang.net/sudoku/skill/1.html

数独优化方法参考:http://www.cnblogs.com/grenet/archive/2013/06/19/3138654.html

 

首先是暴力搜索

把2维数组转化为1维l2[x, y] = l1[x * 9 + y]

从第一个没有 填的数字开始, 通过"每行每列每个九宫格都有1-9且只出现一次" 这个约束,算出该单元格可以填写的数字.填下之后在该格的历史列表中添加填下的数字,并把该格的序列号添加到回滚列表

如果遇到一个格子没有任何数字可填,说明搜索不成功. 回滚到上一次填写的格子的序列号.

由于上一次填下的数字加入了历史列表, 故不会再搜索这个数字

直到搜索指针到达数独的末尾为止.

具体代码如下:

 

  1 #!/usr/bin/env python
  2 # encoding: utf-8
  3 
  4 """
  5 @version: 暴力破解
  6 @author: Wish Chen
  7 @contact: 986859110@qq.com
  8 @file: solving_sodoku.py
  9 @time: 2018/11/27 5:23
 10 
 11 """
 12 import time
 13 history = {}
 14 search_history = []
 15 start_time = 0
 16 
 17 
 18 def show_sudoku(l):
 19     """
 20     print a sudoku
 21     格式化输出
 22     :param sudoku:
 23     :return:
 24     """
 25     global start_time
 26     for i in range(9):
 27         for j in range(9):
 28             print(l[i*9+j], end='')
 29             if j% 3 == 2:
 30                 print('|', end='')
 31             else:
 32                 print(' ', end='')
 33         if i % 3 == 2 and i < 8:
 34             print('\n------------------', end='')
 35         print('')
 36     print('\n\n')
 37     print('用时%.3fs' % (time.time() - start_time))
 38 
 39 
 40 def read_from_file():
 41     """
 42     read a sudoku from '1.in'
 43     :return:
 44     """
 45     l = []
 46     with open('5.in', 'r') as f:
 47         for i in range(9):
 48             row = f.readline().strip().split(' ')
 49             for j in row:
 50                 l.append(int(j))
 51     return l
 52 
 53 
 54 def initialize():
 55     """
 56     初始化历史搜索列表
 57     :return:
 58     """
 59     for i in range(81):
 60         history[i] = []
 61 
 62 
 63 def num_list(index, l):
 64     """
 65     计算出index的位置能有哪些数
 66     :param index:
 67     :param l:
 68     :return:
 69     """
 70     global history
 71     i = index // 9
 72     j = index % 9
 73     list_b = [False, False, False, False, False, False, False, False, False]  # 可以填的数为False, 接下来开始排除
 74     for k in range(9):
 75         if l[i*9+k] != 0: list_b[l[i*9+k]-1] = True  # 排除同行
 76         if l[k*9+j] != 0: list_b[l[k*9+j]-1] = True  # 排除同列
 77 
 78     ii = i // 3 * 3
 79     jj = j // 3 * 3
 80     for row in range(ii, ii+3):
 81         for col in range(jj, jj+3):
 82             if l[row*9+col] != 0: list_b[l[row*9+col]-1] = True  # 排除九宫格
 83 
 84     for num in history[index]:
 85         list_b[num-1] = True  # 排除已搜索过的
 86 
 87     list_n = []
 88     for i in range(len(list_b)):
 89         if not list_b[i]: list_n.append(i+1)
 90     return list_n
 91 
 92 
 93 
 94 
 95 
 96 def solve(l):
 97     """
 98     暴力破解 深搜
 99     :param l:
100     :return:
101     """
102     x = 0
103     n = 0
104     flag = True
105     while flag:
106         while flag and x < 81:
107             n += 1
108             if l[x] == 0:
109                 list_can = num_list(x, l)  # 获取该单元格所有可以被搜索的值
110                 if list_can:  # 该单元格有可以被搜索的值
111                     search_history.append(x)
112                     history[x].append(list_can[0])
113                     l[x] = list_can[0]
114                 else:  # 该单元格没有可以被搜索的值,则说明这条路不通, 回滚
115                     if search_history:  # 有搜索历史, 则回滚
116                         history[x] = []
117                         x = search_history.pop()
118                         l[x] = 0
119                         continue
120                     else:  # 没有搜索历史, 即第一个值所有都不行, 无解
121                         flag = False
122             x += 1
123         if flag:
124             show_sudoku(l)
125             x = search_history.pop()
126             l[x] = 0
127     print(n)
128 
129 
130 if __name__ == '__main__':
131     sudoku = read_from_file()
132     show_sudoku(sudoku)
133     start_time = time.time()
134     initialize()
135     solve(sudoku)
136     print('总用时%.3fs' % (time.time() - start_time))
暴力搜索数独解法

 

下面进行优化

优化1:

通过二进制来存储每一个需要填入的格子里面所有可以填入的数

比如

000110001 表示可以在这一格填入1,5,6三个数字

判断是否可以填入只需要做一下位运算即可, 例:

  000110001

&000010000

=000010000

表示可以在该单元格填入5

如果要排除一个数字, 只需要和数字的反码进行一次&运算即可.例:

  000110001

&111101111

=000100001

表示在可以填入1,5,6的格子内排除5,只剩下1,6可以填入.

建立两个常数索引

V = [1, 2, 4, 8, 16, 32, 64, 128, 256, 511]
vn = {1: 1, 2: 2, 4: 3, 8: 4, 16: 5,
      32: 6, 64: 7, 128: 8, 256: 9}

这样一来取代表数字n的反码就是V[9] - V[n-1] 

 

优化2:

在每一次深搜之前,进行唯一候选数和隐性唯一候选数的排除

具体数独解法参见开头所列的网页

 

 

细节:

如果要将整个列表append进历史搜索列表

要用copy.deepcopy命令

不然拷贝进去的是地址, 所有的历史搜索列表都会乱.

 

整个代码如下:

 

  1 #!/usr/bin/env python
  2 # encoding: utf-8
  3 
  4 """
  5 @version: 先利用唯一候选数法和隐性唯一候选数法优化后暴力破解
  6 @author: Wish Chen
  7 @contact: 986859110@qq.com
  8 @file: solving_sodoku.py
  9 @time: 2018/11/27 5:23
 10 
 11 """
 12 import time, copy
 13 
 14 V = [1, 2, 4, 8, 16, 32, 64, 128, 256, 511]
 15 vn = {1: 1, 2: 2, 4: 3, 8: 4, 16: 5,
 16       32: 6, 64: 7, 128: 8, 256: 9}
 17 history = []
 18 
 19 
 20 def show_sudoku(l, num=1):
 21     """
 22     print a sudoku
 23     格式化输出
 24     :param sudoku:
 25     :return:
 26     """
 27     global start_time
 28     for i in range(9):
 29         for j in range(9):
 30             print(l[i * 9 + j] * num, end='')
 31             if j % 3 == 2:
 32                 print('|', end='')
 33             else:
 34                 print(' ', end='')
 35         if i % 3 == 2 and i < 8:
 36             print('\n------------------', end='')
 37         print('')
 38     print('\n\n')
 39     if num == -1:
 40         print('用时%.3fs' % (time.time() - start_time))
 41 
 42 
 43 def read_from_file():
 44     """
 45     read a sudoku from '1.in'
 46     :return:
 47     """
 48     l = []
 49     with open('1.in', 'r') as f:
 50         for i in range(9):
 51             row = f.readline().strip().split(' ')
 52             for j in row:
 53                 l.append(int(j))
 54     return l
 55 
 56 
 57 def initialize(l):
 58     """
 59     初始化,将所有未填写的部分设置为(111111111)B
 60     遍历整个数独,已填写的数字让其约束生效
 61     :param l:
 62     :return:
 63     """
 64     for i in range(81):
 65         if l[i] == 0:
 66             l[i] = V[9]
 67         else:
 68             l[i] = -l[i]
 69 
 70     for i in range(81):
 71         if l[i] < 0:
 72             put_in(i, -l[i], l)  # 填下一个数字, 使约束生效
 73 
 74 
 75 def choice(index, l):
 76     """
 77     返回序号为index下
 78     所有的可以填入的数字的个数
 79     :param index:
 80     :param l:
 81     :return:
 82     """
 83     x = l[index]
 84     n = 0
 85     while x > 0:
 86         x = x & (x-1)
 87         n += 1
 88     return n
 89 
 90 
 91 def get_first_value(l):
 92     """
 93     返回第一个需要填写的格子
 94     首先检查有没有矛盾
 95     :param l:
 96     :return:
 97     """
 98     for i in range(81):  # 检查有没有矛盾
 99         if l[i] == 0:
100             return i
101 
102     x = 0
103     while l[x] < 0:
104         x += 1
105     return x
106 
107 
108 def solve_only_one(l):
109     """
110     唯一候选数法
111     :param l:
112     :return:
113     """
114     flag = True  # 初始化flag, flag表示这一趟循环有没有填入新的数字
115     while flag:
116         flag = False
117         for i in range(81):
118             if choice(i, l) == 1:  # 如果这个格子可以填写的数字只有一个
119                 flag = True
120                 l[i] = -vn[l[i]]
121                 l = put_in(i, -l[i], l)
122     return l
123 
124 
125 def solve_hide_one(l):
126     """
127     隐形唯一候选数法
128     :param l:
129     :return:
130     """
131     flag = True  # 初始化flag, flag表示这一趟循环有没有填入新的数字
132     while flag:  # 不停循环
133         flag = False
134         for n in range(9):
135             for i in range(9):
136                 num = 0
137                 for j in range(9):
138                     if l[i * 9 + j] > 0 and l[i * 9 + j] & V[n] == V[n]:
139                         num += 1
140                         temp = i * 9 + j
141                 if num == 1:
142                     flag = True
143                     l[temp] = - n - 1
144                     l = put_in(temp, n + 1, l)  # 每一行进行隐性唯一候选数法
145 
146                 num = 0
147                 for j in range(9):
148                     if l[j * 9 + i] > 0 and l[j * 9 + i] & V[n] == V[n]:
149                         num += 1
150                         temp = j * 9 + i
151                 if num == 1:
152                     flag = True
153                     l[temp] = - n - 1
154                     l = put_in(temp, n + 1, l)  # 每一列进行隐性唯一候选数法
155 
156             for i in range(3):
157                 for j in range(3):
158                     num = 0
159                     for row in range(3):
160                         for col in range(3):
161                             index = (i * 3 + row) * 9 + j * 3 + col
162                             if l[index] > 0 and l[index] & V[n] == V[n]:
163                                 num += 1
164                                 temp = index
165                     if num == 1:
166                         flag = True
167                         l[temp] = - n - 1
168                         l = put_in(temp, n + 1, l)  # 每一九宫格进行隐性唯一候选数法
169     return l
170 
171 
172 def map_list(l):
173     """
174     深搜
175     首先通过两个数独解法填入简单的数字
176 
177     :param l:
178     :return:
179     """
180     global history
181     l = solve_only_one(l)
182     l = solve_hide_one(l)
183     n = get_first_value(l)
184     if n == 81:
185         show_sudoku(l, -1)
186         return
187     else:
188         if l[n] == 0:
189             return
190         else:
191             i = 8
192             while l[n] < V[i]:
193                 i -= 1
194             l[n] -= V[i]
195             history.append(copy.deepcopy(l))  # 将此时的数独状态放入历史搜索列表
196             l[n] = -(i + 1)
197             l = put_in(n, i+1, l)
198             map_list(l)
199 
200 
201 def put_in(index, n, l):
202     """
203     在index位置插入n
204     制造约束,同行同列同九宫格不再有这个数的选项
205     :param index:
206     :param n:
207     :param l:
208     :return:
209     """
210     x = index // 9
211     y = index % 9
212     for k in range(9):
213         if l[x*9+k] > 0:
214             l[x*9+k] &= V[9] - V[n-1]  # 排除同行
215         if l[k*9+y] > 0:
216             l[k*9+y] &= V[9] - V[n-1]  # 排除同列
217 
218     ii = x // 3 * 3
219     jj = y // 3 * 3
220     for row in range(ii, ii + 3):
221         for col in range(jj, jj + 3):
222             if l[row * 9 + col] > 0:
223                 l[row * 9 + col] &= V[9] - V[n-1]  # 排除九宫格
224 
225     return l
226 
227 
228 if __name__ == '__main__':
229     sudoku = read_from_file()
230     sudoku.append(1)  # 为了方便get_first_value函数, 在末尾设置一个边界
231     show_sudoku(sudoku)
232     start_time = time.time()
233     initialize(sudoku)
234     map_list(sudoku)
235     while history:
236         map_list(history.pop())
237     print('总用时%.3fs' % (time.time() - start_time))
优化后数独解法

 

输入:

8 0 0 0 0 0 0 0 0
0 0 3 6 0 0 0 0 0
0 7 0 0 9 0 2 0 0
0 5 0 0 0 7 0 0 0
0 0 0 0 4 5 7 0 0
0 0 0 1 0 0 0 3 0
0 0 1 0 0 0 0 6 8
0 0 8 5 0 0 0 1 0
0 9 0 0 0 0 4 0 0


输出:

 

8 0 0|0 0 0|0 0 0|
0 0 3|6 0 0|0 0 0|
0 7 0|0 9 0|2 0 0|
------------------
0 5 0|0 0 7|0 0 0|
0 0 0|0 4 5|7 0 0|
0 0 0|1 0 0|0 3 0|
------------------
0 0 1|0 0 0|0 6 8|
0 0 8|5 0 0|0 1 0|
0 9 0|0 0 0|4 0 0|

 

8 1 2|7 5 3|6 4 9|
9 4 3|6 8 2|1 7 5|
6 7 5|4 9 1|2 8 3|
------------------
1 5 4|2 3 7|8 9 6|
3 6 9|8 4 5|7 2 1|
2 8 7|1 6 9|5 3 4|
------------------
5 2 1|9 7 4|3 6 8|
4 3 8|5 2 6|9 1 7|
7 9 6|3 1 8|4 5 2|

 

用时1.608s
总用时1.664s

 

输入:

1 7 0 0 0 0 0 0 0
0 9 0 0 0 0 0 0 8
0 0 0 0 0 0 5 3 0
0 0 3 9 0 0 0 0 0
0 0 0 7 0 0 0 0 0
0 0 2 0 0 0 0 0 6
0 6 0 0 2 0 0 0 0
0 0 0 0 0 0 0 1 7
0 0 8 0 5 0 0 0 0

 

 

输出:

1 7 0|0 0 0|0 0 0|
0 9 0|0 0 0|0 0 8|
0 0 0|0 0 0|5 3 0|
------------------
0 0 3|9 0 0|0 0 0|
0 0 0|7 0 0|0 0 0|
0 0 2|0 0 0|0 0 6|
------------------
0 6 0|0 2 0|0 0 0|
0 0 0|0 0 0|0 1 7|
0 0 8|0 5 0|0 0 0|

 

1 7 5|8 9 3|6 4 2|
3 9 4|2 6 5|1 7 8|
8 2 6|4 7 1|5 3 9|
------------------
6 5 3|9 4 2|7 8 1|
9 8 1|7 3 6|4 2 5|
7 4 2|5 1 8|3 9 6|
------------------
4 6 7|1 2 9|8 5 3|
5 3 9|6 8 4|2 1 7|
2 1 8|3 5 7|9 6 4|

 

用时0.005s
总用时0.005s

 

 

输入:

0 0 0 6 0 3 9 7 0
0 7 0 0 0 4 0 0 2
0 0 1 0 0 0 3 4 0
0 0 0 8 0 9 0 0 3
4 0 8 1 0 5 0 6 9
0 1 0 2 3 0 7 0 0
3 0 6 4 0 7 5 0 0
0 0 9 5 0 0 0 0 0
1 0 4 0 0 0 0 0 0

 

输出:

0 0 0|6 0 3|9 7 0|
0 7 0|0 0 4|0 0 2|
0 0 1|0 0 0|3 4 0|
------------------
0 0 0|8 0 9|0 0 3|
4 0 8|1 0 5|0 6 9|
0 1 0|2 3 0|7 0 0|
------------------
3 0 6|4 0 7|5 0 0|
0 0 9|5 0 0|0 0 0|
1 0 4|0 0 0|0 0 0|

 

8 4 2|6 1 3|9 7 5|
6 7 3|9 5 4|8 1 2|
5 9 1|7 8 2|3 4 6|
------------------
2 6 7|8 4 9|1 5 3|
4 3 8|1 7 5|2 6 9|
9 1 5|2 3 6|7 8 4|
------------------
3 8 6|4 9 7|5 2 1|
7 2 9|5 6 1|4 3 8|
1 5 4|3 2 8|6 9 7|

 

用时0.005s
8 4 2|6 1 3|9 7 5|
6 7 3|9 5 4|8 1 2|
5 9 1|7 8 2|3 4 6|
------------------
2 6 7|8 4 9|1 5 3|
4 3 8|1 7 5|2 6 9|
9 1 5|2 3 6|7 8 4|
------------------
3 8 6|4 2 7|5 9 1|
7 2 9|5 6 1|4 3 8|
1 5 4|3 9 8|6 2 7|

 

用时0.006s
8 4 2|6 1 3|9 7 5|
6 7 3|9 5 4|8 1 2|
5 9 1|7 2 8|3 4 6|
------------------
2 6 7|8 4 9|1 5 3|
4 3 8|1 7 5|2 6 9|
9 1 5|2 3 6|7 8 4|
------------------
3 8 6|4 9 7|5 2 1|
7 2 9|5 6 1|4 3 8|
1 5 4|3 8 2|6 9 7|

 

用时0.008s
5 4 2|6 1 3|9 7 8|
8 7 3|9 5 4|6 1 2|
6 9 1|7 2 8|3 4 5|
------------------
2 6 7|8 4 9|1 5 3|
4 3 8|1 7 5|2 6 9|
9 1 5|2 3 6|7 8 4|
------------------
3 8 6|4 9 7|5 2 1|
7 2 9|5 8 1|4 3 6|
1 5 4|3 6 2|8 9 7|

 

用时0.010s
总用时0.010s

 

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表