Dancing Links 算法

本文内容遵从CC版权协议 转载请注明出自:   http://blog.csdn.net/masterluo

Dancing Links是解决完美覆盖问题的算法,我不得不说,这是我所见过的最优美的算法。难怪Donald E.Knuth为其取名为Dancing Links。大致思想是把问题转化为一个01矩阵,选取一些列,使每列都包含且只含一个1。采取的数据结构是双向十字链表结构。

Donald E.Knuth写的那篇论文己经有人翻译成了中文版,想了解的可以在这里sqybi.com/works/dlxcn/看到它的翻译版本。也许你在看完论文后会觉得写起程序来会比较好写,因为基本上核心代码在论文中都有比较详细的说明,然而真正的难题却是怎么样才能把问题转化为可以用dlx解决的结构。在论文中,作者写出了三个应用实例:应用于四边形,六边形与皇后问题。对于皇后问题,己经有了很好的解了,然而对于使用Dancing Links算法后比起直接回溯算法还是快了两倍左右。

关于问题怎么转化为一个01矩阵,主要是对于任意有冲突的两块,在某个位置都有有一个相同的1,这样两行就不能被同时选中。下面就一个16*16的数独问题进行简要说明。在一个16*16的矩阵中,每行每列每个方块都只包含A-P一次,己经某些位置己经填了一些字母了,求最后的解(保证有解)。

也许你己经看出来了这是9*9数独的强化版。你也许玩过下面这个游戏:

 

 将这样一类N2 *N2 的数独问题进行转化的思想是一样的。对于这样一类问题都可以转化成N6 行4 * N4  列的01矩阵,而且易知,其实对于每个位置的每个数来说,它只包含4个1,分别是位置,行的位置,列的位置,块的位置,其余位置都是0。这样对于这类关系就有很清楚的认识了。你只需要对于每个位置的每个数,对它进行变换为在原01矩阵中的位置再加入双向十字链表即可。

下面给出01矩阵的完美覆盖算法的代码:

 

  1. #include <iostream>
    #include <cstdio>
    #include <vector>
    using namespace std;

    /***最大行***/
    #define MAXROW 16
    /***最大列***/
    #define MAXCOL 300

    struct DancingLinksNode {
        /***结点所在的行列位置***/
        int r , c;
        /***结点的上下左右结点指针***/
        DancingLinksNode * U , * D , * L , * R;
    };

    /****备用结点****/
    DancingLinksNode node [ MAXROW * MAXCOL ];   
    /****行头****/
    DancingLinksNode row [ MAXROW ];
    /****列头****/
    DancingLinksNode col [ MAXCOL ];
    /****表头****/
    DancingLinksNode head;
    /****使用了多少结点****/
    int cnt;
    /****列含有多少个域****/
    int size [ MAXCOL ];
    /****表的行与列变量****/
    int m , n;

    /****初始化,r, c分别表示表的大小***/
    void init( int r , int c) {
        /****将可以使用的结点设为第一个****/
        cnt = 0;
        /****head结点的r,c分别表示表的大小,以备查****/
        head . r = r;
        head . c = c;
        /****初始化head结点****/
        head . L = head . R = head . U = head . D = & head;

        /***初始化列头***/
        for( int i = 0; i < c; ++ i) {
            col [ i ]. r = r;
            col [ i ]. c = i;
            col [ i ]. L = & head;
            col [ i ]. R = head . R;
            col [ i ]. L -> R = col [ i ]. R -> L = & col [ i ];
            col [ i ]. U = col [ i ]. D = & col [ i ];
            size [ i ] = 0;
        }


        /***初始化行头,在删除的时候,如果碰到row[i].c  == c的情形应当被跳过***/
        for( int i = r - 1; i > - 1; -- i) {
            row [ i ]. r = i;
            row [ i ]. c = c;
            row [ i ]. U = & head;
            row [ i ]. D = head . D;
            row [ i ]. U -> D = row [ i ]. D -> U = & row [ i ];
            row [ i ]. L = row [ i ]. R = & row [ i ];
        }
    }

    /****增加一个结点,在原表中的位置为r行,c列***/
    inline void addNode( int r , int c) {
        /****找一个未曾使用的结点****/
        DancingLinksNode * ptr = & node [ cnt ++ ];

        /****设置结点的行列号****/
        ptr -> r = r;
        ptr -> c = c;

        /****将结点加入双向链表中****/
        ptr -> R = & row [ r ];
        ptr -> L = row [ r ]. L;
        ptr -> L -> R = ptr -> R -> L = ptr;

        ptr -> U = & col [ c ];
        ptr -> D = col [ c ]. D;
        ptr -> U -> D = ptr -> D -> U = ptr;

        /****将size域加1****/
        ++ size [ c ];
    }

    /****删除ptr所指向的结点的左右方向****/
    inline void delLR( DancingLinksNode * ptr) {
        ptr -> L -> R = ptr -> R;
        ptr -> R -> L = ptr -> L;
    }

    /****删除ptr所指向的结点的上下方向****/
    inline void delUD( DancingLinksNode * ptr) {
        ptr -> U -> D = ptr -> D;
        ptr -> D -> U = ptr -> U;
    }

    /****重置ptr所指向的结点的左右方向****/
    inline void resumeLR( DancingLinksNode * ptr) {
        ptr -> L -> R = ptr -> R -> L = ptr;
    }

    /****重置ptr所指向的结点的上下方向****/
    inline void resumeUD( DancingLinksNode * ptr) {
        ptr -> U -> D = ptr -> D -> U = ptr;
    }

    /****覆盖第c例***/
    inline void cover( int c) {
        /**** c == n 表示头****/
        if( c == n) {
            return;
        }

        /****删除表头****/
        delLR( & col [ c ]);

        DancingLinksNode * R , * C;
        for( C = col [ c ]. D; C != ( & col [ c ]); C = C -> D) {
            if( C -> c == n)
                continue;
            for( R = C -> L; R != C; R = R -> L ){
                if( R -> c == n)
                    continue;
                -- size [ R -> c ];
                delUD( R);
            }
            delLR( C);
        }
       
    }

    /****重置第c列****/
    inline void resume( int c) {
        if( c == n)
            return;
        DancingLinksNode * R , * C;
        for( C = col [ c ]. U; C != ( & col [ c ]); C = C -> U) {
            if( C -> c == n)
                continue;
            resumeLR( C);
            for( R = C -> R; R != C; R = R -> R) {
                if( R -> c == n)
                    continue;
                ++ size [ R -> c ];
                resumeUD( R);
            }
        }

        /****把列头接进表头中****/
        resumeLR( & col [ c ]);
    }

    /****搜索核心算法,k表示搜索层数****/
    bool search( int k) {

        /***搜索成功,返回true***/
        if( head . L == ( & head)) {
            return true;
        }
        /***c表示下一个列对象位置,找一个分支数目最小的进行覆盖***/
        int INF = ( 1 << 30 ), c = - 1;
       
        for( DancingLinksNode * ptr = head . L; ptr != ( & head); ptr = ptr -> L) {
            if( size [ ptr -> c ] < INF) {
                INF = size [ ptr -> c ];
                c = ptr -> c;
            }
        }

        /***覆盖第c列***/
        cover( c);

        DancingLinksNode * ptr;

        for( ptr = col [ c ]. D; ptr != ( & col [ c ]); ptr = ptr -> D) {
            DancingLinksNode * rc;
            ptr -> R -> L = ptr;

            for( rc = ptr -> L; rc != ptr; rc = rc -> L) {
                cover( rc -> c);
            }
            ptr -> R -> L = ptr -> L;
            if( search( k + 1)) {
                return true;
            }
            ptr -> L -> R = ptr;
            for( rc = ptr -> R; rc != ptr; rc = rc -> R) {
                resume( rc -> c);
            }
            ptr -> L -> R = ptr -> R;
        }

        /***取消覆盖第c列***/
        resume( c);
        return false;
    }


    int main() {
        printf( "输入矩阵的行与列(输入EOF结束):");
        while( scanf( "%d %d" , & m , &n) != EOF) {
            int tvb;
            init( m , n);
            printf( "请输入01矩阵(以行优先): /n ");
            for( int i = 0; i < m; ++ i) {
                for( int j = 0; j < n; ++ j) {
                    scanf( "%d" , & tvb);
                    if( tvb) {
                        addNode( i , j);
                    }
                }
            }

            if( search( 0)) {
                puts( "存在完美覆盖");
            } else {
                puts( "不存在完美覆盖");
            }
        }
        return 0;
    }
     
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Dancing Links 算法是用于解决精确覆盖问题的算法,俄罗斯方块覆盖问题可以被转化为精确覆盖问题,因此可以使用 Dancing Links 算法求解。下面是使用 Matlab 实现 Dancing Links 算法解决俄罗斯方块覆盖问题的代码: ```matlab function [sol, num_solutions] = tetris_dlx(m, n, blocks) % m: 棋盘行数 % n: 棋盘列数 % blocks: 方块形状,每个方块用一个矩阵表示(0表示空,1表示方块) max_nodes = m * n * numel(blocks); % 初始化 Dancing Links 数据结构 dl = DancingLinks(max_nodes); % 构建 Dancing Links 矩阵 for i = 1:m for j = 1:n for k = 1:numel(blocks) block = blocks{k}; if i + size(block, 1) - 1 > m || j + size(block, 2) - 1 > n continue; end % 将方块转换为约束条件 constraint = zeros(m, n); constraint(i:i+size(block,1)-1, j:j+size(block,2)-1) = block; % 将约束条件插入 Dancing Links 矩阵 columns = (i-1)*n + j + (0:numel(constraint)-1)*m*n; dl.insert_constraint(columns, constraint(:)); end end end % 解决 Dancing Links 矩阵,得到所有解 solutions = dl.solve(); % 将解转换为棋盘布局 num_solutions = size(solutions, 2); sol = cell(num_solutions, 1); for i = 1:num_solutions sol{i} = zeros(m, n); for j = solutions(:, i)' [row, col, k] = ind2sub([m, n, numel(blocks)], j); block = blocks{k}; sol{i}(row:row+size(block,1)-1, col:col+size(block,2)-1) = block; end end end ``` 这个函数接受三个参数:棋盘行数 `m`,棋盘列数 `n`,和方块形状 `blocks`。`blocks` 是一个包含所有方块形状的矩阵数组,每个矩阵表示一个方块,其中 0 表示空,1 表示方块。函数返回两个值:`sol` 是包含所有解的单元格数组,每个单元格表示一个解的棋盘布局;`num_solutions` 是找到的解的数量。 下面是一个使用示例: ```matlab % 定义方块形状 block1 = [1 1; 1 1]; block2 = [1 1 1; 0 1 0]; block3 = [1 1 1; 0 1 1]; block4 = [1 1 0; 0 1 1]; blocks = {block1, block2, block3, block4}; % 解决俄罗斯方块覆盖问题 sol = tetris_dlx(4, 4, blocks); for i = 1:numel(sol) disp(sol{i}); end ``` 这个例子解决了一个 4x4 的俄罗斯方块覆盖问题,使用了四种不同的方块形状。程序输出所有解的棋盘布局。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值