数独介绍: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(' ------------------', end='') 35 print('') 36 print(' ') 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(' ------------------', end='') 37 print('') 38 print(' ') 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