0

SO上有很多子集问题和答案,但不知何故,我找不到针对我的具体问题的解决方案。

我需要找到轨道段的数量和长度来构建长度为 n 的轨道。段的长度:8、10、12、14、16、18、20、22、24 英尺 数量最多可达 4 段 轨道长度约为 20 到 100 英尺(并且总是偶数)

这是一条真正的赛道。段的顺序无关紧要。然而,有优选的尺寸组合。所有长度相等或彼此接近的都比大/小组合更可取。

IE:

  • 70 => 20,20,20,10 很容易选择,但首选 16,18,18,18
  • 60 => 20,20,20 优于 14,14,14,18

如果需要,我可以举出更多例子。

我不是在寻找一个最佳解决方案,而是寻找一小组可能的最佳解决方案。最终一个人会选择,这是关于建议最佳选择。

到目前为止,我所做的如下。它正在工作,只是看起来很复杂。

我从这篇文章Algorithm 中获取了基本算法,以查找大小为 n 的列表中的哪些数字总和到另一个数字。我在这段代码中所做的只是把它变成整数。它返回所有可能的组合。最多 5 个或更多曲目。

为了进一步减少结果集,我做了一些 Linq

    List<int> nums = new List<int>() { 8, 8, 8, 8, 10, 10, 10, 10, 12, 12, 12, 12, 14, 14, 14, 14, 16, 16, 16, 16, 18, 18, 18, 18, 20, 20, 20, 20, 22, 22, 22, 22, 24, 24, 24, 24 };
    int width = 60;
    Console.WriteLine("Total width: " + width);
    Solver solver = new Solver();
    List<List<int>> res = solver.Solve(width, nums.ToArray());

    Console.WriteLine("total :"+res.Count);
    var res1 = res.Distinct(new SequenceComparer<List<int>, int>()).ToList();
    Console.WriteLine("total res1:" + res1.Count);

    var res2 = res1.Where(l => l.Count == 4).ToList();
    Console.WriteLine("total res2:" + res2.Count); //reduce to 4 integer solutions

    var res3 = (from row in res2
             where row[0] == row[1] || row[0] == row[2] || row[0] == row[3] ||
                   row[1] == row[2] || row[1] == row[3] ||
                   row[2] == row[3]
             select row).ToList();
    Console.WriteLine("total res3:" + res3.Count); //reduce to at least two of equal length

    var res4 = (from row in res3
            where row[0] == row[1] && row[0] == row[2] || 
                  row[0] == row[1] && row[0] == row[3] ||
                  row[1] == row[2] && row[1] == row[3] 
            select row).ToList();
     Console.WriteLine("total res4:" + res4.Count); //reduce to  three of equal length

    var res5 = (from row in res4
            where row[0] == row[1] && row[0] == row[2] && row[0] == row[3]
           select row).ToList();

     Console.WriteLine("total res5:" + res5.Count); //reduce to 4 of equal length
     Console.WriteLine("-------------------------------------");
     Console.WriteLine("res4:");
     foreach (List<int> result in res4)
     {
         foreach (int value in result)
         {
              Console.Write("{0}\t", value);
         }
         Console.WriteLine();
      }
      Console.WriteLine("-------------------------------------");
      Console.WriteLine("res5:");
      foreach (List<int> result in res5)
      {
           foreach (int value in result)
           {
               Console.Write("{0}\t", value);
           }
           Console.WriteLine();
      }
      Console.ReadLine();

此代码将产生以下结果,以 60 运行

    Total width: 60
    total :10726
    total res1:74
    total res2:31
    total res3:20
    total res4:3
    total res5:0
    -------------------------------------
    res4:
    12      12      12      24
    12      16      16      16
    14      14      14      18
     -------------------------------------
    res5:

或 80 这个

    Total width: 80
    total :101560
    total res1:237
    total res2:15
    total res3:13
    total res4:3
    total res5:1
    ------------------------------------
    res4:
    8       24      24      24
    14      22      22      22
    20      20      20      20
    ------------------------------------
    res5:
    20      20      20      20

