如何计算任意数字集合的总和,以及这些数字的所有子集?

时间:2011-02-01 21:43:37

标签: c# algorithm

说我有一组数字:

Group1 = 10, Group2 = 15, Group3 = 20, Group4 = 30

我想输出所有数字子集的总和

10 + 15 = 25
10 + 15 + 20 = 45
10 + 15 + 20 + 30 = 75
15 + 20 = 35
15 + 20 + 30 = 65
20 + 30 = 50
10 + 20 = 30
10 + 30 = 40
10 + 20 + 30 = 60
... (assumed the rest is typed out)

这些组中的每一个都有一个名称,所以我想在结果之前打印出计算中使用的名称:

Group1 + Group2 = 25

如何做这样的事情?

编辑:编辑标签的JacobM,这不是作业,在你开始编辑之前会很感激。我实际上是在一个客户站点试图平衡一组数字,结果是错误的。我的想法是确定哪一组数字等于两组之间的差值,这将直接识别问题。

注意:这将是浮点值,而不是整数。

EDIT2:添加任意,以便我可以理解我不能只用一堆string.format来输出一次..我可以在那时轻松使用excel。

8 个答案:

答案 0 :(得分:6)

  

我的想法是确定哪一组数字等于两组之间的差值,这将直接识别问题。

问题“给定一个整数s和一组整数,设置和的任何非空子集到s?”被称为“subset sum problem”。这是非常好的研究,它是NP完全。 (有关相关问题,请参阅this link。)

也就是说,它是在合理的时间内解决的难题之一。人们普遍认为(虽然目前没有证明),但是对于这个问题,可能不存在多项式时间算法。对于包含n个元素的集合,您可以做的最好就是O(2 ^ n)。

(我注意到你的问题是浮点数,而不是整数。只要你正确处理计算总和与目标总和的比较来处理可能产生的任何舍入错误,这并不重要。总和。)

对于少数元素 - 你说你在集合中只有15个左右 - 你最好的选择就是彻底尝试它们。这是你如何做到的。

诀窍是要意识到从0到2 ^ n的每个整数都有一个子集。如果你用二进制看这些数字:

0000
0001
0010
0011
...

每个对应一个子集。第一个没有成员。第二组只有第1组。第三组只有第2组。第四组有第1组和第2组。依此类推。

伪代码很简单:

for each integer i from 1 to 2^n
{
  sum = 0;
  for each integer b from 1 to n
  {
    if the bth bit of i is on then sum = sum + group(b)
  }
  if sum == target then print out i in binary and quit
}
quit with no solution

显然这是O(n 2 ^ n)。如果你能找到总是比O(c ^ n)更好的算法,或者证明你找不到这样的算法,那么你将永远成名。< / p>

维基百科的文章有一个更好的算法,可以更快地给出答案大多数但不是所有。我会首先使用朴素算法,因为它只需要几分钟的时间来编写代码;如果速度慢得令人无法接受,那就选择更快,更复杂的算法。

答案 1 :(得分:2)

这符合所有可能的组合......

static void Main(string[] args)
{
    Dictionary<string, float> groups = new Dictionary<string, float>();
    groups.Add("Group1", 10);
    groups.Add("Group2", 15);
    groups.Add("Group3", 20);
    groups.Add("Group4", 30);

    for (int i=0; i < groups.Count - 1; i++)
    {
        Iterate(groups, i, 0, "");
    }

    Console.Read();
}

private static void Iterate(Dictionary<string, float> groups, int k, float sum, string s)
{
    KeyValuePair<string, float> g = groups.ElementAt(k);

    if (string.IsNullOrEmpty(s))
    {
        s = g.Key;
    }
    else
    {
        s += " + " + g.Key;
        Console.WriteLine(s + " = " + (sum + g.Value));
    }

    for (int i = k + 1; i < groups.Count; i++)
    {
        Iterate(groups, i, sum + g.Value, s);
    }
}

答案 2 :(得分:0)

如果群组是自定义数据类型,您可以重载+-*/===!=以及随后的+=-=*=/=运营商,如下所示:MSDN: Operator Overloading Tutorial

如果您的数据类型是原生数据类型:intInt32),longdecimaldoublefloat你可以做你的操作。

要输出您可以使用的数字总和:

String.Format("{0} + {1} = {2}", Group1, Group2, (Group1 + Group2));

String.Format("{0} + {1} + {2} = {3}", Group1, Group2, Group3, (Group1 + Group2 + Group3));

最后,如果在这些示例中,Group是自定义数据类型,您还必须重载ToString()方法,以便它可以正确显示。

<bleepzter/>

好的,第2部分 - 面向对象算法设计?

我们假设您有以下内容:

public class Set: List<float>
{
    public Set():base(){}

    public static Set operator+(Set set1, Set set2)
    {
        Set result = new Set();
        result.AddRange(set1.ToArray());
        result.AddRange(set2.ToArray());
        return result;
    }

    public float Sum
    {
        get
        {
            if( this.Count == 0 )
                return 0F;

            return this.Sum();                
        }
    }

    public override string ToString()
    {
        string formatString = string.Empty;
        string result = string.Empty;

        for(int i=0; i<this.Count; i++)
        {
            formatString += "{" + i.ToString() + "} + ";
        }

        formatString = result.TrimEnd((" +").ToCharArray()); // remove the last "+ ";    
        float[] values = this.ToArray();       
        result = String.Format(formatString, values);

        return String.Format("{0} = {1}", result, this.Sum);  
    }
}

