Knuth's Algorithm X (Dancing Links; Exact Cover)
Mostrar comentarios más antiguos
I'm trying to write a Matlab version of Knuth's "Algorithm X." This algorithm makes use of the Dancing Links routine and is a recursive, nondeterministic, depth-first, backtracking algorithm. I am using this algorithm to solve a simple application of the exact cover problem. For more information:
Here is pseudocode for Algorithm X:
1. If matrix A is empty, the problem is solved; terminate successfully.
2. Otherwise: choose a column, c, of A with the least number of 1's.
3. Choose a row, r, that contains a 1 in column c (e.g., A(r,c) = 1).
4. Add r to the partial solution.
5. For each column j such that A(r,j) = 1:
for each row i such that A(i,j) = 1:
delete row i from matrix A
delete column j from matrix A
6. Repeat this algorithm recursively on the reduced matrix A.
Following the Wiki-page, here is a working example:
A = zeros(1,7); B = A; C = B; D = C; E = D; F = E;
A([1 4 7]) = 1; B([1 4]) = 1; C([4 5 7]) = 1; D([3 5 6]) = 1; E([2 3 6 7]) = 1; F([2 7]) = 1;
M = [A; B; C; D; E; F]; % the binary 'A' matrix
Knuth's algorithm should return the following exact cover of M: {B, D, F}; i.e., rows 2, 4, and 6 of the original (unreduced) matrix collectively contribute a 1 in each column of M (exactly once).
Here is what I have so far:
function sol = dlx(X,sol)
% if isempty(X)
% return
% end
% Find the first column of X having the lowest number of 1s in any position
c = find(sum(X,1) == min(sum(X,1)), 1);
% If column c is entirely zero, terminate unsuccessfully
% if (sum(X(:,c)) == 0)
% sol = [];
% return
% end
% Find the rows that have a 1 in column c
r = find(X(:,c) == 1);
% if isempty(r)
% sol = [];
% return
% end
% if isempty(r)
% sol = sol(1:end-1);
% return
% end
% Loop over each row that has a 1 in column c
for rr = 1:length(r)
% include row r in the partial solution
sol = [sol r(rr)];
% find the columns in which A(r,j) = 1
J = find(X(r(rr),:) == 1);
% initialize the reduced matrix
Xred = X;
I = [];
for jj = 1:length(J)
I = [I; find(Xred(:,J(jj)) == 1)];
end
I = unique(I);
Xred(I,:) = []; % delete the I rows
Xred(:,J) = []; % delete the J columns
% repeat this algorithm recursively on the reduced matrix Xred.
sol = dlx(Xred, sol);
end
end
For the above example,
sol = dlx(X, [])
sol =
1 2 1 1
Except that my algorithm should return sol = [2 4 6].
Here is another simple example that also bugs my code:
A = [1 1 0; 0 1 1];
sol = dlx(A, [])
sol =
1
In this case, the there is no exact cover of the matrix A, and so my algorithm should return an empty solution (e.g., sol = []).
As you can see, I am very close. I believe that I have successfully written the recursion of reducing the matrix, but I am seeking help on the following:
- Looking at the pseudocode, should the "Choose a row, r, that contains a 1 in column c" be implemented as a for-loop in my code? That is, should I loop over all such rows?
- I'm having trouble formulating the final solution from the partial solution (e.g., so that my function returns sol = [2 4 6]). During the recursion, the partial solution should 'backtrack' when the partial solution turns out not to be valid.
- Where should I place the 'if A is empty' condition from the pseudocode? You'll see that I've tried putting this condition at the beginning of the function, and also at the 'isempty(r)' line.
- I'm also open to suggestions for making my code more efficient; i.e., should/can I replace the 'find' or 'for-loops' with better code?
Can any one offer me suggestions to my questions? Thanks in advance!
3 comentarios
Thomas Patterson
el 4 de Feb. de 2018
The main problem with your code seems to be that when rows/columns are deleted to form the reduced matrix Xred the row/column indexing is changed. Thus if columns 3 and 4 are deleted subsequent reference to the remaining columns will be numbered 1,2,3,... and connection with the original columns is lost. It is also very memory zapping to have potentially a large matrix continually "saved" in the recursions. Instead one needs to maintain an index of the rows and columns. Thus if the row index is R=[2 4 7 9] then referring to row 2 actually corresponds to row R(2)=4 of the original matrix. The indices rather than the matrix elements are deleted. This preserves the sequencing and also saves a lot of memory. Additionally the partial solutions have to be properly managed. A "flag" needs to be included in the output to signal success or failure with the latest partial being removed after a failure.
I have amended your code to take account of these comments and that is appended. I haven't tested it exhaustively but it works on any examples I have tried.
function [sol, OK] = newdlx(X,sol,RR,CC)
% Solution of covering matrix
% X is input matrix
% sol is solution, initially [], (row vector)
% RR is row selection index, initially all rows of X (row vector)
% CC is column selection index, initially all columns of X (row vector)
% OK is flag, true if successful, false if not (logical)
OK=true
% Check for successful result
if isempty(RR) & isempty(CC), return; end
% Find the first column of X having the lowest number of 1s in any position
c = find(sum(X(RR,CC),1) == min(sum(X(RR,CC),1)), 1);
% Check for unsuccessful termination
f1=sum(X(RR,CC(c))) == 0;
f2=isempty(RR) & ~isempty(CC);
f3=isempty(CC) & ~isempty(RR);
if f1 | f2 | f3
OK=false;
return
end
% Find the rows that have a 1 in column c
r = find(X(RR,CC(c)) == 1);
% Loop over each row that has a 1 in column c
for rr=r'
% Include row r in the partial solution
sol = [sol RR(rr)];
% Find the columns in which A(r,j) = 1
J = find(X(RR(rr),CC) == 1);
rows=[];
for iii=J
rows=[rows find(X(RR,CC(iii)) == 1)'];
end
rows=unique(rows); % Remove duplicates
% Remove appropriate rows and columns
RR1=RR;CC1=CC; RR1(rows)=[]; CC1(J)=[];
% Repeat the algorithm recursively
[sol,OK ] = newdlx(X, sol, RR1,CC1);
% Remove last "guess" if unsuccessful
if ~OK, sol=sol(1:end-1); end
end
sol=sort(sol)
end
Guillaume
el 4 de Feb. de 2018
@Thomas, you should put your solution as an answer.
Respuesta aceptada
Más respuestas (1)
Thomas Patterson
el 6 de Feb. de 2018
Please note that the statement near the end of my newdlx code
sol=sort(sol)
should be deleted. It interferes with the proper updating of sol when a failure occurs.
Categorías
Más información sobre Programming en Centro de ayuda y File Exchange.
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!