我想知道是否有一个已知的算法来执行以下操作,并且还想知道如何在C#中实现它。也许这是一种已知类型的问题。
示例:
假设我有一个班级
class GoldMine
{
public int TonsOfGold { get; set; }
}
以及List
个N=3
个此类商品
var mines = new List<GoldMine>() {
new GoldMine() { TonsOfGold = 10 },
new GoldMine() { TonsOfGold = 12 },
new GoldMine() { TonsOfGold = 5 }
};
然后将地雷合并到K=2
地雷将是合并
{ {Lines[0],Lines[1]}, {Lines[2]} }, // { 22 tons, 5 tons }
{ {Lines[0],Lines[2]}, {Lines[1]} }, // { 15 tons, 12 tons }
{ {Lines[1],Lines[2]}, {Lines[0]} } // { 17 tons, 10 tons }
并合并到K=1
地雷将是单一合并
{ Lines[0],Lines[1],Lines[2] } // { 27 tons }
我感兴趣的是合并过程的算法。
答案 0 :(得分:1)
如果我没有弄错的话,你所描述的问题是所有k的k组合数
我找到了一个代码片段,我认为它可以解决您的用例,但我不记得从哪里得到它。它必须来自StackOverflow。如果有人认出这段特殊代码,请告诉我,我一定会相信它。
所以这是扩展方法:
public static class ListExtensions
{
public static List<ILookup<int, TItem>> GroupCombinations<TItem>(this List<TItem> items, int count)
{
var keys = Enumerable.Range(1, count).ToList();
var indices = new int[items.Count];
var maxIndex = items.Count - 1;
var nextIndex = maxIndex;
indices[maxIndex] = -1;
var groups = new List<ILookup<int, TItem>>();
while (nextIndex >= 0)
{
indices[nextIndex]++;
if (indices[nextIndex] == keys.Count)
{
indices[nextIndex] = 0;
nextIndex--;
continue;
}
nextIndex = maxIndex;
if (indices.Distinct().Count() != keys.Count)
{
continue;
}
var group = indices.Select((keyIndex, valueIndex) =>
new
{
Key = keys[keyIndex],
Value = items[valueIndex]
})
.ToLookup(x => x.Key, x => x.Value);
groups.Add(group);
}
return groups;
}
}
一个打印输出的小实用工具:
public void PrintGoldmineCombinations(int count, List<GoldMine> mines)
{
Debug.WriteLine("count = " + count);
var groupNumber = 0;
foreach (var group in mines.GroupCombinations(count))
{
groupNumber++;
Debug.WriteLine("group " + groupNumber);
foreach (var set in group)
{
Debug.WriteLine(set.Key + ": " + set.Sum(m => m.TonsOfGold) + " tons of gold");
}
}
}
你会像这样使用它:
var mines = new List<GoldMine>
{
new GoldMine {TonsOfGold = 10},
new GoldMine {TonsOfGold = 12},
new GoldMine {TonsOfGold = 5}
};
PrintGoldmineCombinations(1, mines);
PrintGoldmineCombinations(2, mines);
PrintGoldmineCombinations(3, mines);
将产生以下输出:
count = 1
group 1
1: 27 tons of gold
count = 2
group 1
1: 22 tons of gold
2: 5 tons of gold
group 2
1: 15 tons of gold
2: 12 tons of gold
group 3
1: 10 tons of gold
2: 17 tons of gold
group 4
2: 10 tons of gold
1: 17 tons of gold
group 5
2: 15 tons of gold
1: 12 tons of gold
group 6
2: 22 tons of gold
1: 5 tons of gold
count = 3
group 1
1: 10 tons of gold
2: 12 tons of gold
3: 5 tons of gold
group 2
1: 10 tons of gold
3: 12 tons of gold
2: 5 tons of gold
group 3
2: 10 tons of gold
1: 12 tons of gold
3: 5 tons of gold
group 4
2: 10 tons of gold
3: 12 tons of gold
1: 5 tons of gold
group 5
3: 10 tons of gold
1: 12 tons of gold
2: 5 tons of gold
group 6
3: 10 tons of gold
2: 12 tons of gold
1: 5 tons of gold
注意:这没有考虑到集合内容的重复,我不确定你是否真的希望那些被过滤掉。 这是你需要的吗?
修改强>
实际上,查看你的评论似乎你不想要重复项,你也想要包含 k 的较低值,所以这里是一个小修改,取出重复项(在一个非常丑陋的方式,我道歉)并给你每组 k 的较低值:
public static List<ILookup<int, TItem>> GroupCombinations<TItem>(this List<TItem> items, int count)
{
var keys = Enumerable.Range(1, count).ToList();
var indices = new int[items.Count];
var maxIndex = items.Count - 1;
var nextIndex = maxIndex;
indices[maxIndex] = -1;
var groups = new List<ILookup<int, TItem>>();
while (nextIndex >= 0)
{
indices[nextIndex]++;
if (indices[nextIndex] == keys.Count)
{
indices[nextIndex] = 0;
nextIndex--;
continue;
}
nextIndex = maxIndex;
var group = indices.Select((keyIndex, valueIndex) =>
new
{
Key = keys[keyIndex],
Value = items[valueIndex]
})
.ToLookup(x => x.Key, x => x.Value);
if (!groups.Any(existingGroup => group.All(grouping1 => existingGroup.Any(grouping2 => grouping2.Count() == grouping1.Count() && grouping2.All(item => grouping1.Contains(item))))))
{
groups.Add(group);
}
}
return groups;
}
它为 k = 2 生成以下输出:
group 1
1: 27 tons of gold
group 2
1: 22 tons of gold
2: 5 tons of gold
group 3
1: 15 tons of gold
2: 12 tons of gold
group 4
1: 10 tons of gold
2: 17 tons of gold
答案 1 :(得分:0)
这实际上是枚举一组N个对象的所有K分区的问题,通常被描述为枚举将N个标记对象放入K个未标记框中的方法。
几乎总是如此,解决涉及未标记或无序替代的枚举问题的最简单方法是创建规范排序,然后弄清楚如何仅生成规范排序的解决方案。在这种情况下,我们假设对象有一些总排序,以便我们可以通过1和N之间的整数引用它们,然后我们将对象按顺序放入分区,并按第一个对象的索引对分区进行排序在每一个。很容易看出这种排序不会产生重复,并且每个分区都必须符合某些规范排序。
然后我们可以通过N个整数序列表示给定的规范排序,其中每个整数是相应对象的分区的编号。然而,并非每个N个整数序列都有效;我们需要约束序列,以便分区按规范顺序排序(按第一个元素的索引排序)。约束很简单:序列中的每个元素必须是先前出现在序列中的某个整数(放置在已存在的分区中的对象),或者它必须是下一个分区的索引,它比索引的索引多一个最后一个分区已经存在。总结:
根据上述一组简单的约束生成序列可以很容易地递归完成。我们从一个空序列开始,然后连续添加每个可能的下一个元素,递归调用此过程以填充整个序列。只要生成可能的下一个元素列表很简单,递归过程就会很简单。在这种情况下,我们需要三条信息来生成此列表: N , K ,以及到目前为止生成的最大值。
这导致以下伪代码:
GenerateAllSequencesHelper(N, K, M, Prefix):
if length(Prefix) is N:
Prefix is a valid sequence; handle it
else:
# [See Note 1]
for i from max(1, length(Prefix) + 1 + K - N)
up to min(M + 1, K):
Append i to Prefix
GenerateAllSequencesHelper(N, K, max(M, i), Prefix)
Pop i off of Prefix
GenerateAllSequences(N, K):
GenerateAllSequencesHelper(N, K, 0, [])
由于递归深度对于此过程的任何实际应用都将极为有限,因此递归解决方案应该没问题。但是,即使不使用堆栈,也可以非常简单地生成迭代解决方案。这是受约束序列的标准枚举算法的实例:
在迭代算法中,向后扫描可能涉及检查O(N)元素,这显然使其比递归算法慢。然而,在大多数情况下,它们将具有相同的计算复杂度,因为在递归算法中,每个生成的序列也会产生递归调用的成本和到达它所需的返回。如果每个(或至少,大多数)递归调用产生多个替代,则递归算法对于每个生成的序列仍然是O(1)。
但是在这种情况下,只要扫描步骤可以在O(1)中执行,迭代算法很可能每个生成的序列也是O(1);也就是说,只要可以在不检查整个序列的情况下执行它。
在这种特殊情况下,计算直到给定点的序列的最大值不是O(1),但是我们也可以通过维持累积最大值的向量来产生O(1)迭代算法。 (实际上,此向量对应于上面递归过程中 M 参数的堆栈。)
维护 M 向量很容易;一旦我们拥有它,我们就可以很容易地确定&#34;可递增的&#34;序列中的元素:如果 i &gt; 0, M [ i ]等于,则元素 i 可递增 M [ i -1], M [ i ]不等于 K 。 [注2]
如果我们想要生成所有分区,我们会用更简单的方法替换上面的for
循环:
for i from 1 to M+1:
这个答案主要基于this answer,但该问题要求所有分区;在这里,您想要生成K分区。如上所述,算法非常相似。