在“精确覆盖问题和算法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)的舞蹈链接