ヒューリスティック アルゴリズムの場合、停止基準に達するまで、特定のセットの組み合わせを次々と評価する必要があります。
それらはたくさんあるので、現時点では、次のメモリ効率の良いイテレータ ブロックを使用してそれらを生成しています (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
。
コンビナトリアルグレイコードもざっと見ましたが、この問題に適した人を見つけることができませんでした。
このようなものを実装する方法についてのアイデアはありますか?
編集 :
この問題には別の (残念ながら簡単ではない) 定式化があります。
集合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);
}
}
}
これは問題なく動作します (Eric が意図したことを願っています :P) が、再帰的な方法の複雑さについてはまだ懸念があります。実際、各合計のすべての組み合わせを再生成して、合計が目的の値に達しない組み合わせを破棄しているようです。
内部関数の複雑さを軽減するために、有効な上限と下限を使用して反復を制限する方法を見つけました (そして、これの複雑さが何であるかを言うのは本当に難しいです)。
私の答えをチェックして、最終的なコードを確認してください。