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

原文鏈接:https://philoscience.iteye.com/blog/1537004

我覺得這個博客講的很不錯,就轉載過來啦。
理解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;  
}  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章