问题
How to generate random matrix without repetition in rows and cols with specific range
example (3x3): range 1 to 3
2 1 3
3 2 1
1 3 2
example (4x4): range 1 to 4
4 1 3 2
1 3 2 4
3 2 4 1
2 4 1 3
回答1:
This algorithm will do the trick, assuming you want to contain all elements between 1
and n
%// Elements to be contained, but no zero allowed
a = [1 2 3 4];
%// all possible permutations and its size
n = numel(a);
%// initialization
output = zeros(1,n);
ii = 1;
while ii <= n;
%// random permuation of input vector
b = a(randperm(n));
%// concatenate with already found values
temp = [output; b];
%// check if the row chosen in this iteration already exists
if ~any( arrayfun(@(x) numel(unique(temp(:,x))) < ii+1, 1:n) )
%// if not, append
output = temp;
%// increase counter
ii = ii+1;
end
end
output = output(2:end,:) %// delete first row with zeros
It definitely won't be the fastest implementation. I would be curios to see others. The computation time increases exponentially. But everything up to 7x7 is bearable.
回答2:
A way of approaching this problem is to generate a circular matrix and shuffle it.
mat_size = 4
A = gallery('circul', 1:mat_size); % circular matrix
B = A( randperm(length(A)) , randperm(length(A)) ); % shuffle rows and columns with randperm
It gives
A =
1 2 3 4
4 1 2 3
3 4 1 2
2 3 4 1
B =
3 4 1 2
2 3 4 1
4 1 2 3
1 2 3 4
This method should be fast. An 11 size problem is computed in 0.047021 seconds.
回答3:
I wrote another code (interesting to compare timings and, if possible, to make it parallel). Also had problem with perms
(needed to restart Matlab to be able to generate for 11 elements, I have x64 and 16GB of memory). Than I decided to keep characters instead of the numbers, reducing the memory occupied by the matrix. It, of course, generates all permutations, and I shuffle them in the beginning, selecting in the loop in a new random order. It runs faster this way and 'eats' less memory. Time for 11 x 11 (of course it differs from run to run) is shown in results.
clear all;
t = cputime;
sze = 11;
variations = perms(char(1 : sze)); % permutations
varN = length(variations);
variations = variations(randperm(varN)', :); % shuffle
sudoku = zeros(sze, sze);
sudoku(1, :) = variations(1, :); % set the first row
indx = 2;
for ii = 2 : varN
% take a random index
rowVal = variations(ii, :);
% check that row numbers do not present in table at
% corresponding columns
if (~isempty(find(repmat(rowVal, sze, 1) - sudoku == 0, 1)))
continue;
end;
sudoku(indx, :) = rowVal;
disp(['Found row ' num2str(indx)]);
indx = indx + 1;
if indx > sze, break; end;
end;
disp(cputime - t);
disp(sudoku);
Result
252.9712 seconds
7 11 3 9 6 2 4 1 8 10 5
1 9 6 3 10 7 11 5 2 4 8
9 6 11 8 2 10 1 7 4 5 3
4 10 7 11 1 8 5 2 6 3 9
2 5 9 1 3 6 8 4 10 7 11
10 3 5 6 7 4 2 9 11 8 1
6 4 2 10 8 5 3 11 9 1 7
3 8 10 4 11 1 7 6 5 9 2
11 1 8 5 4 9 6 3 7 2 10
5 2 4 7 9 3 10 8 1 11 6
8 7 1 2 5 11 9 10 3 6 4
回答4:
Here's a memory-efficient approach. The time it takes is random, but not very large. All possible output matrices are equally likely.
This works by randomly filling the matrix until no more positions are available or until the whole matrix has been filled. The code is commented so it should be obvious how it works.
For size 11 this takes of the order of a few thousands or tens of thousands attempts. On my old laptop that means a (random) running time from a few seconds to tens of seconds.
It could perhaps be sped up using uint8
values instead of double
. I don't think that brings a large gain, though.
The code:
clear all
n = 11; %// matrix size
[ ii jj ] = ndgrid(1:n); %// rows and columns of S
ii = ii(:);
jj = jj(:);
success = 0; %// ...for now
attempt = 0; %// attempt count (not really needed)
while ~success
attempt = attempt + 1;
S = NaN(n, n); %// initiallize result. NaN means position not filled yet
t = 1; %// number t is being placed within S ...
u = 1; %// ... for the u-th time
mask = true(1, numel(ii)); %// initiallize mask of available positions
while any(mask) %// while there are available positions
available = find(mask); %// find available positions
r = randi(numel(available), 1); %// pick one available position
itu = ii(available(r)); %// row of t, u-th time
jtu = jj(available(r)); %// col of t, u-th time
S(itu, jtu) = t; %// store t at that position
remove = (ii==itu) | (jj==jtu);
mask(remove) = false; %// update mask of positions available for t
u = u+1; %// next u
if u > n %// we are done with number t
t = t+1; %// let's go with new t
u = 1; %// initiallize u
mask = isnan(S(:)); %// initiallize mask for this t
end
if t > n %// we are done with all numbers
success = 1; %// exit outer loop (inner will be exited too)
end
end
end
disp(attempt) %// display number of attempts
disp(S) %// show result
An example result:
10 11 8 9 7 2 3 4 1 6 5
8 4 2 1 10 11 6 5 7 9 3
2 3 5 6 11 8 1 10 4 7 9
9 8 7 4 6 10 11 3 5 1 2
3 5 9 8 2 1 4 7 6 11 10
11 9 4 5 3 6 2 1 8 10 7
1 2 6 3 8 7 5 9 10 4 11
7 1 11 10 5 4 9 8 2 3 6
4 7 1 2 9 3 10 6 11 5 8
6 10 3 11 1 5 7 2 9 8 4
5 6 10 7 4 9 8 11 3 2 1
来源:https://stackoverflow.com/questions/26242944/how-to-generate-random-matrix-without-repetition-in-rows-and-cols