Поиск наибольшего общего делителя матрицы в MATLAB

Im ищет способ разделить определенные элементы матрицы с ее наименьшим общим делителем.

например, у меня есть векторы

[0,0,0; 2,4,2;-2,0,8]

Я могу сказать, что наименьший общий делитель равен 2, поэтому матрица после деления будет

[0,0,0;1,2,1;-1,0,4]

что встроенный метод, который может вычислить это?

спасибо заранее

p.s. Мне лично не нравится использовать циклы для этого вычисления, похоже, что есть встроенные вычисления который может выполнять деление элементов матрицы.

4 ответов


поскольку вам не нравятся циклы, как насчет рекурсивных функций?

iif = @(varargin) varargin{2 * find([varargin{1:2:end}], 1, 'first')}();
gcdrec=@(v,gcdr) iif(length(v)==1,v, ...
                     v(1)==1,1, ...
                     length(v)==2,@()gcd(v(1),v(2)), ...
                     true,@()gcdr([gcd(v(1),v(2)),v(3:end)],gcdr));
mygcd=@(v)gcdrec(v(:)',gcdrec);

A=[0,0,0; 2,4,2;-2,0,8];
divisor=mygcd(A);
A=A/divisor;

первая функция iif определит встроенную условную конструкцию. Это позволяет определить рекурсивную функцию, gcdrec, чтобы найти наибольший общий делитель массива. Это iif работает так: он проверяет, является ли первый аргумент true, если это так, то он возвращает второй аргумент. В противном случае он проверяет третий аргумент, и если это true, затем возвращает четвертый, и так далее. Вам нужно защитить рекурсивные функции, а иногда и другие величины, появляющиеся внутри него с помощью @(), в противном случае вы можете получить ошибки.

используя iif рекурсивная функция gcdrec работает так:

  • если входной вектор является скаляром, он возвращает его
  • else если первый компонент вектора равен 1, нет шансов на восстановление, поэтому он возвращает 1 (позволяет быстро возвращать большие матрицы)
  • else если входной вектор имеет длину 2, он возвращает наибольший общий делитель через gcd
  • иначе он называет себя укороченным вектором, в котором первые два элемента заменяются их наибольшим общим делителем.

функции mygcd это просто передний конец для удобства.

должно быть довольно быстро, и я думаю, что только глубина рекурсии может быть проблемой для очень больших проблем. Я быстро проверка времени для сравнения с циклической версией @Adriaan, используя A=randi(100,N,N)-50 С N=100, N=1000 и N=5000 и tic/toc.

  1. N=100:
    • петля 0.008 секунд
    • рекурсивные 0.002 секунд
  2. N=1000:
    • петля 0,46 секунды
    • рекурсивные 0,04 секунды
  3. N=5000:
    • цикл 11.8 секунды!--35-->
    • рекурсивные 0,6 секунды

обновление: интересно, что единственная причина, по которой я не отключил предел рекурсии (который по умолчанию 500), заключается в том, что мои данные не имели общего делителя. Установка случайной матрицы и ее удвоение приведет к попаданию в предел рекурсии уже для N=100. Поэтому для больших матриц это не сработает. Опять же, для небольших матриц решение @Adriaan идеально штраф.

я также попытался переписать его на половину входного вектора на каждом рекурсивном шаге: это действительно решает проблему предела рекурсии, но это очень медленно (2 секунды N=100, 261 секунд N=1000). Где-то может быть середина, где размер матрицы большой(ish), и время выполнения не так уж плохо, но я еще не нашел его.


 A = [0,0,0; 2,4,2;-2,0,8];
 B = 1;
 kk = max(abs(A(:))); % start at the end
 while B~=0 && kk>=0
     tmp = mod(A,kk);
     B = sum(tmp(:));
     kk = kk - 1;
 end
 kk = kk+1;

это, вероятно, не самый быстрый способ, но это будет делать сейчас. То, что я сделал здесь, инициализирует какой-то счетчик,B, чтобы сохранить сумму всех элементов в вашей матрице после принятия mod. the kk - это просто счетчик, который проходит через целые числа. mod(A,kk) вычисляет модуль после деления для каждого элемента A. Таким образом, если все ваши элементы полностью делятся на 2, он вернет 0 для каждого элемента. sum(tmp(:)) затем делает один столбец из Матрицы по модулю, которая суммируется для получения некоторого числа. Если и только если это число равно 0, существует общий делитель, так как тогда все элементы в A полностью делятся на kk. Как только это произойдет, ваш цикл останавливается, и ваш общий делитель-это число в kk. С kk уменьшается каждый счет это на самом деле одно значение слишком низко, таким образом, один добавляется.

примечание: Я только что отредактировал цикл для запуска назад так как вы ищете величайших компакт-диск, не самый маленький компакт-диск. Если бы у вас была матрица, как [4,8;16,8] он остановился бы на 2, а не 4. Извините за это, это работает сейчас, хотя оба других решения здесь намного быстрее.

наконец, деление матриц можно сделать следующим образом:

divided_matrix = A/kk;

согласен, мне тоже не нравятся петли! Давайте убьем их! .. --4-->

unqA = unique(abs(A(A~=0))).';             %//'
col_extent = [2:max(unqA)]';               %//'
B = repmat(col_extent,1,numel(unqA));
B(bsxfun(@gt,col_extent,unqA)) = 0;
divisor = find(all(bsxfun(@times,bsxfun(@rem,unqA,B)==0,B),2),1,'first');
if isempty(divisor)
    out = A;
else
    out = A/divisor;
end

пример работает

Случай #1:

A =
     0     0     0
     2     4     2
    -2     0     8
divisor =
     2
out =
     0     0     0
     1     2     1
    -1     0     4

случай #2:

A =
     0     3     0
     5     7     6
    -5     0    21
divisor =
     1
out =
     0     3     0
     5     7     6
    -5     0    21

вот еще один подход. Пусть A ваш входной массив.

  1. получить ненулевые значения A и принять их абсолютное значение. Вызовите результирующий вектор B.