📜  精确覆盖问题和算法X |设置2(用DLX实施)

📅  最后修改于: 2021-05-04 17:59:00             🧑  作者: Mango

在“精确覆盖问题和算法X”中第一组,我们讨论了精确覆盖问题和算法X以解决精确覆盖问题。在本文中,我们将讨论Donald E. Knuth博士在其论文“ Dancing Links”中提出的使用Dancing Links技术(DLX)的算法X的实现细节。

跳舞链接技术

跳舞链接技术依赖于双重循环链接列表的思想。如前一篇文章所述,我们将精确覆盖问题转换为0和1矩阵的形式。这里,矩阵中的每个“ 1”由链表的一个节点表示,整个矩阵被转换为一个4路连接的节点的网格。每个节点包含以下字段–

  • 指向左侧节点的指针
  • 指向它右边的节点的指针
  • 指向其上方节点的指针
  • 指向它下面的节点的指针
  • 指向它所属的列表头节点的指针

因此,矩阵的每一行都是用左,右指针相互链接的圆形链接列表,矩阵的每一列也将是通过上下指针链接到上方的环形链接列表。每个列列表还包括一个称为“列表头节点”的特殊节点。该标头节点就像简单节点一样,但是几乎没有额外的字段–

  • 列编号
  • 当前列中的节点数

我们可以有两种不同类型的节点,但是在我们的实现中,为了方便起见,我们将仅创建一种具有所有字段的节点,并添加一个额外的“行ID”字段,该字段将告诉该节点属于哪一行。
因此对于矩阵–
矩阵
4路链接矩阵将如下所示–

四向链接矩阵

四向链接矩阵

因此,搜索算法(算法X)的伪代码将为–

f( h.right == h ) { 
     printSolutions(); 
     return; 
} 
else { 
     ColumnNode column = getMinColumn(); 
     cover(column); 

     for( Node row = column.down ; rowNode != column ;
        rowNode = rowNode.down ) { 
            solutions.add( rowNode ); 

            for( Node rightNode = row.right ; rightNode != row ;
                 rightNode = rightNode.right ) 
                    cover( rightNode ); 

     Search( k+1); 
     solutions.remove( rowNode ); 
     column = rowNode.column; 

     for( Node leftNode = rowNode.left ; leftNode != row ;
              leftNode = leftNode.left )                                                                             
            uncover( leftNode ); 
     } 
     uncover( column ); 
} 

覆盖节点

如算法中所讨论的,我们必须删除列以及该列的节点所属的所有行。此过程在此称为节点覆盖。
要删除一列,我们可以简单地将该列的标题与相邻的标题断开链接。这样就无法访问此列。此过程类似于从双向链表中删除节点,假设我们要删除节点x,则–

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

类似地,要删除一行,我们必须取消该行的所有节点与其上方和下方的行的链接。

x.up.down = x.down
x.down.up = x.up

因此,cover(node)的伪代码变为–

Node column = dataNode.column; 

column.right.left = column.left; 
column.left.right = column.right; 

for( Node row = column.down ; row != column ; row = row.down ) 
    for( Node rightNode = row.right ; rightNode != row ; 
         rightNode = rightNode.right ) { 
         rightNode.up.down = rightNode.down; 
         rightNode.down.up = rightNode.up; 
    } 
} 

因此,例如,在覆盖A列之后,矩阵将如下所示–

覆盖

覆盖

在这里,我们首先从其他列中删除该列,然后向下移动到每个列节点,并通过向右移动来删除行,因此删除了第2行和第4行。

发现节点

假设算法已走到尽头,在这种情况下,算法必须回溯,因此无法解决。因为我们在回溯时已删除了列和行,所以我们再次链接了那些删除的行和列。这就是我们所说的发现。注意,被删除的节点仍然具有指向其邻居的指针,因此我们可以使用这些指针将它们重新链接回去。
要显示列,我们将执行覆盖操作,但顺序相反–

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

