dancing link X算法(DLX算法)详解

我觉得这个博客讲的很不错,就转载过来啦。
理解DLX算法的含义这个很不错,但是拿来做算法竞赛题,我还是觉得kuangbin大佬的板子(静态链表)比较好用。
转载自:https://philoscience.iteye.com/blog/1537004 (仅个人学习,侵删)

Exact-Cover问题:

问题描述:给定一个0/1矩阵(一般是稀疏矩阵),选出一些行,使得由这些行构成的新矩阵中每一列有且仅有一个1。换个说法,把列看成域(universe)中的元素,把行看成域里的一个子集,exact-cover问题就是找出一些不相交的子集,它们的并集覆盖整个域。

问题举例:
有如下0/1矩阵:

在这里插入图片描述

矩阵的行(1,4,5)构成的新矩阵中每一行每一列都有且仅有一个1.

解决方案:
我们知道,当我们选择某一行时,其他与该行在某列上同时有一个1的行都将不能再入选。也就是说,这些行在下一次遴选前都将被删除,而相应的列也可以被删除,因为这些列已经有了1了。也就是说,我们可以通过删除矩阵的行、列来缩小问题的规模,直到最终矩阵为空,说明我们已经找到了一个合法的解。假如矩阵出现没有1的列,说明这个问题无解,因为它说明这个列没有办法得到一个1.下面是Donald总结的算法流程:

伪代码 :

Exact_Cover(A):  
    if A is empty:  
        problem solve, return  
    else pick a column c:  
        choose a row, r, such that A[r,c]=1  
        include r in the partial solution  
        for each j such that A[r,j]=1:  
            delete column j from matrix A  
            for each i such that A[i,j]=1:  
                delete row i from matrix A  
        repeat this algorithm recursively on the reduced matrix A  

上面的描述便是前面提到的algorithm X。它其实是一个深搜问题。在行4,算法选择一个非确定性的(nondeterministic)行r,这将形成搜索树上的不同分枝,例如上面的例子我们确定性地选择列1做为我们的初始入口,选择行2或选择行4将形成搜索树的两个分支,我们首先沿着行2这个分枝搜索下去,如果找不到解,将一步一步回溯,最后返回root,然后再从行4这个分枝搜索。在这个过程中,算法将花费很多时间在矩阵的操作(删除行/列)上。

Donald在文章的开篇就提出了一件很是让人感到aha~!的事情:
假设x指向双向链表的一个元素,x.left指向x的左元素,x.right指向x的右元素,当我们决定删除x时,可以做如下操作:

x.left.right = x.right  
x.right.left = x.left  

而下面这两句精练的对称的语句将重新将x放回链表上:

x.left.right = x  
x.right.left = x  

是不是很美妙呢?而这,便是dancing link的精华了。

让我们换个数据结构来表现一个稀疏的0/1矩阵,形象一点,就是下图这样的表示:

在这里插入图片描述

每个矩阵元素,我们称之为data object,有四个指针,分别指向其上、下、左、右元素,矩阵的每一行、每一列都是一个双向循环链表,每一列还连带一个list header,我们称之为column object,以方便我们进行列的删除操作。column object同样也是双向链表中的一部分。每个data object除了那四个指针外,还带有一个column object的引用,以方便对该列进行删除。而column object则还另外包括一个size元素,说明该列总共有多少个元素。还记得我们前面提到的算法不?我们每次选一个列c,该c拥有的每一个行都将成为一个分支。实际上我们做的事情是在众多未知数中先假设某个未知数,从而缩小搜索的空间,再在未知数中做新的假设,,直到最后所有假设都符合题目限制于是成功获得结果或者某个限制被打破从而搜索失败必须回溯。该列有多少个元素将决定了搜索树在该子根下将有多少个分支,那我们当然希望越在上层的分枝越少就越好,(因为随着树的深入,可能很多未知量都变成已知的),因此就有了这个size,以方便我们进行列的选择。由column object组成的双向横向链表还包含一个root,用来标志矩阵是否为空,当root.right==root时。上面的算法通过改用这个数据结构,可以重新描述如下:

