用pl/sql解决芬兰数学家因卡拉设计的最难数独

      最近看朋友在玩消灭星星(pop start),想起当年还不是彩屏手机的时候,我喜欢玩数独游戏.数独(Sudoku)又称九宫格,据说来头很大,起源于河图洛书,相信大家都有玩过.百度九宫格,
百度百科最后有提到芬兰数学家因卡拉花费3个月设计出了世界上迄今难度最大的九宫格游戏,而且它只有一个答案,就想挑战一下.水平有限花了2天时间问题解决,速度还行2秒内就
能解答,发现还是比较简单的,大概的分为两段,第一段是找到唯一的那些值填上做为永久值,然后再找唯一值做为永久值,直到找不到唯一值.第二段通过回溯,把可能的值按顺序尝试,不
合适就回退,至到找到最终合适的.


以下脚本及解释:

DECLARE
  --定义数独行列
  TYPE T_ROW IS VARRAY(9) OF NUMBER;
  TYPE T_COL_ROW IS VARRAY(9) OF T_ROW;  
  --初始化数独
  v_init   T_COL_ROW := T_COL_ROW(T_ROW(8, 0, 0, 0, 0, 0, 0, 0, 0),
                                  T_ROW(0, 0, 3, 6, 0, 0, 0, 0, 0),
                                  T_ROW(0, 7, 0, 0, 9, 0, 2, 0, 0),
                                  
                                  T_ROW(0, 5, 0, 0, 0, 7, 0, 0, 0),
                                  T_ROW(0, 0, 0, 0, 4, 5, 7, 0, 0),
                                  T_ROW(0, 0, 0, 1, 0, 0, 0, 3, 0),
                                  
                                  T_ROW(0, 0, 1, 0, 0, 0, 0, 6, 8),
                                  T_ROW(0, 0, 8, 5, 0, 0, 0, 1, 0),
                                  T_ROW(0, 9, 0, 0, 0, 0, 4, 0, 0));
  --用以存放答案
  v_result T_COL_ROW;

  --定义一个用以计算小宫里面的同组数据的位置
  TYPE T_X IS VARRAY(2) OF NUMBER;
  TYPE T_Y IS VARRAY(3) OF T_X;
  --用以根据相对位置,算出同组数据的绝对位置
  v_xy T_Y := T_Y(T_X(1, 2), T_X(-1, 1), T_X(-1, -2));
 
  x integer;
  y integer;
 
  --定义每个点的可能值,i存放列位置,j存放行位置,val存放可能值(可能值里面存放的是多个值,根据cur确认当前是那个),cur的值用于从val中找到当前值
  TYPE T_POS IS record(
    i   number,
    j   number,
    val varchar2(10),
    cur number);
  TYPE T_SEL IS TABLE OF T_POS INDEX BY BINARY_INTEGER;

  v_sel T_SEL;

  v_n         varchar2(10);
  v_flag      number := 1;
  v_sel_count number := 0;
  v_hs        number := 0;
