DeepSeek辅助编写添加了矛盾检测的数独求解SQL

在每次填充数字后,立即检查是否有位置候选数为空(无解状态)。

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%,不算太多,除非有更好的实现,不然没有必要。

相关推荐
欧亚学术16 小时前
突发!刚刚新增17本期刊被剔除!
数据库·论文·sci·期刊·博士·scopus·发表
踩坑记录16 小时前
leetcode hot100 11.盛最多水的容器 medium 双指针
算法·leetcode·职场和发展
黑白极客16 小时前
怎么给字符串字段加索引?日志系统 一条更新语句是怎么执行的
java·数据库·sql·mysql·引擎
MM_MS17 小时前
Halcon基础知识点及其算子用法
开发语言·人工智能·python·算法·计算机视觉·视觉检测
大厂技术总监下海17 小时前
数据湖加速、实时数仓、统一查询层:Apache Doris 如何成为现代数据架构的“高性能中枢”?
大数据·数据库·算法·apache
hetao173383717 小时前
2026-01-06 hetao1733837 的刷题笔记
c++·笔记·算法
LeenixP17 小时前
RK3576-Debian12删除userdata分区
linux·运维·服务器·数据库·debian·开发板
a努力。17 小时前
国家电网Java面试被问:最小生成树的Kruskal和Prim算法
java·后端·算法·postgresql·面试·linq
知行合一。。。17 小时前
Python--03--函数入门
android·数据库·python
洛生&17 小时前
Counting Towers
算法