En el artículo Problema de Cobertura Exacta y Algoritmo X | En el Conjunto 1 discutimos el problema de Cobertura Exacta y el Algoritmo X para resolver el problema de Cobertura Exacta. En este artículo, discutiremos los detalles de implementación del Algoritmo X usando la Técnica de Enlaces Danzantes (DLX) propuesta por el Dr. Donald E. Knuth en su artículo » Enlaces Danzantes «.
Técnica de enlace de baile
La técnica de enlace de baile se basa en la idea de una lista enlazada doblemente circular . Como se discutió en el artículo anterior , transformamos el problema de la cobertura exacta en forma de array de 0 y 1. Aquí, cada «1» en la array está representado por un Node de lista enlazada y toda la array se transforma en una malla de 4 Nodes conectados. . Cada Node contiene los siguientes campos:
- Puntero al Node que le queda
- Puntero al Node derecho a él
- Puntero al Node por encima de él
- Puntero al Node debajo de él
- Puntero para enumerar el Node de encabezado al que pertenece
Cada fila de la array es, por lo tanto, una lista enlazada circular vinculada entre sí con punteros izquierdo y derecho y cada columna de la array también será una lista enlazada circular vinculada a cada una de las anteriores con puntero arriba y abajo. Cada lista de columnas también incluye un Node especial llamado «Node de encabezado de lista». Este Node de encabezado es como un Node simple pero tiene pocos campos adicionales:
- identificación de la columna
- Recuento de Nodes en la columna actual
Podemos tener dos tipos diferentes de Nodes, pero en nuestra implementación, crearemos solo un tipo de Node con todos los campos para mayor comodidad con un campo adicional de «ID de fila» que indicará a qué fila pertenece este Node.
Entonces, para la array:
la array vinculada de 4 vías se verá así:
Entonces, el pseudocódigo para el algoritmo de búsqueda (Algoritmo X) será:
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 ); }
Node de cobertura
Como se discutió en el algoritmo, tenemos que eliminar columnas y todas las filas a las que pertenecen los Nodes de esa columna. Este proceso se denomina aquí cobertura del Node.
Para eliminar una columna, simplemente podemos desvincular el encabezado de esa columna de los encabezados vecinos. De esta forma no se puede acceder a esta columna. Este proceso es similar a la eliminación del Node de la lista doblemente enlazada, supongamos que queremos eliminar el Node x entonces:
x.left.right = x.right x.right.left = x.left
De manera similar, para eliminar una fila, debemos desvincular todos los Nodes de la fila de los Nodes de la fila superior e inferior.
x.up.down = x.down x.down.up = x.up
Por lo tanto, el pseudocódigo para la cubierta (Node) se convierte en:
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; } }
Entonces, por ejemplo, después de cubrir la columna A, la array se verá así:
Aquí primero eliminamos la columna de otras columnas, luego nos movemos hacia abajo a cada Node de columna y eliminamos la fila atravesando a la derecha, por lo que se eliminan las filas 2 y 4.
Descubrimiento de Node
Supongamos que el algoritmo llegó a un callejón sin salida y no es posible una solución en ese caso, el algoritmo tiene que retroceder. Debido a que hemos eliminado columnas y filas cuando retrocedemos, hemos vuelto a vincular esas filas y columnas eliminadas. Esto es lo que llamamos destape. Tenga en cuenta que los Nodes eliminados aún tienen punteros a sus vecinos, por lo que podemos vincularlos nuevamente usando estos punteros.
Para descubrir la columna, realizaremos la operación de cobertura pero en orden inverso:
x.left.right = x x.right.left = x
De manera similar, para descubrir cualquier Node de fila x –
x.up.down = x x.down.up = x
Por lo tanto, el pseudocódigo para descubrir (Node) se convertirá en:
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; }
A continuación se muestra la implementación de la técnica de enlace de baile:
// C++ program for solving exact cover problem // using DLX (Dancing Links) technique #include <bits/stdc++.h> #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 <struct Node*> 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<struct Node*>::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<nCol; j++) { // if it's row 0, it consist of column // headers. Initialize it with 1 if(i == 0) ProbMat[i][j] = true; else ProbMat[i][j] = false; } } // Manually filling up 1's ProbMat[1][0] = true; ProbMat[1][3] = true; ProbMat[1][6] = true; ProbMat[2][0] = true; ProbMat[2][3] = true; ProbMat[3][3] = true; ProbMat[3][4] = true; ProbMat[3][6] = true; ProbMat[4][2] = true; ProbMat[4][4] = true; ProbMat[4][5] = true; ProbMat[5][1] = true; ProbMat[5][2] = true; ProbMat[5][5] = true; ProbMat[5][6] = true; ProbMat[6][1] = true; ProbMat[6][6] = true; ProbMat[7][0] = true; ProbMat[7][3] = true; // create 4-way linked matrix createToridolMatrix(); search(0); return 0; }
Producción:
Printing Solutions: 6 4 2 Printing Solutions: 6 4 7
Referencias
- https://www.ocf.berkeley.edu/%7Ejchu/publicportal/sudoku/sudoku.paper.html
- Enlaces de baile por Donald Knuth
Este artículo es una contribución de Atul Kumar . Si le gusta GeeksforGeeks y le gustaría contribuir, también puede escribir un artículo usando contribuya.geeksforgeeks.org o envíe su artículo por correo a contribuya@geeksforgeeks.org. Vea su artículo que aparece en la página principal de GeeksforGeeks y ayude a otros Geeks.
Escriba comentarios si encuentra algo incorrecto o si desea compartir más información sobre el tema tratado anteriormente.
Publicación traducida automáticamente
Artículo escrito por GeeksforGeeks-1 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA