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
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值