【问题标题】:How to count the number of possible solutions for a sudoku board using backtracking如何使用回溯计算数独板的可能解决方案数量
【发布时间】:2021-09-16 09:31:37
【问题描述】:

这是对以下内容的跟进: Need some feedback on my fold_neighbours attempt on this problem in Ocaml

我得到了一些非常好的建议,并在我实施这个版本时应用了它们。

这就是我现在所拥有的,该程序实际上可以读取数独并解决它。这是一个演示:

Input board 
530070000
600195000
098000060
800060003
400803001
700020006
060000280
000419005
000080079

Solved board 
534678912
672195348
198342567
859761423
426853791
713924856
961537284
287419635
345286179

Input board 
480006902
002008001
900370060
840010200
003704100
001060049
020085007
700900600
609200018

Solved board 
487156932
362498751
915372864
846519273
593724186
271863549
124685397
738941625
659237418

这是目前的代码

type vertex = int * int 

type gamma = int


module V = Map.Make(struct
    type t = vertex
    let compare = Stdlib.compare
  end)

module G = Set.Make(struct
    type t = gamma 
    let compare = compare end)

type pc = gamma V.t

(* Help methods for coloring *)

let color v pc = V.find v pc
let color_vertex v c pc = V.update v (fun _ -> Some c) pc
let is_uncolored v pc = color v pc = 0

let allcolors = (let acc = ref G.empty
                 in
                 for gamma = 1 to 9 do
                   acc := G.add gamma (!acc)
                 done;
                 !acc
                )

(* Reads a textfile representing sudoku *)
let ascii_digit c = Char.code c - Char.code '0'

let read_matrix_from_channel chan =
  let rec loop i j grid =
    match input_char chan with
    | exception End_of_file -> grid
    | '\n' -> loop (i+1) 0 grid
    | '0'..'9' as c ->
      loop i (j+1) @@
      V.add (i,j) (ascii_digit c) grid
    | _ -> invalid_arg "invalid input" in
  loop 0 0 V.empty

let matrix_from_file file =
  let chan = open_in file in
  let r = read_matrix_from_channel chan in
  close_in chan;
  r

(* Prints a pretty sudokuboard (not used in the demo) *)
let print_board vertex = 
  let print_node (_x,_y) _grid =
    if _y = 0
    then Printf.printf "\n | ";
    print_int _grid;
    print_string " | "
  in 
  V.iter print_node vertex


(*executes a function on all vertices *)
let fold_vertices f acc =
  let acc' = ref acc in
  for i=0 to 8 do
    for j=0 to 8 do
      acc' := f (i,j) (!acc')
    done
  done;
  !acc'


let find_vertex pred =
  fold_vertices
    (fun v a -> match a with
       | None -> if pred v then Some v else None
       | _ -> a) None

(* neighbours *)
let is_neighbour (i,j) (i',j') =
  if (i,j) = (i',j') then
    false
  else if i = i' || j = j' then
    true
  else
    (i/3) = (i'/3) && (j/3) = (j'/3)

(* fold_neighbours  *)
let fold_neighbours f v acc =
  let ff u acc =
    if is_neighbour u v then f u acc else acc
  in fold_vertices ff acc            

(* Checks if vertex is allowed *)
let allowed v pc =
  fold_neighbours (fun u allowed ->
      match color u pc with
      | 0 -> allowed
      | c -> G.remove c allowed)
    v allcolors


(* Solve *)
let rec solve pc =
  match find_vertex (fun v -> is_uncolored v pc) with
  | Some v ->
    let all = allowed v pc in
    G.fold (fun c -> function
        | None -> color_vertex v c pc |> solve
        | opc -> opc)
      all None
  | None -> Some pc

我现在遇到了问题,不知道如何解决。我想使用回溯计算数独的所有可能解决方案。所以我只想将解决方案的数量作为整数返回。

我认为我需要在“nsolve”方法中递归调用solve 方法。如果没有解决方案,则返回 None,否则我检查解决方案是否与之前的相同,如果不是,则返回 Some solution 并计数,否则我返回 None。

否则,也许有一些方法可以得到近似的解数。因为我猜对于一个几乎空的数独板,递归地获得解决方案的数量需要很长时间。一个空的数独板有 10^21 个解,数量很多。我不知道如何解决这个问题。 我还有其他方法吗?

这是我试图实现的方法([nsolve pc]):

(** [nsolve pc] uses backtracking to count the number of possible solution to the sudoku. *)
let rec nsolve pc = .....

【问题讨论】:

  • 这似乎更像是一个领域问题而不是编程问题,因此在 SO 上是题外话。也许它更适合Mathematics
  • 可能值得注意的是,计算格式良好数独的解很容易。只有一个。
  • 克里斯 - 是的,但我认为我们不应该忽略“较少”的格式良好的
  • 这不是数学问题。数学已经回答了(也许不是确定的,见afjarvis.staff.shef.ac.uk/sudoku/felgenhauer_jarvis_spec1.pdf),并说最后他们必须蛮力。所以 OP 的问题是如何使用回溯来暴力破解解决方案的数量。
  • 你已经有一个反向跟踪求解器,唯一的问题是它为同一个板返回相同的答案。您需要以某种方式强制它返回不同的答案。我看到了两种方法:(1)您可以置换您的颜色集(现在您每次都以相同的顺序分配颜色),或者(2)您可以限制您的求解器以防止它选择已经采用的解决方案。

标签: ocaml


【解决方案1】:

最简单的方法是暴力破解。在伪代码中,这将是

fun nb_sol cpt i j grid = 
  if i > 8 /\ j > 8 then cpt + 1
  else if is_empty grid(i)(j) then 
    for k = 1 to 9 do 
      grid(i)(j) <- k
      if is_ok grid then
        res += (if j > 8 then nb_sol cpt (i+1) 0 grid
          else nb_sol cpt i (j+1) grid)
    done
    grid(i)(j) <- empty

我没有检查它是否正是你想要的,但你明白了:-)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-04-15
    • 1970-01-01
    • 2020-04-06
    • 2011-10-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多