在每次填充数字后,立即检查是否有位置候选数为空(无解状态)。
sql
WITH RECURSIVE
initial AS (
SELECT id,
puzzle board,
-- 初始化行掩码:确保 SUM 结果被强制转为 int
(SELECT array_agg(m) FROM ( --用2个元素分别保存前5个低位和后4个高位
SELECT SUM(case when val>0 then 1::bigint << (case when r<=4 then r else r-5 end *9+val - 1) else 0 end)::bigint as m
FROM generate_series(0, 8) r
INNER JOIN LATERAL (SELECT substring(puzzle, r*9 + i, 1) as ch FROM generate_series(1, 9) i) s ON ch <> '.'
CROSS JOIN LATERAL (SELECT (ch::text)::int as val) v
GROUP BY case when r<=4 then 1 else 2 end ORDER BY case when r<=4 then 1 else 2 end
) s) as rows1,
-- 初始化列掩码
(SELECT array_agg(m) FROM (
SELECT SUM(case when val>0 then 1 << (val - 1)else 0 end)::int as m
FROM generate_series(1, 9) c
INNER JOIN LATERAL (SELECT substring(puzzle, (i-1)*9 + c, 1) as ch FROM generate_series(1, 9) i) s ON ch <> '.'
CROSS JOIN LATERAL (SELECT (ch::text)::int as val) v
GROUP BY c ORDER BY c
) s) as cols,
-- 初始化宫掩码
(SELECT array_agg(m) FROM (
SELECT SUM(case when val>0 then 1 << (val - 1)else 0 end)::int as m
FROM generate_series(0, 8) b
INNER JOIN LATERAL (SELECT substring(puzzle, (b/3)*27 + (b%3)*3 + ((i-1)/3)*9 + ((i-1)%3) + 1, 1) as ch FROM generate_series(1, 9) i) s ON ch <> '.'
CROSS JOIN LATERAL (SELECT (ch::text)::int as val) v
GROUP BY b ORDER BY b
) s) as boxes
,(SELECT array_agg(pos::int) FROM generate_series(1, 81) p(pos) WHERE substring(puzzle, p.pos, 1) = '0' )as positions
, length(replace(replace(puzzle,'0',''),chr(10),'')) known
FROM (select id,replace(replace(puzzle, '?', '0'),chr(10), '') puzzle from sudoku9_9 where length(replace(replace(puzzle,'?',''),chr(10),''))<30) sudoku9_9
),
solve AS (
SELECT id, board::text board, rows1, cols, boxes, false as solved,positions,known FROM initial
UNION ALL
(
WITH current_level AS (
SELECT * FROM solve WHERE NOT solved
),
all_candidates AS (
SELECT id,
cl.board, cl.rows1, cl.cols, cl.boxes,
pos, positions,known,
-- 修正重点:每一个数组提取都加 ::int,并且用括号包裹位运算
(( ((cl.rows1[(pos-1)/45 + 1]>>9*((pos-1)/9%5) &511)::int | cl.cols[(pos-1)%9 + 1]::int | cl.boxes[((pos-1)/27*3 + (pos-1)%9/3) + 1]::int) # 511 ) & 511 )::int as available_mask
FROM (select *,unnest(positions) pos from current_level) cl
),
best_pos AS (
SELECT DISTINCT ON (board)
*,
-- 计算 1 的数量
length(replace((available_mask::bit(9))::text, '0', '')) as c_count
FROM all_candidates
ORDER BY board, c_count
,bit_count(boxes[((pos-1)/27*3 + (pos-1)%9/3) + 1]::int::bit(9))
/*
case when known>=40 then
pos
else
-greatest(length(replace(substr(board,1,pos),'0',''))*100/pos, length(replace(substr(board,pos+1),'0',''))*100/(81-pos+1))
end
*/
),
next_step AS (
SELECT id,
substring(bp.board, 1, bp.pos - 1) || n.val || substring(bp.board, bp.pos + 1) as next_board,
case when pos<=45 then
array[bp.rows1[1]|(1::bigint << (n.val-1 +(pos-1)/9%5*9)) , bp.rows1[2]]
else
array[bp.rows1[1] , bp.rows1[2]|(1::bigint << (n.val-1 +(pos-1)/9%5*9))]
end as next_rows,
bp.cols[:c_idx-1] || ((bp.cols[c_idx]::int | (1 << (n.val-1)))::int)::int || bp.cols[c_idx+1:] as next_cols,
bp.boxes[:b_idx-1] || ((bp.boxes[b_idx]::int | (1 << (n.val-1)))::int)::int || bp.boxes[b_idx+1:] as next_boxes
, array_remove(positions, pos) as rem_pos,
known,
bp.pos,
n.val as filled_val
FROM best_pos bp
CROSS JOIN LATERAL (select val from generate_series(1, 9) n(val) WHERE get_bit(bp.available_mask::int::bit(9) ,9-n.val) = 1)n
CROSS JOIN LATERAL (
SELECT ((pos-1)/9) + 1 as r_idx,
((pos-1)%9) + 1 as c_idx,
((pos-1)/27*3 + (pos-1)%9/3) + 1 as b_idx
) idx
),
-- 矛盾检测:检查填充后是否有位置候选数为空
conflict_check AS (
SELECT
ns.id,
ns.next_board,
ns.next_rows,
ns.next_cols,
ns.next_boxes,
ns.rem_pos,
ns.known,
-- 检查是否有位置候选数为空(矛盾状态)
EXISTS (
SELECT 1
FROM unnest(ns.rem_pos) as p(pos)
CROSS JOIN LATERAL (
SELECT (
((ns.next_rows[(pos-1)/45 + 1]>>9*((pos-1)/9%5) & 511)::int |
ns.next_cols[(pos-1)%9 + 1]::int |
ns.next_boxes[((pos-1)/27*3 + (pos-1)%9/3) + 1]::int) # 511
) & 511 as available_mask
) mask
WHERE mask.available_mask = 0 -- 候选数为空,矛盾!
) /*false*/ as has_conflict
FROM next_step ns
)
SELECT
id,
next_board,
next_rows,
next_cols,
next_boxes,
(position('0' IN next_board) = 0) AND (NOT has_conflict) as solved, -- 已解决且无矛盾
rem_pos,
known+1 as known
FROM conflict_check
WHERE NOT has_conflict -- 过滤掉有矛盾的状态
)
)
SELECT i.id,
regexp_replace(i.board,'(.{9})', '\1' || chr(10),'g') AS puzzle ,
regexp_replace(v.board,'(.{9})', '\1' || chr(10),'g') AS result
FROM initial i
LEFT JOIN (
SELECT id, board
FROM (
SELECT id, board, row_number() OVER (PARTITION BY id ORDER BY known DESC) as rn
FROM solve
WHERE solved
) v0
WHERE rn = 1
) v ON i.id = v.id;
实测表明,提前过滤代价还是比下轮测试大,因为要算这么多候选数,而下轮实际候选数只选best_pos,过滤的比例大约在10%,不算太多,除非有更好的实现,不然没有必要。