Как перебрать все подматрицы? Подматрицей называется часть матрицы, полученная вычеркиванием какого-либо количества строк, и(или) какого-либо количества столбцов. Для примера матрица:
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
26 27 28 29 30
31 32 33 34 35
Подматрица:
12 13 15
22 23 25
27 28 30
При попытках написать алгоритм получается очень много вложенных циклов. Какой алгоритм можно использовать?
Harry
214k15 золотых знаков117 серебряных знаков229 бронзовых знаков
задан 24 мар 2021 в 17:34
3
Если размер не слишком большой (скажем, до 32×32 :)), обозначим вычеркнутую строку/столбец 0, оставленную — 1. Тогда любой набор вычеркиваний из набора N столбцов описывается числом от 0 до 2N. То же и для строк.
Так что получаем два цикла — по строкам и по столбцам, ну, а внутри надо просто аккуратно разобраться, какие строки и столбцы остаются и записать получающуюся матрицу.
Для показанного вами варианта имеется 1024 подматрицы — от пустой до полной матрицы.
ответ дан 24 мар 2021 в 17:45
HarryHarry
214k15 золотых знаков117 серебряных знаков229 бронзовых знаков
Assuming the matrix
Matrix = [
[1, 2,3],
[3, 4,5],
[5,6,7]
]
Split into 3 function:
def ContinSubSeq(lst):
size=len(lst)
for start in range(size):
for end in range(start+1,size+1):
yield (start,end)
def getsubmat(mat,start_row,end_row,start_col,end_col):
return [i[start_col:end_col] for i in mat[start_row:end_row] ]
def get_all_sub_mat(mat):
rows = len(mat)
cols = len(mat[0])
for start_row,end_row in ContinSubSeq(list(range(rows))):
for start_col,end_col in ContinSubSeq(list(range(cols))):
yield getsubmat(mat,start_row,end_row,start_col,end_col)
Run this
for i in get_all_sub_mat(Matrix):
print i
Or simpler, put into one function:
def get_all_sub_mat(mat):
rows = len(mat)
cols = len(mat[0])
def ContinSubSeq(lst):
size=len(lst)
for start in range(size):
for end in range(start+1,size+1):
yield (start,end)
for start_row,end_row in ContinSubSeq(list(range(rows))):
for start_col,end_col in ContinSubSeq(list(range(cols))):
yield [i[start_col:end_col] for i in mat[start_row:end_row] ]
У меня есть 2D вектор, который содержит матрицу целых чисел, которая выглядит следующим образом:
vector<vector<int>> Members;
То, что я пытаюсь найти, — это способ извлечения каждой возможной субматрицы матрицы NxN.
Например, если у меня была матрица 2×2:
0 -2
9 2
Это вывело бы:
0
-2
9
2
0
9
-2
2
0 -2
9 2
-1
Решение
Подматрица зависит от левой и правой нижней точек, поэтому вы можете указать все возможные местоположения и распечатать их по одному, например так:
//data stored in mat[max][max]
int max=5;//size of matrix
int i,j,m,n;
for(i=0;i<=max-1;i++)
for(j=0;j<=max-1;j++)
for(m=i;m<=max-1;m++)
for(n=j;n<=max-1;j++)
print(i,j,m,n);//a simple function
0
Другие решения
Других решений пока нет …
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
static void Main(string[] args) { int[,] arr = new int[5, 5] { { 1,2,3,4,5 }, { 6,7,8,9,10 }, { 11,12,13,14,15 }, { 16,17,18,19,20 }, { 21,22,23,24,25 } }; List<int[,]> res = GetSubSquareMatrix(arr).ToList(); } static IEnumerable<T[,]> GetSubSquareMatrix<T>(T[,] arr) { int n = arr.GetLength(0); int m = arr.GetLength(1); int[] nIndxs = Enumerable.Range(0, n).ToArray(); int[] mIndxs = Enumerable.Range(0, m).ToArray(); for (int i = 2; i < Math.Min(n, m); i++) { foreach (var sub_N_Indxs in GetCombinations(nIndxs, i)) { foreach (var sub_M_Indxs in GetCombinations(mIndxs, i)) { T[,] res = new T[i, i]; for (int k = 0; k < i; k++) { for (int j = 0; j < i; j++) { res[k,j] = arr[sub_N_Indxs[k], sub_N_Indxs[j]]; } } yield return res; } } } } /// <summary> /// Получение всех сочетаний из n (количество элементов массива arr) по k (combinationSize) /// </summary> static IEnumerable<T[]> GetCombinations<T>(T[] arr, int combinationSize) { if (arr == null || arr.Length == 0) yield break; if (combinationSize > arr.Length || combinationSize <= 0) throw new ArgumentException(); T[] res = new T[combinationSize]; int[] indxs = new int[combinationSize]; for (int i = 0; i < combinationSize; i++) indxs[i] = i; res = indxs.Select(x => arr[x]).ToArray(); yield return res; while (indxs[0] < arr.Length - combinationSize) { NextIndexes(indxs, arr.Length - 1); res = indxs.Select(x => arr[x]).ToArray(); yield return res; } } /// <summary> /// Вычисляет индексы элементов, составляющих еще пока непройденную комбинацию /// </summary> /// <param name="arr">Массив с индексами</param> /// <param name="maxIndx">Максимальный возможный индекс</param> static void NextIndexes(int[] arr, int maxIndx) { for (int i = 0; i < arr.Length; i++) { int temp = arr.Length - 1 - i; if (arr[temp] < maxIndx - i) { arr[temp]++; for (int j = temp + 1; j < arr.Length; j++) { arr[j] = arr[j - 1] + 1; } break; } } } |
Время на прочтение
5 мин
Количество просмотров 10K
Не так давно прошёл конкурс параллельного программирования Acceler8 2011. Суть задачи заключалась в поиске максимальной подматрицы в данной матрице (сумма элементов найденной подматрицы должна быть максимальной). После недолгого «гугления» было найдено, что некий алгоритм Тадао Такаока решает эту задачу быстрее других.
«Вызов принят!», и я начала искать этот алгоритм везде, где только можно, задавшись целью реализовать его. Не смотря на то, что распараллеливается он плохо и в своей сложности содержит немаленькую константу.
Однако всё, что удалось найти, — статьи на английском этого самого Тадао Такаоки (вот одна из этих статей). Пришлось переводить.
Сама идея алгоритма сначала казалась до безобразия простой:
Для начала нам необходимо посчитать все префиксные суммы s[i, j] для таких матриц, как a[1..i, 1..j], для всех i и j с ограничением s[i, 0] = s[0, j] = 0. Очевидно, это делается за время O(mn).
Основной алгоритм:
Если матрица оказывается одним элементом — возвращаем его значение.
Иначе:
- если m > n, поворачиваем матрицу на 90 градусов.
Таким образом m ≤ n.
- Пусть A_left — решение для левой половины.
- A_right — решение для правой половины.
- A_column — решение для column-centered задачи (нахождение такой максимальной подматрицы, которая бы пересекала центральную линию)
- Общее решение — максимум из этих трёх.
Если A_left и A_right находятся через рекурсию, то находить A_column надо вручную.
Вот, что написано по поводу нахождения этого самого решения в оригинальной статье:
«Теперь column-centered задача может быть решена следующим образом.
A_column = max(k=1:i-1,l=0:n/2-1,i=1:m,j=n/2+1:n) {s[i, j] − s[i, l] − s[k, j] + s[k, l]}.
Мы фиксируем i и k, и максимизируем оставшееся выражение изменением l и j. Тогда это эквивалентно следующему выражению:
i = 1, …, m и k = 1, …, i − 1.
A_column [i, k] = max(l=0:n/2-1,j=n/2+1:n) {−s[i, l] + s[k, l] + s[i, j] − s[k, j]}
Пусть s∗[i, j] = −s[j, i]. Тогда данное выражение может быть сведено к:
A_column [i, k] = −min(l=0:n/2-1) {s[i, l] + s∗ [l, k]} + max(j=n/2+1:n) {s[i, j] + s ∗[j, k]}«
Далее следует упоминуть о некой другой задаче Тадао Такаока: Distance Matrix Multiplication (не берусь переводить этот термин).
Суть такова:
Целью DMM является вычисление произведения расстояний(distance product) C = AB для двух n-мерных матриц A = а[i,j] and B = b[i,j], чьи элементы — вещественные числа.
c[i,j] = min(k=1:n) { a[i,k] + b[k,j] }
Суть c[i,j] — наименьшее расстояние из вершины i из первого слоя до вершины j в третьем слое в ациклическом ориентрированном графе, состоящем из трёх слоёв вершин. Эти вершины пронумерованы 1,…,n в каждом слое, и расстояние от i в первом слое до j во втором слое — a[i,j] и из i во втором слое до j в третьем слое — b[i,j]. Очевидно, что это выражение можно легко привести для подсчёта максимума вместо минимума — это будет задача о нахождении наибольших путей.
Так вот воспользовавшись этим определением можно получить следующее:
В формуле A_column [i, k] = −min(l=0:n/2-1) {s[i, l] + s∗ [l, k]} + max(j=n/2+1:n) {s[i, j] + s ∗[j, k]} видно, что первая часть формулы — DMM для минимума, вторая — DMM для максимума. Пусть S1 и S2 матрицы, чьи элементы (i, j) являются s[i, j − 1] s[i, j + n/2] соответственно. Для любой матрицы T, пусть T∗ — матрица, полученная из T транспонированием и отрицанием. Тогда верхнее выражение может быть посчитано «перемножением» S1 и S1∗ для минимума и получением нижне-треугольной матрицы, «перемножением» S2 и S2∗ для максимума и получением нижне-треугольной матрицы и, наконец, вычитанием из последней предыдущей матрицы. Вычислив в ней максимум, получим ответ:
A_column = max(DMM_max(S2*S2∗)-DMM_min(S1*S1∗)).
Далее преведен псевдокод для решения задачи DMM для матриц A и B размером nxn:
Copy Source | Copy HTML
- /Отсортировать n строк B в порядке убывания в списки list[i], т.о. в каждом списке list[i] будут находится индексы упорядоченных элементов строки i;
- /Заведём множество V = {1, ..., n};
- for i := 1 to n do begin
- for k := 1 to n do begin
- cand[k]:=first of list[k];
- d[k] := a[i, k] + b[k, cand[k]];
- end;
- / Упорядочим V как очередь с приоритетом по ключам d[1], ..., d[n];
- / Множество решений S - пустое;
- /* Фаза 1 : До критической точки */
- while |S| ≤ n − n log n do begin
- /Найти v с минимальным ключем в очереди;
- /Занести cand[v] в множество S;
- c[i, cand[v]] := d[v];
- /Пусть множество W = {w|cand[w] = cand[v]};
- for w in W do
- while cand[w] is in S do cand[w]:= next of list[w];
- /Отсортировать очередь W по новым ключам d[w] = a[i, w]+b[w, cand[w]];
- end;
- U := S;
- /* Фаза 2 : После критической точки */
- while |S| < n do begin
- /Найти v с минимальным ключём в очереди;
- if cand[v] is not in S then begin
- /Занести cand[v] в множество S;
- c[v, cand[v]] := d[v];
- /Пусть множество W = {w|cand[w] = cand[v]};
- end else W = {v};
- for w in W do
- cand[w]:=next of list[w];
- while cand[w] is in U do cand[w]:= next of list[w];
- /Отсортировать очередь W по ключам d[w] = a[i, w]+b[w, cand[w]];
- end;
- end.
В статье рекомендуется использовать бинарную кучу для очередей. В более поздних версиях было указано, что использование фиббоначиевой кучи могло бы ещё больше ускорить процесс, однако я, к своему стыду, не смогла её реализовать в полной мере. Поэтому использовала бинарную.
Однако даже реализовав этот псевдокод, я обнаружила, что решение не работает. Очевидная ошибка была найдена на этапе вычисления S1 и S2 — описанного правила для построения этих матриц было недостаточно для правильной работы алгоритма. И ни в одной статье я более подробного описания не нашла. Таким образом строить матрицы пришлось самой.
Итак, после долгих часов, потраченных на перебор и проверку, были получены следующие правила.
Пусть S — префиксная матрица для матрицы A.
Тогда матрица S1 размерами m на n/2 содержит в себе первый столбец из 0, а оставшиеся n/2-1 столбцы содержат первые столбцы префиксной матрицы S:
Матрица S1∗ размерами n/2 на m содержит первую строку и столбец из 0, оставшаяся подматрица представляет собой отрицание и транспонирование первых строк и столбцов префиксной матрицы S:
(k = n div 2 + n mod 2)
Матрица S2 размерами m на k содержит в себе столбцы префиксной матрицы S, начиная с k-го:
Матрица S2∗ размерами k на m: первый столбец из 0, далее — транспонирование и отрицание столбцов префиксной матрицы S, начиная с k-го столбца:
В целом алгоритм оказался очень сложным для понимания и реализации и не оправдал своих ожиданий на исключительную скорость(никаких дополнительных оптимизаций не осуществлялось).
В той же статье утверждается, что данный алгоритм работает за время O(n^3*log(log(n))/log(n)), а также упоминается, что алгоритм работает быстрее в сравнении с другими алгоритмами на матрицах большого размера (видимо, очень большого).
Если кому-то будет интересно или жизненно необходимо, здесь лежат исходники моей реализации (написано на плюсах, поэтому «понятности» не гарантирую). Буду очень рада, если эта статья кому-то поможет (не зря же я потратила столько времени на разбирательства с этим алгоритмом!).