2016-09-08 3 views
1

Я попытался найти хороший способ ускорить код для проблемы, над которой я работал. Основная идея кода очень проста. Существует пять входов:Оптимизация четырех вложенных циклов - я обещаю, что искал

Четыре 1xm (для некоторых m < n могут быть разных размеров) матрицы (A, B, C, D), которые являются попарно непересекающимися подмножествами {1,2, ..., n} и одну nxn симметричную двоичную матрицу (M). Основная идея для кода, чтобы проверить неравенство для для каждой комбинации элементов и, если выполняется неравенство, возвращать значения, которые заставляют его держать, то есть:

for a = A 
     for b = B 
     for c = C 
      for d = D 
      if M(a,c) + M(b,d) < M(a,d) + M(b,c) 
       result = [a b c d]; 
       return 
      end 
      end 
     end 
     end 
    end 

Я знаю, что должно быть лучшим способом сделать это. Во-первых, поскольку он симметричен, я могу сократить половину проверяемых элементов, так как M (a, b) = M (b, a). Я изучал векторизацию, нашел несколько функций, о которых я никогда не слышал с MATLAB (поскольку я относительно новый), но я не могу найти ничего, что особенно помогло бы мне в этой конкретной проблеме. Я думал о других способах решения проблемы, но ничего не было совершено, и я просто не знаю, что делать в этот момент.

Например, я мог бы разделить это на два случая: 1) Правая рука 1: то я должен проверить, что оба слагаемых в левой части равны 0. 2) Правая рука 2 : тогда я должен проверить, что хотя бы одно слагаемое в левой части равно 0.

Но, опять же, я не смогу избежать гнездования.

Я ценю всю помощь, которую вы можете предложить. Спасибо!

+0

Вы можете использовать ответы от этого вопроса, чтобы получить комбинированную матрицу http://stackoverflow.com/questions/4165859/generate-all-possible-combinations-of-the-elements-of-some-vectors -cartesian-pr –

+2

Пожалуйста, определите свои массивы в коде Matlab, а не на словах. –

ответ

1

Здесь вы задаете два вопроса: (1) есть более эффективный алгоритм для выполнения этого поиска, и (2) как можно векторизовать это в MATLAB. Первое очень интересно задуматься, но может немного выходить за рамки этого форума. На второй легче ответить.

Как указано в комментариях ниже ваш вопрос, вы можете векторизации для цикла, перечисляя все возможности и проверять их все вместе, и ответы from this question могут помочь:

[a,b,c,d] = ndgrid(A,B,C,D);  % Enumerate all combos 
a=a(:); b=b(:); c=c(:); d=d(:); % Reshape from 4-D matrices to vectors 
ac = sub2ind(size(M),a,c);  % Convert subscript pairs to linear indices 
bd = sub2ind(size(M),b,d); 
ad = sub2ind(size(M),a,d); 
bc = sub2ind(size(M),b,c); 
mask = (M(ac) + M(bd) < M(ad) + M(bc));  % Test the inequality 
results = [a(mask), b(mask), c(mask), d(mask)]; % Select the ones that pass 

Опять же, это ISN Это алгоритмическое изменение: оно по-прежнему имеет ту же сложность, что и ваш вложенный цикл. Инициализация может привести к тому, что он будет работать быстрее, но также не имеет раннего завершения, поэтому в некоторых случаях он может быть медленнее.

0

Если у вас есть доступ к Neural Network Toolbox, combvec может быть полезно здесь.

работает allCombs = combvec(A,B,C,D) даст вам матрицу (4 по m1*m2*m3*m4), который выглядит как:

[... 
a1, a1, a1, a1, a1 ... a1... a2... am1; 
b1, b1, b1, b1, b1 ... b2... b1... bm2; 
c1, c1, c1, c1, c2 ... c1... c1... cm3; 
d1, d2, d3, d4, d1 ... d1... d1... dm4] 

Вы можете использовать sub2ind и Matrix индексирование для установки двух значений нужно для неравенства:

indices = [sub2ind(size(M),allCombs(1,:),allCombs(3,:)); 
      sub2ind(size(M),allCombs(2,:),allCombs(4,:)); 
      sub2ind(size(M),allCombs(1,:),allCombs(4,:)); 
      sub2ind(size(M),allCombs(2,:),allCombs(3,:))]; 

testValues = M(indices); 
testValues(5,:) = (testValues(1,:) + testValues(2,:) < testValues(3,:) + testValues(4,:)) 

ваших окончательного a,b,c,d индексов может быть получен, говоря

allCombs(:,find(testValues(5,:))) 

Который бы напечатал матрицу со всеми столбцами, для которых было выполнено неравенство.

This article может быть полезно.

