JAVA数独解题(九):数链法(数串、垂直)

284 阅读4分钟

说明

参考文章:数独高级技巧:数串法的原理与应用详解(19年12月27日) 如下图所示,数字n在某一列中,只有两个空白格B1,B2(不在同宫中)存在,并且在行中也只有两个空白格B3,B4(不在同宫中)。并且两个端点都在某个宫中。则另外两个端点相交的其他宫的空白格B5,则没有数字n。

图片

在这里插入图片描述 在这里插入图片描述

算法代码

CalcEnum

在 绑定算法 中添加 属性

package com.suduku.calc.enums;

import com.suduku.calc.*;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.HashMap;
import java.util.Map;

/**
 * 功能描述: 算法枚举 <br/>
 *
 */
@Getter
@AllArgsConstructor
public enum CalcEnum {

    /***/
    ONLY_NUM(OnlyNumCalc.class, "唯余法"),
    ONLY_BOX(OnlyBoxCalc.class, "摒除法"),
    GRID_XY(GridXYCalc.class, "单宫行列法"),
    SU_DUI(SuDuiCalc.class, "数对法"),
    X_WING(XwingCalc.class, "X-wing"),
    Y_WING(YwingCalc.class, "Y-wing"),
    XY_WING(XYwingCalc.class, "XY-wing"),
    YX_WING(YXwingCalc.class, "YX-wing"),
    XYZ_WING(XYZwingCalc.class, "XYZ-wing"),
    SU_LIAN_V(SuLianVerticalCalc.class, "数链垂直"), // 需要测试数据符合算法,调整顺序
    SU_LIAN_T(SuLianTrapezoidCalc.class, "数链梯形"),
    ;

    private static final Map<Class<? extends AbstractCalc>, CalcEnum> CE_MAP = new HashMap<>(CalcEnum.values().length);

    static {
        for(CalcEnum ce : CalcEnum.values()) {
            CE_MAP.put(ce.getClazz(), ce);
        }
    }

    /**
     * 功能描述: 通过类,获取枚举 <br/>
     *
     * @param clazz 类
     * @return "com.suduku.calc.enums.CalcEnum"
     */
    public static CalcEnum indexOf(Class<? extends AbstractCalc> clazz) {
        return CE_MAP.get(clazz);
    }

    private final Class<? extends AbstractCalc> clazz;
    private final String name;

}

SuLianVerticalCalc

package com.suduku.calc;

import com.suduku.entity.Box;
import com.suduku.util.SudoUtil;

import java.util.List;
import java.util.Map;

/**
 * 数链-垂直(数字n在某一列中,只有两个空白格B1,B2(不在同宫中)存在,
 * 并且在行中也只有两个空白格B3,B4(不在同宫中)。并且两个端点都在某个宫中。
 * 则另外两个端点相交的其他宫的空白格B5,则没有数字n) <br/>
 *
 * 测试数据:DataConstant.OTHER_SU_LIAN_V_01
 */
public class SuLianVerticalCalc extends AbstractCalc {