BEGIN
  --打印问题数独
  dbms_output.put_line('问题:');
  FOR i IN 1 .. v_init.COUNT LOOP
    FOR j IN 1 .. v_init(i).COUNT LOOP
      dbms_output.put(v_init(i) (j) || '  ');
    END LOOP;
    dbms_output.put_line('');
  END LOOP;
 
  --通过循环找唯一值,把唯一值做为永久值,直到找不到唯一值
  while v_flag = 1 loop
    v_flag := 0;
    v_sel.delete;
    
    FOR i IN 1 .. v_init.COUNT LOOP
      FOR j IN 1 .. v_init(i).COUNT LOOP
        --v_init(i) (j) = 0的是用来填答案的,其它就是永久值
        if v_init(i) (j) = 0 then
          v_n := '';
          --x,y是用来计算除行,列之后还在去对比的同组中的4个值
          x := case
                 when mod(i, 3) = 0 then
                  3
                 else
                  mod(i, 3)
               end;
          y := case
                 when mod(j, 3) = 0 then
                  3
                 else
                  mod(j, 3)
               end;
          --通过循环找到可能值,把可能的值存入v_n
          for k in 1 .. 9 loop
            if k not in
               (--行
                v_init(i) (1),
                v_init(i) (2),
                v_init(i) (3),
                v_init(i) (4),
                v_init(i) (5),
                v_init(i) (6),
                v_init(i) (7),
                v_init(i) (8),
                v_init(i) (9),
                --列
                v_init(1) (j),
                v_init(2) (j),
                v_init(3) (j),
                v_init(4) (j),
                v_init(5) (j),
                v_init(6) (j),
                v_init(7) (j),
                v_init(8) (j),
                v_init(9) (j),
                --同组另外的四个要对比的值
                v_init(i + v_xy(x) (1)) (j + v_xy(y) (1)),
                v_init(i + v_xy(x) (1)) (j + v_xy(y) (2)),
                v_init(i + v_xy(x) (2)) (j + v_xy(y) (1)),
                v_init(i + v_xy(x) (2)) (j + v_xy(y) (2))) then
              begin
                v_n := v_n || k;
              end;
            end if;
          
          end loop;
          --把可能值的位置,值存入队列
          v_sel_count := v_sel.count + 1;
          v_sel(v_sel_count).i := i;
          v_sel(v_sel_count).j := j;
          v_sel(v_sel_count).val := v_n;
          v_sel(v_sel_count).cur := 1;
          --如果v_n=1就是唯一值,把这个值永久化,并把v_flag设为1用以再循环一次
          --如果v_n=0说明在此位置没有任何合适的值,说明这个数独开始的数据有问题
          if length(v_n) = 1 then
            v_init(i)(j) := to_number(v_n);
            v_flag := 1;
          elsif length(v_n) = 0 then
            dbms_output.put_line('数独初始化的数据错误!');
            exit;
          end if;
        end if;
      END LOOP;
    END LOOP;
  end loop;

  --把做过唯一值永久化后的数据赋值给答案数独
  v_result := v_init;
 
  --开始回溯算法通过可能值找最终答案
  while v_hs < v_sel.COUNT LOOP
 
    v_hs := v_hs + 1;
    --x,y是用来计算除行,列之后还在去对比的同组中的4个值
    x := case
           when mod(v_sel(v_hs).i, 3) = 0 then
            3
           else
            mod(v_sel(v_hs).i, 3)
         end;
    y := case
           when mod(v_sel(v_hs).j, 3) = 0 then
            3
           else
            mod(v_sel(v_hs).j, 3)
         end;
    --判断当前值是否合适,如果不合适,当前位置的可能值向后推进一位,如果当前位置的值已经用完,再后回退,上一层的可能值推进
    if to_number(substr(v_sel(v_hs).val, v_sel(v_hs).cur, 1)) in
       (v_result(v_sel(v_hs).i) (1),
        v_result(v_sel(v_hs).i) (2),
        v_result(v_sel(v_hs).i) (3),
        v_result(v_sel(v_hs).i) (4),
        v_result(v_sel(v_hs).i) (5),
        v_result(v_sel(v_hs).i) (6),
        v_result(v_sel(v_hs).i) (7),
        v_result(v_sel(v_hs).i) (8),
        v_result(v_sel(v_hs).i) (9),
        
        v_result(1) (v_sel(v_hs).j),
        v_result(2) (v_sel(v_hs).j),
        v_result(3) (v_sel(v_hs).j),
        v_result(4) (v_sel(v_hs).j),
        v_result(5) (v_sel(v_hs).j),
        v_result(6) (v_sel(v_hs).j),
        v_result(7) (v_sel(v_hs).j),
        v_result(8) (v_sel(v_hs).j),
        v_result(9) (v_sel(v_hs).j),
        
        v_result(v_sel(v_hs).i + v_xy(x) (1)) (v_sel(v_hs).j + v_xy(y) (1)),
        v_result(v_sel(v_hs).i + v_xy(x) (1)) (v_sel(v_hs).j + v_xy(y) (2)),
        v_result(v_sel(v_hs).i + v_xy(x) (2)) (v_sel(v_hs).j + v_xy(y) (1)),
        v_result(v_sel(v_hs).i + v_xy(x) (2)) (v_sel(v_hs).j + v_xy(y) (2))) then
      if v_sel(v_hs).cur < length(v_sel(v_hs).val) then
        begin
          v_sel(v_hs).cur := v_sel(v_hs).cur + 1;
          v_hs := v_hs - 1;
        end;
      else
        if v_hs > 1 then
          v_result(v_sel(v_hs).i)(v_sel(v_hs).j) := 0;
          v_sel(v_hs).cur := 1;
          v_hs := v_hs - 2;
        else
          dbms_output.put_line('数独初始化的数据错误!');
          exit;
        end if;
      end if;
    else
      v_result(v_sel(v_hs).i)(v_sel(v_hs).j) := to_number(substr(v_sel(v_hs).val,
                                                                 v_sel(v_hs).cur,
                                                                 1));
    
    end if;
  END LOOP;
 
  --打印答案数独
  dbms_output.put_line('答案:');
  FOR i IN 1 .. v_result.COUNT LOOP
    FOR j IN 1 .. v_result(i).COUNT LOOP
      dbms_output.put(v_result(i) (j) || '  ');
    END LOOP;
    dbms_output.put_line('');
  END LOOP;
END;

备注:
另外再放两个数独,有兴趣的可以测试.
(0, 0, 0, 0, 0, 0, 0, 0, 0),
(0, 0, 0, 0, 0, 0, 0, 0, 0),
(0, 0, 0, 0, 0, 0, 0, 0, 0),
                    
(0, 0, 0, 0, 0, 0, 0, 0, 0),
(0, 0, 0, 0, 0, 0, 0, 0, 0),
(0, 0, 0, 0, 0, 0, 0, 0, 0),
                    
(0, 0, 0, 0, 0, 0, 0, 0, 0),
(0, 0, 0, 0, 0, 0, 0, 0, 0),
(0, 0, 0, 0, 0, 0, 0, 0, 0)
---------------------------------------  
 
(0, 6, 2, 0, 0, 0, 5, 0, 0),
(3, 5, 0, 0, 0, 0, 0, 0, 7),
(0, 0, 8, 0, 1, 5, 0, 4, 0),
                    
(0, 0, 0, 0, 6, 0, 9, 0, 8),
(9, 0, 0, 0, 0, 0, 0, 2, 0),
(0, 2, 5, 0, 9, 0, 0, 0, 0),
                    
(5, 0, 0, 0, 3, 0, 7, 0, 0),
(0, 8, 0, 0, 0, 0, 0, 5, 6),
(0, 0, 9, 0, 0, 0, 8, 0, 4)

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/28539951/viewspace-1822270/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/28539951/viewspace-1822270/

你可能感兴趣的:(用pl/sql解决芬兰数学家因卡拉设计的最难数独)