C# - 组合学

C# - Combinatorics

我有一个约 300 个对象的列表,它们都有价格和分数。我需要找到总价小于 X 的 15 个对象的最佳组合(即最高总分)。

在我看来,最直接的方法是嵌套 15 个 for 循环并检查每个可能的组合,但这需要几天时间。

在 C# 中有任何 'clean' 方法可以做到这一点吗?

谢谢!

没有示例很难提供帮助,但如果我理解了问题,那么这可能会有所帮助。

假设您的对象看起来像这样

public class Item
{
        public int Score { get; set; }
        public decimal Price { get; set; }
}

那么下面的内容应该可以解决你的问题。

var listOfObjects = new List<Item>();

var topItems = listOfObjects.Where(p => p.Price < 100).OrderByDescending(p => p.Score).Take(15);

编辑:所有细节都公开后,以下内容应该有所帮助

免责声明:快速而肮脏的解决方案(次优)

新建一个class

public class ItemWithRunningTotal
{
    public Item Item { get; set; }
    public decimal RunningTotal { get; set; }
}

那么以下内容应该可以满足您的需求。

var maxTotal = 1500; //for you 8000
        var objects = new List<Item>()
                      {
                          new Item() {Score = 10, Price = 100},
                          new Item() {Score = 20, Price = 800},
                          new Item() {Score = 40, Price = 600},
                          new Item() {Score = 5, Price = 300},
                      };

        decimal runningTotal = 0;
        var newList = objects
            .OrderByDescending(p => p.Score)
            .Select(p =>
                    {
                        runningTotal = runningTotal + p.Price;
                        return new ItemWithRunningTotal()
                               {
                                   Item = p,
                                   RunningTotal = runningTotal
                               };
                    })
            .OrderByDescending(p => p.RunningTotal)
            .Where(p => p.RunningTotal <= maxTotal).Take(15);

您正在寻找的是背包问题的解决方案。没有已知的方法可以快速做到这一点。 您可以修改动态解决方案 (https://en.wikipedia.org/wiki/Knapsack_problem#0.2F1_knapsack_problem) 以最多选择 maxCount 个项目,如下所示:

public static List<Item> BestCombination(List<Item> items, int maxPrice, int maxCount)
{
    var scores = new int[items.Count + 1, maxPrice + 1, maxCount + 1];

    for (int i = 1; i <= items.Count; i++)
    {
        var item = items[i - 1];
        for (int count = 1; count <= Math.Min(maxCount, i); count++)
        { 
            for (int price = 0; price <= maxPrice; price++)
            {                    
                if (item.Price > price)
                    scores[i, price, count] = scores[i - 1, price, count];                
                else
                    scores[i, price, count] = Math.Max(
                        scores[i - 1, price, count],
                        scores[i - 1, price - item.Price, count - 1] + item.Score);
            }
        }
    }

    var choosen = new List<Item>();
    int j = maxPrice;
    int k = maxCount;
    for (int i = items.Count; i > 0; i--)
    {
        var item = items[i - 1];
        if (scores[i, j, k] != scores[i - 1, j, k])
        {
            choosen.Add(item);
            j -= item.Price;
            k--;
        }
    }            

    return choosen;
}

请记住,与选择 <= maxCount 个对象相比,选择 exaclty maxCount 个对象给你的总分要低。例如,对于商品 [(100, 10), (200,20), (300,30), (500,80)],maxPrice = 500 和 maxCount = 2 此方法仅 returns (500,80) .如果你想要它 return [(200,20), (300,30)],你可以像这样初始化数组:

for (int i = 0; i <= items.Count; i++)
{
    for (int price = 0; price <= maxPrice; price++)
    {
        for (int count = 1; count <= maxCount; count++)
        {
            scores[i, price, count] = int.MinValue;
        }
    }
}

以确保 scores[, , count] 中是 count 分数(或 MinValue)的总和。但是,如果无法准确选择 maxCount,它仍然可以 return 其他数量的项目。