0

Поскольку M является двоичным, мы можем думать об этом как о проблеме графа. i, j в {1..n} соответствуют узлам, а M(i,j) указывает, существует ли неориентированный край, соединяющий их.

С A, B, C, D являются непересекающимися, что немного упрощает проблему. Мы можем подойти к решению проблемы поэтапно:

  1. Найти все (c,d), для которых существует a такое, что M(a,c) < M(a,d). Назовем этот набор CD_lt_a (подмножество C * D такое, что для некоторого выполняется неравенство «меньше».
  2. Найти все (c,d), для которых существует a такой, что M(a,c) <= M(a,d), и позвоните по этому набору CD_le_a.
  3. Повторите для b, образуя CD_lt_b для M(b,d) < M(b,c) и CD_le_b для M(b,d)<=M(b,c).
  4. Один из способов удовлетворить общее неравенство - M(a,c) < M(a,d) и M(b,d) <= M(b,c), поэтому мы можем посмотреть на пересечение CD_lt_a и CD_le_b.
  5. Другой способ - если M(a,c) <= M(a,d) и M(b,d) < M(b,c), поэтому перейдите на перекресток CD_le_a и CD_lt_b.
  6. С (c,d) известно, мы можем вернуться назад и найти (a,b).

И вот моя реализация:

% 0. Some preliminaries 
% Get the size of each set 
mA = numel(A); mB = numel(B); mC = numel(C); mD = numel(D); 

% 1. Find all (c,d) for which there exists a such that M(a,c) < M(a,d) 
CA_linked = M(C,A); 
AD_linked = M(A,D); 
CA_not_linked = ~CA_linked; 
% Multiplying these matrices tells us, for each (c,d), how many nodes 
% in A satisfy this M(a,c)<M(a,d) inequality 
% Ugh, we need to cast to double to use the matrix multiplication 
CD_lt_a = (CA_not_linked * double(AD_linked)) > 0;  

% 2. For M(a,c) <= M(a,d), check that the converse is false for some a 
AD_not_linked = ~AD_linked; 
CD_le_a = (CA_linked * double(AD_not_linked)) < mA; 

% 3. Repeat for b 
CB_linked = M(C,B); 
BD_linked = M(B,D); 
CD_lt_b = (CB_linked * double(~BD_linked)) > 0; 
CD_le_b = (~CB_linked * double(BD_linked)) < mB; 

% 4. Find the intersection of CD_lt_a and CD_le_b - this is one way 
% to satisfy the inequality M(a,c)+M(b,d) < M(a,d)+M(b,c) 
CD_satisfy_ineq_1 = CD_lt_a & CD_le_b; 

% 5. The other way to satisfy the inequality is CD_le_a & CD_lt_b 
CD_satisfy_ineq_2 = CD_le_a & CD_lt_b; 
inequality_feasible = any(CD_satisfy_ineq_1(:) | CD_satisfy_ineq_2(:)); 

Обратите внимание, что вы можете остановиться здесь, если осуществимость ваша единственная забота. Сложность составляет A*C*D + B*C*D, что лучше, чем худший случай A*B*C*D сложность цикла for. Однако раннее завершение означает, что ваши вложенные для циклов все еще могут быть быстрее в определенных случаях.

В следующем блоке кода перечислены все a,b,c,d, которые удовлетворяют неравенству. Он не очень хорошо оптимизирован (он присоединяется к матрице изнутри цикла), поэтому он может быть довольно медленным, если есть много результатов.

% 6. With (c,d) known, find a and b 
% We can define these functions to help us search 
find_a_lt = @(c,d) find(CA_not_linked(c,:)' & AD_linked(:,d)); 
find_a_le = @(c,d) find(CA_not_linked(c,:)' | AD_linked(:,d)); 
find_b_lt = @(c,d) find(CB_linked(c,:)' & ~BD_linked(:,d)); 
find_b_le = @(c,d) find(CB_linked(c,:)' | ~BD_linked(:,d)); 
% I'm gonna assume there aren't too many results, so I will be appending 
% to an array inside of a for loop. Bad for performance, but maybe a bit 
% more readable for a StackOverflow answer. 
results = zeros(0,4); 
% Find those that satisfy it the first way 
[c_list,d_list] = find(CD_satisfy_ineq_1); 
for ii = 1:numel(c_list) 
    c = c_list(ii); d = d_list(ii); 
    a = find_a_lt(c,d); 
    b = find_b_le(c,d); 
    % a,b might be vectors, in which case all combos are valid 
    % Many ways to find all combos, gonna use ndgrid() 
    [a,b] = ndgrid(a,b); 
    % Append these to the growing list of results 
    abcd = [a(:), b(:), repmat([c d],[numel(a),1])]; 
    results = [results; abcd]; 
end 
% Repeat for the second way 
[c_list,d_list] = find(CD_satisfy_ineq_2); 
for ii = 1:numel(c_list) 
    c = c_list(ii); d = d_list(ii); 
    a = find_a_le(c,d); 
    b = find_b_lt(c,d); 
    % a,b might be vectors, in which case all combos are valid 
    % Many ways to find all combos, gonna use ndgrid() 
    [a,b] = ndgrid(a,b); 
    % Append these to the growing list of results 
    abcd = [a(:), b(:), repmat([c d],[numel(a),1])]; 
    results = [results; abcd]; 
end 
% Remove duplicates 
results = unique(results, 'rows'); 
% And actually these a,b,c,d will be indices into A,B,C,D because they 
% were obtained from calling find() on submatrices of M. 
if ~isempty(results) 
    results(:,1) = A(results(:,1)); 
    results(:,2) = B(results(:,2)); 
    results(:,3) = C(results(:,3)); 
    results(:,4) = D(results(:,4)); 
end 

Я испытал это на следующий тест:

m = 1000; 
A = (1:m); B = A(end)+(1:m); C = B(end)+(1:m); D = C(end)+(1:m); 
M = rand(D(end),D(end)) < 1e-6; M = M | M'; 

Мне нравится думать, что первая часть (см если неравенство является допустимым для любого а, Ь, с, d) работал очень хорошо , Другие векторизованные ответы (которые используют ndgrid или combvec для перечисления всех комбинаций a, b, c, d) потребуют 8 терабайт памяти для проблемы такого размера!

Но я бы не рекомендовал запускать вторую часть (перечисляя все результаты), когда есть несколько сотен c,d, которые удовлетворяют неравенству, потому что это будет довольно медленно.


P.S.Я знаю, что уже ответил, но этот ответ касался векторизации таких циклов в целом и менее специфичен для вашей конкретной проблемы.

P.P.S. Этот вид напоминает мне об stable marriage problem. Возможно, некоторые из этих ссылок будут содержать алгоритмы, относящиеся к вашей проблеме. Я подозреваю, что истинный алгоритм, основанный на графике, может, вероятно, достичь наихудшей сложности, так как это дополнительно предлагает раннее завершение. Но я думаю, что было бы сложно эффективно реализовать алгоритм на основе графов в MATLAB.


P.P.P.S. Если вы хотите только одно из возможных решений, вы можете упростить шаг 6, чтобы вернуть только одно значение, например.

find_a_lt = @(c,d) find(CA_not_linked(c,:)' & AD_linked(:,d), 1, 'first'); 
find_a_le = @(c,d) find(CA_not_linked(c,:)' | AD_linked(:,d), 1, 'first'); 
find_b_lt = @(c,d) find(CB_linked(c,:)' & ~BD_linked(:,d), 1, 'first'); 
find_b_le = @(c,d) find(CB_linked(c,:)' | ~BD_linked(:,d), 1, 'first'); 
if any(CD_satisfy_ineq_1) 
    [c,d] = find(CD_satisfy_ineq_1, 1, 'first'); 
    a = find_a_lt(c,d); 
    b = find_a_le(c,d); 
    result = [A(a), B(b), C(c), D(d)]; 
elseif any(CD_satisfy_ineq_2) 
    [c,d] = find(CD_satisfy_ineq_2, 1, 'first'); 
    a = find_a_le(c,d); 
    b = find_a_lt(c,d); 
    result = [A(a), B(b), C(c), D(d)]; 
else 
    result = zeros(0,4); 
end 
+0

Благодарим вас за этот очень подробный ответ! Раннее завершение - вот то, к чему я стремился здесь, поэтому мне не нужны все возможные множества, которые удовлетворяют неравенству. На каком языке вы могли бы рекомендовать эту программу вместо MATLAB? – loopified

+0

Я добавил пример того, как вернуть только одно решение. Если проблема достаточно мала, что вы можете представить 'M' как полную матрицу, я думаю, что этот метод (основанный на матричном умножении матрицы смежности) действительно не так уж плох. Если вы действительно хотите попробовать другой язык, я бы рекомендовал какой-то объектно-ориентированный скомпилированный язык, такой как C++. Но это также зависит от того, с чем вы уже знакомы и/или заинтересованы в обучении. Я также слышал хорошие вещи о Джулии, которая больше похожа на MATLAB, но скомпилирована. – KQS

+0

Большое спасибо! Не могли бы вы объяснить, почему двойной должен был использоваться с матричным умножением? Я просмотрел литературу и не совсем понимаю, почему это необходимо в этом случае. – loopified

Смежные вопросы