所以我的最终结果(4&5)实际上接近我所需要的。

但是我必须为任何可能的 3 轨道解决方案再次编写相同的代码,也许还有 2 轨道。

那么结果是否需要相互比较(不知何故,不确定如何)。这一切让我觉得我错过了什么。感觉很复杂,感觉不对。我错过了什么?

我开始使用错误的算法吗?一旦解决了我的问题,他们会更好吗?

4

3 回答 3

2

您确实有求和集的特殊情况。虽然它是 NP 难的,但解决方案空间受到足够的限制,以至于蛮力可能会正常工作,因为总共只有 10000 (10 ^ 4) 个可能的解决方案(大约等于所需的实际时间步数),因为你还必须考虑 0 作为可能的长度。

这是psudo-Python中的代码。考虑在 C# 中尝试它,但实际上并不熟悉它,因此它可能不会很好地工作。

lengths = [0, 8, 10, 12, 14, 16, 18, 20, 22, 24]
solutions = []
for len1 in lengths:
    for len2 in lengths:
        for len3 in lengths:
            for len4 in lengths:
                if (len1 + len2 + len3 + len4) == n:
                    solutions.append([len1,len2,len3,len4])
return solutions

一旦你有了所有有效的解决方案,你就可以简单地确定你想向用户展示哪些,或者你可以简单地编写一个算法来选择最好的一个。当然,您可能不希望实际包含任何大小为 0 的长度。

您可以使用只会找到所有有效解决方案的贪心方法来稍微改进此算法。然而,再一次,除非事情在空间或时间方面非常有限,否则它所说的问题不太可能复杂到需要它。

作为奖励,如果您期望多个查询(例如用户要求 n = 40 和后来的 n = 50),您可以删除 if 语句并将所有 10000 个解决方案简单地存储在以总和值为键的哈希表中,允许用于 O(1) 查询。

缩小解决方案集:

您在这里需要的是一个比较算法,它基本上将这个解决方案与那个解决方案进行比较,并说“这个解决方案比那个解决方案更好/最差”。这允许您编写一个排序算法,您可以使用该算法对解决方案进行排序以获得最佳解决方案,或者您可以简单地找到最佳解决方案。

您可以通过简单地计算每个解决方案的标准偏差然后比较标准偏差解决绝大多数情况。这将给出一个数字,显示解决方案长度的差异程度。如果您使用“较低的标准偏差更好”,那么这将为您提供“所有长度相等或彼此接近的人比大/小组合更受欢迎”。标准差的算法相当简单,所以我将把它留给你尝试实现。实际上,C# 很有可能内置了该函数。只要确保不包含任何零长度,实际上可能应该在将它们添加到解决方案集之前将它们删除以避免出现问题,这需要对我给的代码。

但是,您将遇到棘手的部分,即处理不同解决方案具有相同标准偏差的情况。我想只有两种情况会发生这种情况。

  1. 第一个仅出现多个理想解决方案。例如如果 n = 24,则三个解将是 [8,8,8]、[12,12] 和 [24]。

  2. 第二个是由于算法的蛮力性质而发生的,这就是为什么有这么多解决方案。这是因为对于像 [8,10,12,14](四个唯一长度)这样的每个解决方案,有 24 种方法来排列这些长度,例如 [14,12,10,8] 和 [12,10,14,8 ]。因此,改进蛮力算法的最佳方法是使用从 [0, 8, 10, 12, 14, 16, 18, 20, 22, 24] 中多选 4 个元素的算法。这将解决方案集缩小到只有 715 个解决方案。当然,如果您真的想要 [8,10,12,14]、[14,12,10,8] 和 [12,10,14,8] 作为不同的解决方案,那么您无能为力。

上述两段完全属于“视情况而定”的范畴。您必须决定算法在每种情况下应遵循哪些规则,但我认为这是您可以找到相同标准偏差的仅有的两种情况。

