Для эвристического алгоритма мне нужно оценить один за другим комбинации определенного набора, пока не достигнет критерия остановки.
Поскольку их много, на данный момент я их генерирую, используя следующий блок памяти итератора с памятью (вдохновленный python itertools.combinations
):
public static IEnumerable<T[]> GetCombinations<T>(this IList<T> pool, int r)
{
int n = pool.Count;
if (r > n)
throw new ArgumentException("r cannot be greater than pool size");
int[] indices = Enumerable.Range(0, r).ToArray();
yield return indices.Select(idx => pool[idx]).ToArray();
while (true)
{
int i;
for (i = r - 1; i >= 0; i--)
if (indices[i] != i + n - r)
break;
if (i < 0)
break;
indices[i] += 1;
for (int j = i + 1; j < r; j++)
indices[j] = indices[j - 1] + 1;
yield return indices.Select(idx => pool[idx]).ToArray();
}
}
Проблема заключается в том, чтобы значительно повысить эффективность моей эвристики, мне нужно было бы сгенерировать эти комбинации, отсортированные по сумме их индексов (другими словами, мне нужно сначала создать, комбинации, содержащие первые элементы набора).
например.
Рассмотрим множество S = {0,1,2,3,4,5}
(Я выбираю этот набор для простоты, так как элементы и их индексы совпадают).
Все возможные комбинации чисел r=4
, генерируемые данным алгоритмом:
(0, 1, 2, 3) SUM: 6
(0, 1, 2, 4) SUM: 7
(0, 1, 2, 5) SUM: 8
(0, 1, 3, 4) SUM: 8
(0, 1, 3, 5) SUM: 9
(0, 1, 4, 5) SUM: 10
(0, 2, 3, 4) SUM: 9
(0, 2, 3, 5) SUM: 10
(0, 2, 4, 5) SUM: 11
(0, 3, 4, 5) SUM: 12
(1, 2, 3, 4) SUM: 10
(1, 2, 3, 5) SUM: 11
(1, 2, 4, 5) SUM: 12
(1, 3, 4, 5) SUM: 13
(2, 3, 4, 5) SUM: 14
где, как вы видите, комбинации не сортируются строго по восходящей сумме.
Желаемый результат - это следующее:
(порядок комбинаций с одинаковой суммой не важен)
(0, 1, 2, 3) SUM: 6
(0, 1, 2, 4) SUM: 7
(0, 1, 2, 5) SUM: 8
(0, 1, 3, 4) SUM: 8
(0, 1, 3, 5) SUM: 9
(0, 2, 3, 4) SUM: 9
(0, 1, 4, 5) SUM: 10
(0, 2, 3, 5) SUM: 10
(1, 2, 3, 4) SUM: 10
(0, 2, 4, 5) SUM: 11
(1, 2, 3, 5) SUM: 11
(0, 3, 4, 5) SUM: 12
(1, 2, 4, 5) SUM: 12
(1, 3, 4, 5) SUM: 13
(2, 3, 4, 5) SUM: 14
Тривиальное решение состоит в том, чтобы сгенерировать все комбинации, затем отсортировать их по их сумме; но это не очень эффективно/возможно, так как количество комбинаций становится огромным, поскольку n
растет.
Я также быстро посмотрел на комбинаторные серые коды, но я не мог найти никого подходящего для этой проблемы.
У вас есть идея о том, как реализовать что-то вроде этого?
EDIT:
У этой проблемы есть альтернативная (к сожалению, не простая) формулировка.
Учитывая множество S
и число r
, все возможные суммы тривиальны, так как они всего лишь числа из суммы первых элементов r
из S
в сумму последних r
элементов S
.
Если сказать, что если для каждой суммы T
мы можем эффективно найти все комбинации, имеющие сумму T
, мы решим исходную задачу, так как просто сгенерируем их в порядке возрастания.
¹ эффективно означает, что я не хочу генерировать все комбинации и отбрасывать те, у которых есть другая сумма.
ИЗМЕНИТЬ 2:
После предложения @EricLippert я создал следующий код:
public static IEnumerable<T[]>
GetCombinationsSortedByIndexSum<T>(this IList<T> pool, int r)
{
int n = pool.Count;
if (r > n)
throw new ArgumentException("r cannot be greater than pool size");
int minSum = ((r - 1) * r) / 2;
int maxSum = (n * (n + 1)) / 2 - ((n - r - 1) * (n - r)) / 2;
for (int sum = minSum; sum <= maxSum; sum++)
{
foreach (var indexes in AllMonotIncrSubseqOfLenMWhichSumToN(0, n - 1, r, sum))
yield return indexes.Select(x => pool[x]).ToArray();
}
}
static IEnumerable<IEnumerable<int>>
AllMonotIncrSubseqOfLenMWhichSumToN(int seqFirstElement, int seqLastElement, int m, int n)
{
for (int i = seqFirstElement; i <= seqLastElement - m + 1; i++)
{
if (m == 1)
{
if (i == n)
yield return new int[] { i };
}
else
{
foreach (var el in AllMonotIncrSubseqOfLenMWhichSumToN(i + 1, seqLastElement, m - 1, n - i))
yield return new int[] { i }.Concat(el);
}
}
}
Это прекрасно работает (надеюсь, это то, что имел в виду Эрик: P), но меня все еще беспокоит сложность рекурсивного метода. На самом деле кажется, что мы регенерируем все комбинации для каждой суммы, отбрасывая те, которые не суммируются до желаемого значения.
Чтобы уменьшить сложность внутренней функции, я нашел способ ограничить итерации, используя эффективные верхние и нижние границы (и теперь очень сложно сказать, в чем сложность этого).
Отметьте мой ответ, чтобы увидеть окончательный код.