伪代码:

search(k):  
    if root.right == root:  
        solution found, return true or print it~  
    { as we've mentioned before, choose one column that has least elements}  
    choose a column c   
    cover column c  {remember c is a column object}  
    for each r <- c.down, c.down.down, ..., while r!=c  
        solution[k]=r  
        for each j <- r.right, r.right.right, ..., while j!=r  
            cover column j  
        if search(k+1):  
            return true  
        {doing clean up jobs}  
        for each j <- r.left, r.left.left, ..., while j!=r  
            uncover column j      
    {if we get here, no solution is found}  
    uncover column c  
    return false  

cover跟uncover的操作如下:
伪代码:

cover( column object c ):  
    c.left.right = c.right  
    c.right.left = c.left  
    for each i <- c.down, c.down.down, ..., while i!=c:  
        for each j <- i.right, i.right.right, ..., while j!=i:  
            j.down.up = j.up  
            j.up.down = j.down  
            j.column.size--  
  
uncover( column object c ):  
    for each i <- c.up, c.up.up, ..., while i!=c:  
        for each j <- i.left, i.left.left, ..., while j!=i:  
            j.down.up = j  
            j.up.down = j  
            j.column.size++  
    c.left.right = c  
    c.right.left = c  

是不是相当简洁呢?对于上面的那个例子,下图是cover了column A后的矩阵:

在这里插入图片描述

当我们选择了行2后,将cover column D跟G,下面是cover了D跟G后的矩阵:

在这里插入图片描述

Dancing link使矩阵的行/列删除变得相当快捷,有如弹弹手指般容易。实现起来也相当地容易。不过由于我对python还是很不熟,而当时又感觉自己没有弄懂dancing link的样子,于是我只好从自己比较熟悉的c++开始编码了,于是就有了下面两个版本的dlx:
Cpp代码

//ExtractCoverMatrix.h  
#include<iostream>  
using namespace std;  
  
struct Node  
{  
    Node* left, *right, *up, *down;  
    int col, row;  
    Node(){  
        left = NULL; right = NULL;  
        up = NULL; down = NULL;  
        col = 0; row = 0;  
    }  
    Node( int r, int c )  
    {  
        left = NULL; right = NULL;  
        up = NULL; down  = NULL;  
        col = c; row = r;  
    }  
};  
  
struct ColunmHeader : public Node  
{  
    int size;  
    ColunmHeader(){  
        size = 0;  
    }  
};  
  
class ExactCoverMatrix  
{  
    public:  
        int ROWS, COLS;  
        //这是存储结果的数组  
        int* disjointSubet;  
        //接收矩阵其及维度  
        ExactCoverMatrix( int rows, int cols, int** matrix );  
        //释放内存  
        ~ExactCoverMatrix();  
        //在行r列c的位置上插入一个元素  
        void insert( int r, int c );  
        //即我们的cover和uncover操作了  
        void cover( int c );  
        void uncover( int c );  
        //即我们的algorithm X的实现了  
        int search( int k=0 );  
    private:  
        ColunmHeader* root;  
        ColunmHeader* ColIndex;  
        Node* RowIndex;  
};  
  
  
//ExactCoverMatrix.cpp  
#include "ExtracCoverMatrix.h"  
  
ExtracCoverMatrix::ExtracCoverMatrix( int rows, int cols, int** matrix )  
{  
    ROWS = rows;  
    COLS = cols;  
    disjointSubet = new int[rows+1];  
    ColIndex = new ColunmHeader[cols+1];  
    RowIndex = new Node[rows];  
    root = &ColIndex[0];  
    ColIndex[0].left = &ColIndex[COLS];  
    ColIndex[0].right = &ColIndex[1];  
    ColIndex[COLS].right = &ColIndex[0];  
    ColIndex[COLS].left = &ColIndex[COLS-1];  
    for( int i=1; i<cols; i++ )  
    {  
        ColIndex[i].left = &ColIndex[i-1];  
        ColIndex[i].right = &ColIndex[i+1];  
    }  
  
    for ( int i=0; i<=cols; i++ )  
    {  
        ColIndex[i].up = &ColIndex[i];  
        ColIndex[i].down = &ColIndex[i];  
        ColIndex[i].col = i;  
    }  
    ColIndex[0].down = &RowIndex[0];  
      
    for( int i=0; i<rows; i++ )  
        for( int j=0; j<cols&&matrix[i][j]>0; j++ )  
        {  
            insert(  i, matrix[i][j] );  
        }  
}  
  
ExtracCoverMatrix::~ExtracCoverMatrix()  
{  
    delete[] disjointSubet;  
    for( int i=1; i<=COLS; i++ )  
    {  
        Node* cur = ColIndex[i].down;  
        Node* del = cur->down;  
        while( cur != &ColIndex[i] )  
        {  
            delete cur;  
            cur = del;  
            del = cur->down;  
        }  
    }  
    delete[] RowIndex;  
    delete[] ColIndex;  
}  
  
void ExtracCoverMatrix::insert( int r, int c )  
{  
    Node* cur = &ColIndex[c];  
    ColIndex[c].size++;  
    Node* newNode = new Node( r, c );  
    while( cur->down != &ColIndex[c] && cur->down->row < r )  
        cur = cur->down;  
    newNode->down = cur->down;  
    newNode->up = cur;  
    cur->down->up = newNode;  
    cur->down = newNode;  
    if( RowIndex[r].right == NULL )  
    {  
        RowIndex[r].right = newNode;  
        newNode->left = newNode;  
        newNode->right = newNode;  
    }  
    else  
    {  
        Node* rowHead = RowIndex[r].right;  
        cur = rowHead;  
        while( cur->right != rowHead && cur->right->col < c )  
            cur = cur->right;  
        newNode->right = cur->right;  
        newNode->left = cur;  
        cur->right->left = newNode;  
        cur->right = newNode;  
    }  
}  
  
void ExtracCoverMatrix::cover( int c )  
{  
    ColunmHeader* col = &ColIndex[c];  
    col->right->left = col->left;  
    col->left->right = col->right;  
    Node* curR, *curC;  
    curC = col->down;  
    while( curC != col )  
    {  
        Node* noteR = curC;  
        curR = noteR->right;  
        while( curR != noteR )  
        {  
            curR->down->up = curR->up;  
            curR->up->down = curR->down;  
            ColIndex[curR->col].size--;  
            curR = curR->right;  
        }  
        curC = curC->down;  
    }  
}  
  
void ExtracCoverMatrix::uncover( int c )  
{  
    Node* curR, *curC;  
    ColunmHeader* col = &ColIndex[c];  
    curC = col->up;  
    while( curC != col )  
    {  
        Node* noteR = curC;  
        curR = curC->left;  
        while( curR != noteR )  
        {  
            ColIndex[curR->col].size++;  
            curR->down->up = curR;  
            curR->up->down = curR;  
            curR = curR->left;  
        }  
        curC = curC->up;  
    }  
    col->right->left = col;  
    col->left->right = col;  
}  
  
int ExtracCoverMatrix::search( int k )  
{  
    if( root->right == root )  
        return k;  
    ColunmHeader* choose = (ColunmHeader*)root->right, *cur=choose;  
    while( cur != root )  
    {  
        if( choose->size > cur->size )  
            choose = cur;  
        cur = (ColunmHeader*)cur->right;  
    }  
    if( choose->size <= 0 )  
        return -1;  
          
    cover( choose->col );  
    Node* curC = choose->down;  
    while( curC != choose )  
    {  
        disjointSubet[k] = curC->row;  
        Node* noteR = curC;  
        Node* curR = curC->right;  
        while( curR != noteR )  
        {  
            cover( curR->col );  
            curR = curR->right;  
        }  
        int res=-1;  
        if( (res = search( k+1 ))>0 )  
            return res;  
        curR = noteR->left;  
        while( curR != noteR )  
        {  
            uncover( curR->col );  
            curR = curR->left;  
        }  
        curC = curC->down;  
    }  
    uncover( choose->col );  
    return -1;  
}  
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值