于 2013-01-16T11:03:11.650 回答
2

让我们将所有内容除以 2,因为所有内容都是偶数。我们现在有长度为 4 到 12 的轨道部件,总长度约为 10 到 50。

将n命名为我们必须达到的长度。对于每个可能的k个轨道段(一般为 1 到 4,但n <16 时为 1 到 3 或n > 36 时为 3 到 4),建议采用长度为n/k +1 和kn的n%k段%k个长度为n/k的片段。

'/' 表示整数除法,'%' 表示余数。

于 2013-01-16T14:33:53.540 回答
1

数学来拯救!

您可以检查每个大于 8 的偶数是否是该集合中元素的线性组合 - 请在 Math Overflow 上寻求证明;)。

所以让我们用数学重新表述这个问题:

  • 我们有一个超完备的基向量字典(例如,因为 16 是 8 的倍数),
  • 其中我们可以将每个大于 8 的偶数表示为这些基向量的线性组合,并且
  • 我们试图最小化这个输入向量的零“范数”

好消息:这是一个非常有趣的问题,有很多应用领域,所以研究得很好

坏消息:这仍然是一个难(NP-hard)问题。

但是,嘿,至少现在你知道了。

编辑: 为了不让我被指责挥手哲学答案,这是一个修改(完全非优化)的版本,Solver.recursiveSolve它详尽地搜索与目标匹配的段组合;和一个零范数比较器类,您可以使用它对结果进行排序:

    private void RecursiveSolve(int goal, int currentSum,
        List<int> included, List<int> segments, int startIndex)
    {

        for (int index = 0; index < segments.Count; index++)
        {
            int nextValue = segments[index];
            if (currentSum + nextValue == goal)
            {
                List<int> newResult = new List<int>(included);
                newResult.Add(nextValue);
                mResults.Add(newResult);
            }
            else if (currentSum + nextValue < goal)
            {
                List<int> nextIncluded = new List<int>(included);
                nextIncluded.Add(nextValue);
                RecursiveSolve(goal, currentSum + nextValue,
                    nextIncluded, segments, startIndex++);
            }
        }
    }

class ZeroNormAndSDComparer : IComparer<List<int>>
{
    private int l0_norm(List<int> v)
    {
        int norm = 0;
        HashSet<int> seen = new HashSet<int>();

        for (int i = 0; i < v.Count; ++i)
        {
            if (!seen.Contains(v[i]))
            {
                norm++;
                seen.Add(v[i]);
            }
        }
        return norm;
    }

    private static double StandardDeviation(List<int> v)
    {
        double M = 0.0;
        double S = 0.0;
        int k = 1;
        foreach (double value in v)
        {
            double tmpM = M;
            M += (value - tmpM) / k;
            S += (value - tmpM) * (value - M);
            k++;
        }
        return Math.Sqrt(S / (k - 1));
    }

    public int Compare(List<int> x, List<int> y)
    {
        int normComparison = l0_norm(x).CompareTo(l0_norm(y));
        if  (normComparison == 0)
        {
            return StandardDeviation(x).CompareTo(StandardDeviation(y));
        }
        return normComparison;
    }
}

并且您修改后的 Main (现在排序是在结果减少到不同的 4 段结果后完成的):

        List<int> nums = new List<int>() { 8, 10, 12, 14, 16, 18, 20, 22, 24 };

        int width = 80;
        Console.WriteLine("Total width: " + width);

        Solver solver = new Solver();
        List<List<int>> res = solver.Solve(width, nums.ToArray());
        Console.WriteLine("total :" + res.Count);
        var res1 = res.Distinct(new SequenceComparer<List<int>, int>()).ToList();
        Console.WriteLine("total res1:" + res1.Count);

        var res2 = res1.Where(l => l.Count == 4).ToList();
        Console.WriteLine("total res2:" + res2.Count); //reduce to 4 integer solutions

        res2.Sort(new ZeroNormAndSDComparer());
于 2013-01-16T10:45:08.943 回答