类似于发现任何行节点x –

x.up.down = x
x.down.up = x

因此,uncover(node)的伪代码将变为–

Node column = dataNode.column; 

for( Node row = column.up ; row != column ; row = row.up ) 
    for( Node leftNode = row.left ; leftNode != row ;
         leftNode = leftNode.right ) { 
         leftNode.up.down = leftNode; 
         leftNode.down.up = leftNode; 
     } 
column.right.left = column; 
column.left.right = column; 
} 

以下是跳舞链接技术的实现–

// C++ program for solving exact cover problem
// using DLX (Dancing Links) technique
  
#include 
  
#define MAX_ROW 100
#define MAX_COL 100
  
using namespace std;
  
struct Node
{
public:
    struct Node *left;
    struct Node *right;
    struct Node *up;
    struct Node *down;
    struct Node *column;
    int rowID;
    int colID;
    int nodeCount;
};
  
// Header node, contains pointer to the
// list header node of first column
struct Node *header = new Node();
  
// Matrix to contain nodes of linked mesh
struct Node Matrix[MAX_ROW][MAX_COL];
  
// Problem Matrix
bool ProbMat[MAX_ROW][MAX_COL];
  
// vector containing solutions
vector  solutions;
  
// Number of rows and columns in problem matrix 
int nRow = 0,nCol = 0;
  
  
// Functions to get next index in any direction
// for given index (circular in nature) 
int getRight(int i){return (i+1) % nCol; }
int getLeft(int i){return (i-1 < 0) ? nCol-1 : i-1 ; }
int getUp(int i){return (i-1 < 0) ? nRow : i-1 ; }  
int getDown(int i){return (i+1) % (nRow+1); }
  
// Create 4 way linked matrix of nodes
// called Toroidal due to resemblance to
// toroid
Node *createToridolMatrix()
{
    // One extra row for list header nodes
    // for each column
    for(int i = 0; i <= nRow; i++)
    {
        for(int j = 0; j < nCol; j++)
        {
            // If it's 1 in the problem matrix then 
            // only create a node 
            if(ProbMat[i][j])
            {
                int a, b;
  
                // If it's 1, other than 1 in 0th row
                // then count it as node of column 
                // and increment node count in column header
                if(i) Matrix[0][j].nodeCount += 1;
  
                // Add pointer to column header for this 
                // column node
                Matrix[i][j].column = &Matrix[0][j];
  
                // set row and column id of this node
                Matrix[i][j].rowID = i;
                Matrix[i][j].colID = j;
  
                // Link the node with neighbors
  
                // Left pointer
                a = i; b = j;
                do{ b = getLeft(b); } while(!ProbMat[a][b] && b != j);
                Matrix[i][j].left = &Matrix[i][b];
  
                // Right pointer
                a = i; b = j;
                do { b = getRight(b); } while(!ProbMat[a][b] && b != j);
                Matrix[i][j].right = &Matrix[i][b];
  
                // Up pointer
                a = i; b = j;
                do { a = getUp(a); } while(!ProbMat[a][b] && a != i);
                Matrix[i][j].up = &Matrix[a][j];
  
                // Down pointer
                a = i; b = j;
                do { a = getDown(a); } while(!ProbMat[a][b] && a != i);
                Matrix[i][j].down = &Matrix[a][j];
            }
        }
    }
  
    // link header right pointer to column 
    // header of first column 
    header->right = &Matrix[0][0];
  
    // link header left pointer to column 
    // header of last column 
    header->left = &Matrix[0][nCol-1];
  
    Matrix[0][0].left = header;
    Matrix[0][nCol-1].right = header;
    return header;
}
  
