Поиск наибольшего общего делителя матрицы в 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
.
-
N=100
:- петля 0.008 секунд
- рекурсивные 0.002 секунд
-
N=1000
:- петля 0,46 секунды
- рекурсивные 0,04 секунды
-
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
ваш входной массив.
- получить ненулевые значения
A
и принять их абсолютное значение. Вызовите результирующий векторB
.