适用于所有可能组合的最快解决方案,在k> 2且n大的情况下从k中取出k个元素

时间:2015-03-19 02:33:42

标签: algorithm matlab combinations

我正在使用MATLAB从 n 可能的元素中找到 k 元素的所有可能组合。我偶然发现this question,但遗憾的是它并没有解决我的问题。当然,nchoosek也不是因为我的 n 大约是100。

事实是,我不需要同时拥有所有可能的组合。我将解释我需要什么,因为可能有一种更简单的方法来实现所需的结果。我有一个100行和25列的矩阵 M

M 的子矩阵视为由 M 的所有列形成的矩阵,并且只是行的子集。我有一个函数 f ,它可以应用于任何给出-1或1结果的矩阵。例如,您可以将函数视为sign(det(A))其中 A 是任何矩阵(确切的函数与问题的这一部分无关)。

我想知道 M 的最大行数是多少,这些行形成的子矩阵 A f(A)= 1 。请注意,如果 f(M)= 1 ,我就完成了。但是,如果不是这种情况,那么我需要开始组合行,从99行的所有组合开始,然后取98行的那些,依此类推。

到目前为止,我的实现与nchoosek有关,当 M 只有几行时,它就有效。但是,现在我正在处理一个相对较大的数据集,事情就会陷入困境。你们有没有人想到一种方法来实现这一点而不必使用上述功能?任何帮助都将很高兴。

这是我的最小工作示例,适用于小obs_tot,但在尝试使用更大的数字时失败:

value = -1; obs_tot = 100; n_rows = 25;
mat = randi(obs_tot,n_rows);
while value == -1
    posibles = nchoosek(1:obs_tot,i);
    [num_tries,num_obs] = size(possibles);
    num_try = 1;
        while value == 0 && num_try <= num_tries
        check = mat(possibles(num_try,:),:);
        value = sign(det(check));
        num_try = num_try + 1;
        end
    i = i - 1;
end
obs_used = possibles(num_try-1,:)';

1 个答案:

答案 0 :(得分:1)

<强>序言

正如你自己在你的问题中注意到的那样,不要让nchoosek同时返回所有可能的组合,而是逐个枚举它们以便在{{1}时不爆炸内存变大了。如下所示:

n

这是一个像Matlab类这样的枚举器的实现。它基于C#/ .NET中的经典IEnumerator<T>接口,并模仿nchoosek中的子功能enumerator = CombinationEnumerator(k, n); while(enumerator.MoveNext()) currentCombination = enumerator.Current; ... end 展开方式):

combs

我们可以从命令窗口测试实现是否正常:

%
% PURPOSE:
%
%   Enumerates all combinations of length 'k' in a set of length 'n'.
%
% USAGE:
%
%   enumerator = CombinaisonEnumerator(k, n);
%   while(enumerator.MoveNext())
%       currentCombination = enumerator.Current;
%       ...
%   end
%

%% ---
classdef CombinaisonEnumerator  < handle

    properties (Dependent) % NB: Matlab R2013b bug => Dependent must be declared before their get/set !
        Current; % Gets the current element.
    end

    methods
        function [enumerator] = CombinaisonEnumerator(k, n)
        % Creates a new combinations enumerator.

            if (~isscalar(n) || (n < 1) || (~isreal(n)) || (n ~= round(n))), error('`n` must be a scalar positive integer.'); end
            if (~isscalar(k) || (k < 0) || (~isreal(k)) || (k ~= round(k))), error('`k` must be a scalar positive or null integer.'); end
            if (k > n), error('`k` must be less or equal than `n`'); end

            enumerator.k = k;
            enumerator.n = n;
            enumerator.v = 1:n;            
            enumerator.Reset();

        end
        function [b] = MoveNext(enumerator)
        % Advances the enumerator to the next element of the collection.

            if (~enumerator.isOkNext), 
                b = false; return; 
            end

            if (enumerator.isInVoid)
                if (enumerator.k == enumerator.n),
                    enumerator.isInVoid = false;
                    enumerator.current = enumerator.v;
                elseif (enumerator.k == 1)
                    enumerator.isInVoid = false;
                    enumerator.index = 1;
                    enumerator.current = enumerator.v(enumerator.index);                    
                else
                    enumerator.isInVoid = false;
                    enumerator.index = 1;
                    enumerator.recursion = CombinaisonEnumerator(enumerator.k - 1, enumerator.n - enumerator.index);
                    enumerator.recursion.v = enumerator.v((enumerator.index + 1):end); % adapt v (todo: should use private constructor)
                    enumerator.recursion.MoveNext();
                    enumerator.current = [enumerator.v(enumerator.index) enumerator.recursion.Current]; 
                end
            else
                if (enumerator.k == enumerator.n),
                    enumerator.isInVoid = true;
                    enumerator.isOkNext = false;
                elseif (enumerator.k == 1)
                    enumerator.index = enumerator.index + 1;
                    if (enumerator.index <= enumerator.n)
                        enumerator.current = enumerator.v(enumerator.index);
                    else 
                        enumerator.isInVoid = true;
                        enumerator.isOkNext = false;
                    end                                       
                else
                    if (enumerator.recursion.MoveNext())
                        enumerator.current = [enumerator.v(enumerator.index) enumerator.recursion.Current];
                    else
                        enumerator.index = enumerator.index + 1;
                        if (enumerator.index <= (enumerator.n - enumerator.k + 1))
                            enumerator.recursion = CombinaisonEnumerator(enumerator.k - 1, enumerator.n - enumerator.index);
                            enumerator.recursion.v = enumerator.v((enumerator.index + 1):end); % adapt v (todo: should use private constructor)
                            enumerator.recursion.MoveNext();
                            enumerator.current = [enumerator.v(enumerator.index) enumerator.recursion.Current];
                        else 
                            enumerator.isInVoid = true;
                            enumerator.isOkNext = false;
                        end                                                               
                    end
                end
            end

            b = enumerator.isOkNext;

        end
        function [] = Reset(enumerator)
        % Sets the enumerator to its initial position, which is before the first element.

            enumerator.isInVoid = true;
            enumerator.isOkNext = (enumerator.k > 0);

        end
        function [c] = get.Current(enumerator)
            if (enumerator.isInVoid), error('Enumerator is positioned (before/after) the (first/last) element.'); end
            c = enumerator.current;
        end
    end

    properties (GetAccess=private, SetAccess=private)
        k = [];
        n = [];
        v = [];
        index = [];
        recursion = [];
        current = [];
        isOkNext = false;
        isInVoid = true;
    end

end

以预期方式返回以下>> e = CombinaisonEnumerator(3, 6); >> while(e.MoveNext()), fprintf(1, '%s\n', num2str(e.Current)); end 组合:

n!/(k!*(n-k)!)

此枚举器的实现可以进一步针对速度进行优化,或者通过按照更适合您的情况的顺序枚举组合(例如,首先测试一些组合而不是其他组合)......好吧,至少它是有效的! :)

解决问题

现在解决问题非常简单:

1  2  3
1  2  4
1  2  5
1  2  6
1  3  4
1  3  5
1  3  6
1  4  5
1  4  6
1  5  6
2  3  4
2  3  5
2  3  6
2  4  5
2  4  6
2  5  6
3  4  5
3  4  6
3  5  6
4  5  6