    /**
     *  1. 遍历所有数字
     *  2. 遍历所有列,获取只有两个候选值有数字n的空白格B1,B2,不能在同一宫中(可能存在多列)
     *  3. 遍历所有行,获取只有两个候选值有数字n的空白格B3,B4,不能在同一宫中(可能存在多行)
     *  4. 判断是否有两个顶点在同一个宫中。
     *  5. 如果存在有两个顶点在同一宫中,则另外两个顶点垂直相交的另一个点是空白格,则不包含数字n
     */
    @Override
    Box solve() {
        // 遍历数字
        for(Integer n : Box.INIT_LIST) {
            // 遍历所有列
            for (Map.Entry<Integer, List<Box>> yEntry : getYMap().entrySet()) {
                // 获取B1,B2两个空白格
                List<Box> b12List = SudoUtil.findBoxByCList(yEntry.getValue(), n);
                if(SudoUtil.isTwoBox(b12List)) {
                    // 遍历所有行,获取B3,B4两个空白格
                    for (Map.Entry<Integer, List<Box>> xEntry : getXMap().entrySet()) {
                        List<Box> b34List = SudoUtil.findBoxByCList(xEntry.getValue(), n);
                        if(SudoUtil.isTwoBox(b34List)) {
                            // B1B3相交同一宫中
                            Box clearBox = intersectAndClear(b12List.get(1), b12List.get(0), b34List.get(0), b34List.get(1), n);
                            if(clearBox != null) {
                                return clearBox;
                            }
                            // B1B4相交同一宫中
                            clearBox = intersectAndClear(b12List.get(1), b12List.get(0), b34List.get(1), b34List.get(0), n);
                            if(clearBox != null) {
                                return clearBox;
                            }
                            // B2B3相交同一宫中
                            clearBox = intersectAndClear(b12List.get(0), b12List.get(1), b34List.get(0), b34List.get(1), n);
                            if(clearBox != null) {
                                return clearBox;
                            }
                            // B2B4相交同一宫中
                            clearBox = intersectAndClear(b12List.get(0), b12List.get(1), b34List.get(1), b34List.get(0), n);
                            if(clearBox != null) {
                                return clearBox;
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

    /**
     * 功能描述: 相交与同一宫中,并且清理数字n <br/>
     * 
     * @param b1 列非相交点
     * @param b2 列相交点
     * @param b3 行相交点
     * @param b4 行非相交点
     * @param n 候选值n
     * @return "com.suduku.entity.Box"
     */
    private Box intersectAndClear(Box b1, Box b2, Box b3, Box b4, Integer n) {
        if(b2.getG() == b3.getG()) {
            Box box = getBoxByXY(b1.getX(), b4.getY());
            if(box != null && box.isBlank() && box.getCList().contains(n)) {
                getListener().sendMsg("清除数字%d\n", n);
                box.removeCList(n);
                return box;
            }
        }
        return null;
    }

}

SudoUtil

    /**
     * 功能描述: 判断是否不在同一宫中的两个单元格 <br/>
     *
     * @param bList 待判断单元格列表
     * @return "boolean"
     */
    public static boolean isTwoBox(List<Box> bList) {
        return bList.size() == 2 && bList.get(0).getG() != bList.get(1).getG();
    }

测试数据

在 DataConstant 中添加

public static final String OTHER_X_WING_01 = "000300100500401090001028600090800001008017002010040800004085000300100040002634000";

输出结果

尝试【数链垂直	】
清除数字3
确认位置【行:1,列:6】	值为:【7】	候选值为:【】
 0 (3        ) 2 (         ) 9 (         ) |  1 (         ) 4 (         ){7}(         ) |  6 (         ) 8 (         ) 5 (         ) | 
 5 (         ) 1 (         ) 4 (         ) |  8 (         ) 6 (         ) 2 (         ) |  0 (37       ) 0 (39       ) 0 (379      ) | 
 6 (         ) 0 (378      ) 0 (38       ) |  0 (39       ) 0 (9        ) 5 (         ) |  2 (         ) 1 (         ) 4 (         ) | 

 1 (         ) 6 (         ) 7 (         ) |  5 (         ) 2 (         ) 8 (         ) |  0 (34       ) 0 (349      ) 0 (39       ) | 
 0 (48       ) 0 (348      ) 0 (358      ) |  7 (         ) 1 (         ) 9 (         ) |  0 (58       ) 2 (         ) 6 (         ) | 
 2 (         ) 9 (         ) 0 (58       ) |  6 (         ) 3 (         ) 4 (         ) |  0 (58       ) 7 (         ) 1 (         ) | 

 0 (37       ) 5 (         ) 2 (         ) |  4 (         ) 8 (         ) 1 (         ) |  9 (         ) 6 (         ) 0 (37       ) | 
 0 (49       ) 0 (34       ) 6 (         ) |  2 (         ) 0 (79       ) 0 (3        ) |  1 (         ) 5 (         ) 8 (         ) | 
 0 (89       ) 0 (378      ) 1 (         ) |  0 (39       ) 5 (         ) 6 (         ) |  0 (347      ) 0 (34       ) 2 (         ) | 

最终结果

 3 (         ) 2 (         ) 9 (         ) |  1 (         ) 4 (         ) 7 (         ) |  6 (         ) 8 (         ) 5 (         ) | 
 5 (         ) 1 (         ) 4 (         ) |  8 (         ) 6 (         ) 2 (         ) |  3 (         ) 9 (         ) 7 (         ) | 
 6 (         ) 7 (         ) 8 (         ) |  3 (         ) 9 (         ) 5 (         ) |  2 (         ) 1 (         ) 4 (         ) | 

 1 (         ) 6 (         ) 7 (         ) |  5 (         ) 2 (         ) 8 (         ) |  4 (         ) 3 (         ) 9 (         ) | 
 4 (         ) 8 (         ) 3 (         ) |  7 (         ) 1 (         ) 9 (         ) |  5 (         ) 2 (         ) 6 (         ) | 
 2 (         ) 9 (         ) 5 (         ) |  6 (         ) 3 (         ) 4 (         ) |  8 (         ) 7 (         ) 1 (         ) | 

 7 (         ) 5 (         ) 2 (         ) |  4 (         ) 8 (         ) 1 (         ) |  9 (         ) 6 (         ) 3 (         ) | 
 9 (         ) 4 (         ) 6 (         ) |  2 (         ) 7 (         ) 3 (         ) |  1 (         ) 5 (         ) 8 (         ) | 
 8 (         ) 3 (         ) 1 (         ) |  9 (         ) 5 (         ) 6 (         ) |  7 (         ){4}(         ) 2 (         ) | 

============数独解题完成,尝试次数为:45============

总结

在编写改算法的过程中,发现了数对方法考虑的场景漏了,补充了数对的场景。 相关链接:JAVA数独解题(四):数对法

代码详情

代码地址