用Dancing Links解决数独(Sudoku)问题

Dancing Links可以认为是一种数据结构(好像本校面向大二年级开设的数据结构课程中就有它),其实就是一种链表,准确地讲是十字双向循环链表
十字:普通链表是一条线,而十字链表就是一张网,每个结点不仅有左右邻居,还有上下邻居。
双向:双向是链表中很经典的概念,即每个结点要保存上下左右四个方向的邻居指针。
循环:循环也是比较常见的概念了,即每一行的最左结点的左邻居设置为这一行的最右结点,最右结点的右邻居设置为最左结点;每一列同理。
至此,Dancing Links的基本样子就有了。对于有一点点数据结构基础的同学来说(比如我),十字双向循环链表可能比一整页的详细描述更好懂吧……
不难看出,这个数据结构很像是矩阵,很适合用来维护稀疏矩阵的元素。
不过,Dancing Links一般还含有两种特殊类型的结点——头结点(Head)和列结点(Column)。
如果我们把矩阵元素坐标用\((1...n,1...m)\)来表示,那么头结点就是\((0,0)\),列结点就是\((0,1...m)\)
头结点可以看为是我们访问所有结点的一个入口,额外创建一个这样的头结点是因为我们在后续过程中始终不会将该结点移除,不像其他结点可能被删掉(那样我们用那个结点就不一定能找到当前剩余的全部元素了)。
列结点私以为是用来辅助Dancing Links X算法的(一个用来解决Exact Cover问题的算法)。
来自HatenaBlog

Exact Cover 问题

设全集是\(S\),给出一些子集\(S_i\),要求选一些子集出来,恰好包含\(S\)的所有元素且每个元素只被一个子集包含。

算法描述

首先用01矩阵\(A\)描述上面的问题。
每一行代表一个子集,每一列代表一个元素。
比如子集\(S_i\)包含元素\(a_j\),那么\(A_{i,j}=1\);如果子集\(S_i\)不包含元素\(a_j\),那么\(A_{i,j}=0\)
问题转化为选择一些行,这些行上的1恰好不重复地覆盖每一列。
采用递归搜索的方法解决此问题。

  1. 选择矩阵中一列\(c\),考虑用某该列为1的行来覆盖这一列。
  2. 枚举该列为1的行,比如\(r\)行。
  3. 删除因为此次决策被覆盖掉的列、行,以及因为此次决策而不可能选取的行。
  4. 递归搜索,从第1条开始。
  5. 回溯,恢复本次决策删掉的所有行和列。
    算法描述大概就是这样子的。

Arxiv Paper

数独问题

9x9的数独问题
16x16的数独问题

class Base(object):
    def __init__(self, id):
        self.id = id
        self.l = self
        self.r = self
        self.u = self
        self.d = self

class Head(Base):
    def __init__(self):
        super().__init__(id=('head'))

class Cell(Base):
    def __init__(self, x, y, c=None):
        super().__init__(id=('cell', x, y))
        if c:
            self.c = c
            self.u = c.u
            self.d = c
            self.u.d = self
            self.d.u = self
            self.c.s += 1

class Coln(Base):
    def __init__(self, c):
        super().__init__(id=('coln', c))
        self.s = 0

class DancingLinks(object):
    def __init__(self, head):
        self.h = head

    def _cover(self, c):
        c.r.l = c.l
        c.l.r = c.r
        i = c.d
        while i != c:
            j = i.r
            while j != i:
                j.d.u = j.u
                j.u.d = j.d
                j.c.s -= 1
                j = j.r
            i = i.d

    def _uncover(self, c):
        i = c.u
        while i != c:
            j = i.l
            while j != i:
                j.c.s += 1
                j.u.d = j
                j.d.u = j
                j = j.l
            i = i.u
        c.l.r = c
        c.r.l = c

    def _choose_col(self):
        c, s = None, float('inf')
        i = self.h.r
        while i != self.h:
            if s > i.s:
                c, s = i, i.s
            i = i.r
        return c

    def _search(self, s):
        if self.fall or len(self.ans) == 0:
            if self.h == self.h.r:
                self.ans.append(s[:])
            else:
                c = self._choose_col()
                self._cover(c)
                i = c.d
                while i != c:
                    s.append(i.id)
                    j = i.r
                    while j != i:
                        self._cover(j.c)
                        j = j.r
                    self._search(s)
                    j = i.l
                    while j != i:
                        self._uncover(j.c)
                        j = j.l
                    i = i.d
                self._uncover(c)

    def run(self, find_all=False):
        self.ans = []
        self.fall = find_all
        if self.h != self.h.r:
            self._search([])
        return self.ans

    @classmethod
    def from_sudoku(cls, grid):
        n = len(grid)
        m = int(n**0.5)
        h = Head()
        a = []
        for i in range(4*n*n):
            c = Coln(i)
            c.r = h
            c.l = h.l
            c.l.r = c
            c.r.l = c
            a.append(c)
        pos = lambda x, y, k: x * n + y
        row = lambda x, y, k: x * n + k + n*n
        col = lambda x, y, k: y * n + k + n*n*2
        box = lambda x, y, k: ((x // m) * m + (y // m)) * n + k + n*n*3
        idx = lambda x, y, k: (x * n + y) * n + k
        def link_row(a, b, c, d):
            a.l, b.l, c.l, d.l = d, a, b, c
            a.r, b.r, c.r, d.r = b, c, d, a
        def create(x, y, k):
            p = Cell(x=idx(x, y, k), y=pos(x, y, k), c=a[pos(x, y, k)])
            r = Cell(x=idx(x, y, k), y=row(x, y, k), c=a[row(x, y, k)])
            c = Cell(x=idx(x, y, k), y=col(x, y, k), c=a[col(x, y, k)])
            b = Cell(x=idx(x, y, k), y=box(x, y, k), c=a[box(x, y, k)])
            link_row(p, r, c, b)
        for i in range(n):
            for j in range(n):
                if grid[i][j] == 0:
                    for k in range(n):
                        create(i, j, k)
                else:
                    create(i, j, grid[i][j]-1)
        return cls(h)

def soduku(grid):
    grid = [i[:] for i in grid]
    n = len(grid)
    d = DancingLinks.from_sudoku(grid)
    a = d.run()[0]
    for i in a:
        r = i[1]
        k = r % n
        y = (r // n) % n
        x = ((r // n) // n) % n
        grid[x][y] = k + 1
    return grid

def nextline():
    s = input().strip()
    if len(s) == 0:
        return nextline()
    else:
        return s

def soduko9():
    def input_grid():
        grid = []
        for i in range(9):
            grid.append([])
            for j in nextline():
                grid[-1].append(int(j))
        return grid

    def output_grid(grid):
        for i in grid:
            for j in i:
                print(j, end='')
            print("")

    cas = int(input())
    for c in range(cas):
        output_grid(soduku(input_grid()))

def soduko16():
    def input_grid():
        grid = []
        for i in range(16):
            grid.append([])
            for j in nextline():
                grid[-1].append(ord(j)-ord('A')+1 if j != '-' else 0)
        return grid

    def output_grid(grid):
        for i in grid:
            for j in i:
                print(chr(ord('A')+j-1), end='')
            print("")
        print("")

    while True:
        try:
            output_grid(soduku(input_grid()))
        except EOFError:
            exit(0)

if __name__ == '__main__':
    # soduko9()
    # soduko16()

猜你喜欢

转载自www.cnblogs.com/you-siki/p/dancing-links-and-sudoku.html
今日推荐