Knuth's Algorithm X (Dancing Links; Exact Cover)
Show older comments
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 Comments
Thomas Patterson
on 4 Feb 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
on 4 Feb 2018
@Thomas, you should put your solution as an answer.
Accepted Answer
More Answers (1)
Thomas Patterson
on 6 Feb 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.
Categories
Find more on Programming in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!