// Cover the given node completely
void cover(struct Node *targetNode)
{
    struct Node *row, *rightNode;
  
    // get the pointer to the header of column
    // to which this node belong 
    struct Node *colNode = targetNode->column;
  
    // unlink column header from it's neighbors
    colNode->left->right = colNode->right;
    colNode->right->left = colNode->left;
  
    // Move down the column and remove each row
    // by traversing right
    for(row = colNode->down; row != colNode; row = row->down)
    {
        for(rightNode = row->right; rightNode != row;
            rightNode = rightNode->right)
        {
            rightNode->up->down = rightNode->down;
            rightNode->down->up = rightNode->up;
  
            // after unlinking row node, decrement the
            // node count in column header
            Matrix[0][rightNode->colID].nodeCount -= 1;
        }
    }
}
  
// Uncover the given node completely
void uncover(struct Node *targetNode)
{
    struct Node *rowNode, *leftNode;
  
    // get the pointer to the header of column
    // to which this node belong 
    struct Node *colNode = targetNode->column;
  
    // Move down the column and link back
    // each row by traversing left
    for(rowNode = colNode->up; rowNode != colNode; rowNode = rowNode->up)
    {
        for(leftNode = rowNode->left; leftNode != rowNode;
            leftNode = leftNode->left)
        {
            leftNode->up->down = leftNode;
            leftNode->down->up = leftNode;
  
            // after linking row node, increment the
            // node count in column header
            Matrix[0][leftNode->colID].nodeCount += 1;
        }
    }
  
    // link the column header from it's neighbors
    colNode->left->right = colNode;
    colNode->right->left = colNode;
}
  
// Traverse column headers right and 
// return the column having minimum 
// node count
Node *getMinColumn()
{
    struct Node *h = header;
    struct Node *min_col = h->right;
    h = h->right->right;
    do
    {
        if(h->nodeCount < min_col->nodeCount)
        {
            min_col = h;
        }
        h = h->right;
    }while(h != header);
  
    return min_col;
}
  
  
void printSolutions()
{
    cout<<"Printing Solutions: ";
    vector::iterator i;
  
    for(i = solutions.begin(); i!=solutions.end(); i++)
        cout<<(*i)->rowID<<" ";
    cout<<"\n";
}
  
// Search for exact covers
void search(int k)
{
    struct Node *rowNode;
    struct Node *rightNode;
    struct Node *leftNode;
    struct Node *column;
  
    // if no column left, then we must
    // have found the solution
    if(header->right == header)
    {
        printSolutions();
        return;
    }
  
    // choose column deterministically
    column = getMinColumn();
  
    // cover chosen column
    cover(column);
  
    for(rowNode = column->down; rowNode != column; 
        rowNode = rowNode->down )
    {
        solutions.push_back(rowNode);
  
        for(rightNode = rowNode->right; rightNode != rowNode;
            rightNode = rightNode->right)
            cover(rightNode);
  
        // move to level k+1 (recursively)
        search(k+1);
  
        // if solution in not possible, backtrack (uncover)
        // and remove the selected row (set) from solution
        solutions.pop_back();
  
        column = rowNode->column;
        for(leftNode = rowNode->left; leftNode != rowNode;
            leftNode = leftNode->left)
            uncover(leftNode);
    }
  
    uncover(column);
}
  
// Driver code
int main()
{    
    /*
     Example problem
  
     X = {1,2,3,4,5,6,7}
     set-1 = {1,4,7}
     set-2 = {1,4}
     set-3 = {4,5,7}
     set-4 = {3,5,6}
     set-5 = {2,3,6,7}
     set-6 = {2,7}
     set-7 = {1,4}
  
     Solutions : {6 ,4, 2} and {6, 4, 7}
    */
  
    nRow = 7;
    nCol = 7;
      
    // initialize the problem matrix 
    // ( matrix of 0 and 1) with 0
    for(int i=0; i<=nRow; i++)
    {
        for(int j=0; j

输出:

Printing Solutions: 6 4 2 
Printing Solutions: 6 4 7

参考

  • https://www.ocf.berkeley.edu/%7Ejchu/publicportal/sudoku/sudoku.paper.html
  • 唐纳德·克努斯(Donald Knuth)的舞蹈链接