对象集将具有Sum属性,以及将显示总和及其所有内容的ToString()方法。

答案 3 :(得分:0)

我问了一个关于将整数转换为字节表示以解决类似问题的问题。

Converting integer to a bit representation

答案 4 :(得分:0)

好的,最后一个并不像我想象的那么简单。我这次实际测试了它,它给出了正确的结果。

void PrintInner( string output, float total, List<KeyValuePair<string, float>> children )
{
    var parent = children[0];
    var innerChildren = new List<KeyValuePair<string, float>>();
    innerChildren.AddRange( children );
    innerChildren.Remove( parent );

    output += parent.Key + ":" + parent.Value.ToString();
    total += parent.Value;

    if( output != "" ) // Will prevent outputting "Group1:10 = 10", comment out if desired.
        Console.WriteLine( output + " = " + total.ToString() );
    output += " + ";

    while( innerChildren.Count > 0 )
    {
        PrintInner( output, total, innerChildren );
        innerChildren.RemoveAt( 0 );
    }

}


void PrintAll()
{
    var items = new List<KeyValuePair<string,float>>()
    {
        new KeyValuePair<string,float>>( "Group1", 10 ),
        new KeyValuePair<string,float>>( "Group2", 15 ),
        new KeyValuePair<string,float>>( "Group3", 20 ),
        new KeyValuePair<string,float>>( "Group4", 30 )
    }

    while( items.Count > 0 )
    {
        PrintInner( "", 0, items );
        items.RemoveAt( 0 );
    }
}

答案 5 :(得分:0)

这是我的10美分。它使用了我认为@DK暗示的概念。您获取一个整数并将其转换为二进制数,表示要添加的组的位掩码。 1表示添加它,0表示跳过它。它在VB中,但应该很容易转换为C#。

    '//Create the group of numbers
    Dim Groups As New List(Of Integer)({10, 15, 20, 30})
    '//Find the total number groups (Same as 2^Groups.Count() - 1 but reads better for me)
    Dim MaxCount = Convert.ToInt32(New String("1"c, Groups.Count), 2)

    '//Will hold our string representation of the current bitmask (0011, 1010, etc)
    Dim Bits As String
    '//Will hold our current total
    Dim Total As Integer
    '//Will hold the names of the groups added
    Dim TextPart As List(Of String)
    '//Loop through all possible combination
    For I = 0 To MaxCount
        '//Create our bitmask
        Bits = Convert.ToString(I, 2).PadLeft(Groups.Count, "0")
        '//Make sure we have got at least 2 groups
        If Bits.Count(Function(ch) ch = "1"c) <= 1 Then Continue For
        '//Re-initialize our group array
        TextPart = New List(Of String)
        '//Reset our total
        Total = 0
        '//Loop through each bit
        For C = 0 To Bits.Count - 1
            '//If its a 1, add it
            If Bits(C) = "1"c Then
                Total += Groups(C)
                TextPart.Add("Group" & (C + 1))
            End If
        Next
        '/Output
        Trace.WriteLine(Join(TextPart.ToArray(), " + ") & " = " & Total)
    Next

输出:

Group3 + Group4 = 50
Group2 + Group4 = 45
Group2 + Group3 = 35
Group2 + Group3 + Group4 = 65
Group1 + Group4 = 40
Group1 + Group3 = 30
Group1 + Group3 + Group4 = 60
Group1 + Group2 = 25
Group1 + Group2 + Group4 = 55
Group1 + Group2 + Group3 = 45
Group1 + Group2 + Group3 + Group4 = 75

答案 6 :(得分:0)

这是一个相当经典的组合问题。有关详细信息,请参阅此帖子:

Algorithm to return all combinations of k elements from n

实际上你想要做的是从N-choose-1到N-choose-N迭代并计算每个子集的总和。

答案 7 :(得分:0)

正如已经说过,解决方案的关键在于获得所有可能的组合!您可以在 static 类中输入类似的内容,将其注册为扩展方法:

public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int length = -1)
{
    switch (length)
    {
        case -1:
            foreach (var combination in Enumerable.Range(1, elements.Count()).Select(count => elements.Combinations(count)).SelectMany(c => c))
                yield return combination;
            break;
        case 0:
            yield return new T[0];
            break;
        default:
            if (length < -1) throw new ArgumentOutOfRangeException("length");
            foreach (var combination in 
                elements
                .SelectMany((element, index) => 
                    elements
                    .Skip(index + 1)
                    .Combinations(length - 1)
                    .Select(previous => (new[] { element }).Concat(previous))))
                yield return combination;
            break;
    }
}

...并像这样使用它:

static void Main(string[] args)
{
    var groups = new[]
                    {
                        new Tuple<string, int>("Group1", 15),
                        new Tuple<string, int>("Group2", 5),
                        new Tuple<string, int>("Group3", 17),
                    };

    foreach (var sum in groups
        .Combinations()
        .Select(x => 
            string.Join(" + ", x.Select(tuple => tuple.Item1)) + 
            " = " + 
            x.Sum(tuple => tuple.Item2)))
    {
        Console.WriteLine(sum);
    }
    Console.ReadLine();
}

输出:

Group1 = 15
Group2 = 5
Group3 = 17
Group1 + Group2 = 20
Group1 + Group3 = 32
Group2 + Group3 = 22
Group1 + Group2 + Group3 = 37