【问题标题】:Minimizing the sum of a special function over a list最小化列表中特殊函数的总和
【发布时间】:2009-01-06 15:19:50
【问题描述】:

假设我有一个列表,我希望对它进行排列,以便在其连续元素上运行的某个函数的总和最小。

例如,考虑列表 { 1, 2, 3, 4 } 和 sum a^b 的连续对 (a,b) 在整个列表中。 IE。 1^2 + 2^3 + 3^4 = 90。经检查,列表排列为{ 2, 3, 1, 4 } => (2^3 + 3^1 + 1^4 = 12时达到最小总和。

请注意,总和不是循环的(即我不认为last^first)并且顺序很重要(2^3 != 3^2) 并且a^b 可以是对任意数量的连续元素进行操作的任何函数。

这种算法有名称吗?是否有既定的实现方法?

编辑:我已经改写了这个问题,因为我错误地将其标记为排序问题。正如所指出的,这更像是一个优化问题。

【问题讨论】:

  • + 1 表示非常晦涩的问题。我无法想象有答案,但会回来看看。
  • 我是不是看错了什么? 1^2 + 2^3 + 3^4 = 90,不是 91...
  • 不,你是对的。现在是 90 岁,但这并不是一个真正重要的问题 ;)
  • @Binary Worrier:我(不幸地)想问的是:当您需要考虑整个列表而不是逐个比较元素时,如何对列表进行排序? @balabaster:对。谢谢。
  • 我不认为这是一个排序的事情 - 这是一个优化的事情。您正在对列表的所有排列优化一个函数。

标签: algorithm language-agnostic optimization


【解决方案1】:

“排序”通常使用二进制比较运算符(“小于或等于”)定义。您正在寻找的是列表的“最佳”排列,其中“最佳”被定义为在整个列表上定义的标准(而“某些函数”是在相邻元素上定义的,整个列表的总和使其成为全局属性)。

如果我理解正确,“旅行推销员”就是你的问题的一个例子,所以你的问题无论如何都是 NP-complete ;-)

【讨论】:

  • 如果 function = "a 和 b 城市之间的距离" 那么是的,这是旅行商问题!
  • 更准确地说,给定任何 TSP 实例,您可以通过在此处将“函数”定义为 TSP 实例中的相应距离,使其成为该问题的实例。因此,TSP 减少了这个问题。
【解决方案2】:

因为对使用的功能没有限制

a^b 也可以是对任意数量的连续元素进行操作的任何函数。

如果使用了一个常量函数(比如一个 alwasys 返回 1 的函数),那么所有排序的总和将是相同的,但在查看所有排序之前,您不一定知道这一点。

所以我看不出比在所有排列上评估函数加和更快的方法了。

(你可以记住每个元组的结果以加快评估速度,但我认为你仍然需要全部查看)

编辑:另外,因为它可能是一个作用于所有元素的函数,所以你可以有一个函数对所有排列返回 0,除了一个,它返回 1。

因此,对于一般情况,您肯定需要对所有排列的函数进行评估。

【讨论】:

  • 很好的解释,谢谢。我只是想,由于我的距离函数正在处理连续元素,因此可能有一种聪明的方法可以将其简化为更简单的问题,而不是强制所有排列。
【解决方案3】:

这似乎是Optimization Probelm,而不是排序问题。

我敢打赌,只要做一点(或者可能很多)工作,就会有人证明这在功能上等同于著名的 NP 完全问题之一。但是,对于某些特定功能(例如您的示例中的 a^b ),问题可能会更容易。

【讨论】:

    【解决方案4】:

    这是家庭作业吗?

    如果不是,那就是动态规划问题。要查看它,您应该使用您的示例作为基础将您的问题转换为以下问题。你在开始。您可以选择 {1,2,3,4} 之一。从那里您可以选择前往 {1,2,3,4}。这样做 4 次,您就拥有了列表 {1,2,3,4} 长度为 4 的所有排列。

    现在你需要一个成本函数,定义为:

    f(prev, next) = prev ^ next
                  = 0 if the solution is not valid for your original problem 
                  = 0 if prev is the start
    

    总成本表示为

    cost(i|a|X) = min(i in {1,2,3,4}, f(i, a) + cost(X))
    

    请注意,i|a|X 表示一个以元素 a 开头的列表,然后是 i,列表的其余部分是 X。

    查看cost 函数,您应该认识到动态规划。

    从那里你可以推导出一个算法。查看维基百科以获取 introduction to dynamic programming

    您可以使用 PLT Scheme 测试的我的 Scheme 实现是:

    (define (cost lst f)
      (if (null? lst)
          0
          (let ((h (car lst))
                (t (cdr lst)))
            (if (null? t)
                0
                (+ (f h (car t))
                   (cost t f))))))
    
    (define (solve lst f)
      (let loop ((s '()))
        (if (= (length s) (length lst))
            s
            (loop
             (let choose ((candidate lst)
                          (optimal #f)
                          (optimal-cost #f))
               (if (null? candidate)
                   optimal
                   (let ((c (car candidate)))
                     (if (memq c s)
                         (choose (cdr candidate) optimal optimal-cost)
                         (if (not optimal) 
                             (choose (cdr candidate) (cons c s) (cost (cons c s) f))
                             (if (<= (cost (cons c s) f)
                                     (cost optimal f))
                                 (choose (cdr candidate) (cons c s) (cost (cons c s) f))
                                 (choose (cdr candidate) optimal optimal-cost)))))))))))
    

    然后调用(solve '(1 2 3 4) expt) 会产生另一个最小解'(3 2 1 4)。

    【讨论】:

    • 除了“动态编程”之外,您可能想探索的另一个词是“约束”的概念。
    • 这只是标准的 (n^2)(2^n) DP。顺便说一句,您可以从代码中减少 n 倍。
    • 这适用于 (1 2 3 4)。但是当我尝试 (1 2 3 4 5) 时,它返回 (4 3 2 1 5),成本为 76。但是,存在更好的解决方案:(4 2 3 1 5),成本为 28。
    【解决方案5】:

    我只看到一个解决方案:

    蛮力

        public static int Calculate(Func<int, int, int> f, IList<int> l)
        {
            int sum = 0;
            for (int i = 0; i < l.Count-1; i++)
            {
                sum += f(l[i], l[i + 1]);
            }
            return sum;
        }
    
        public static IEnumerable<IEnumerable<T>> Permute<T>(IEnumerable<T> list, int count)
        {
            if (count == 0)
            {
                yield return new T[0];
            }
            else
            {
                int startingElementIndex = 0;
                foreach (T startingElement in list)
                {
                    IEnumerable<T> remainingItems = AllExcept(list, startingElementIndex);
    
                    foreach (IEnumerable<T> permutationOfRemainder in Permute(remainingItems, count - 1))
                    {
                        yield return Concat<T>(
                            new T[] { startingElement },
                            permutationOfRemainder);
                    }
                    startingElementIndex += 1;
                }
            }
        }
    
        // Enumerates over contents of both lists.
        public static IEnumerable<T> Concat<T>(IEnumerable<T> a, IEnumerable<T> b)
        {
            foreach (T item in a) { yield return item; }
            foreach (T item in b) { yield return item; }
        }
    
        // Enumerates over all items in the input, skipping over the item
        // with the specified offset.
        public static IEnumerable<T> AllExcept<T>(IEnumerable<T> input, int indexToSkip)
        {
            int index = 0;
            foreach (T item in input)
            {
                if (index != indexToSkip) yield return item;
                index += 1;
            }
        }
    
        public static void Main(string[] args)
        {
            List<int> result = null;
            int min = Int32.MaxValue;
            foreach (var p in Permute<int>(new List<int>() { 1, 2, 3, 4 }, 4))
            {
                int sum = Calculate((a, b) => (int)Math.Pow(a, b), new List<int>(p));
                if (sum < min)
                {
                    min = sum;
                    result = new List<int>(p);
                }
            }
            // print list
            foreach (var item in result)
            {
                Console.Write(item);
            }
        }
    

    我从Ian Griffiths blog 窃取了排列代码。

    【讨论】:

      【解决方案6】:

      这绝对不是一个排序列表。如果您有一个排序列表 [x~0~...x~n~],则列表 [x~0~..x~i-1~, x~i+1~..x~n~] (即 x~i~ 被移除)也将根据定义进行排序。在您的示例中,从子序列 100,0,100 中删除 0 很可能会取消对列表的排序。

      【讨论】:

        【解决方案7】:

        这是我目前所拥有的。我创建了一个 Calc 类,我可以将每个组合传递到该类中,然后它计算总数并具有 ToString() 方法,因此您不必担心迭代输出总和字符串和值。您可以获取构造函数中传入的总数和列表。然后,您可以将每个组合集添加到一个列表中,您可以在 inst.Total... 中按 LINQ 对其进行排序...正如我所展示的。仍在研究生成每种组合的方法...

        class Calc
        {
            private int[] items;
            private double total;
            public double Total 
            { 
                get
                { 
                    return total; 
                } 
            }
            public int[] Items
            {
                get { return items;  }
                set { total = Calculate(value); }
            }
            public static double Calculate(int[] n)
            {
                double t = 0;
                for (int i = 0; i < n.Length - 1; i++)
                {
                    int a = n[i]; int b = n[i + 1];
                    t += a^b;
                }
                return t;
            }
            public Calc(int[] n)
            {
                this.items = n;
                this.total = Calculate(n);
            }
            public override string ToString()
            {
                var s = String.Empty;
                for (int i = 0; i < items.Length - 1; i++)
                {
                    int a = items[i]; int b = items[i + 1];
                    s += String.Format("{0}^{1}", a, b);
                    s += i < items.Length - 2 ? "+" : "=";
                }
                s += total;
                return s;
            }
        }
        

        然后我们在计算中使用该类,并很快按每个排列的总数排序:

        class Program
        {
            static void Main(string[] args)
            {
                var Calculations = new List<Calc>();
        
                ////Add a new item to totals for every combination of...working on this
                Calculations.Add(new Calc(new int[] { 1, 2, 3, 4 }));
                //...
        
                //Grab the item with the lowest total... if we wanted the highest, we'd
                //just change .First() to .Last()
                var item = Calculations.OrderBy(i=>i.Total).First();
                Console.WriteLine(item);
                //Or if we wanted all of them:
                //Calculations.OrderBy(i=>i.Total).ForEach(Console.WriteLine);
            }
        }
        

        【讨论】:

        • 你可以保持一个迄今为止最好的总数,然后如果 t 大于这个值,就退出计算的内部循环。
        • 你可以,而且它对于精确计算的表现最好 - 但是能够选择你抓取的而不是放弃所有其他的增加了很大的灵活性。
        【解决方案8】:

        这是一个 NP 完全问题,因为算法是未知的(NB 对于给定的函数 a^b 它不是 NP 完全的,它可以通过排序后的单遍来完成,参见下面的解决方案示例)

        如果不为列表的所有可能排列计算给定函数的结果,就无法在一般情况下编写“排序算法”。

        但是,一旦给定一个函数,您就可以(可能)为此设计一个排序方法,例如对于上面的 a^b,因此对列表进行排序(不对任何项目应用该函数)“Max, min, next max, next min 。 . .” 并颠倒该顺序。

        根据给定函数的复杂性,提供优化的排序例程将越来越困难。

        谢谢,

        【讨论】:

        • 当时我确信这不是一个 NP 完全问题。因为我已经删除了反对票。希望您的代表已相应调整。对不起...
        • 此外,根据应用的特定功能(OP 的一般问题),此问题可能不是 NP 完全的。
        • @Gregg:我同意,我的原始答案中没有明确说明,所以我稍微编辑了答案。谢谢
        • @Pierre:谢谢,我总是发表评论说我为什么不投票(如果那里没有足够的评论),稍后再回来看看我是否可以删除它。再次感谢队友
        【解决方案9】:

        嗯,这是一个有趣的问题。使用现有的排序结构 (IComparable) 效果不佳,因为您需要的信息比 compareTo 方法可用的信息多。

        对此的解决方案将在很大程度上取决于您要用于排序的方法是什么。但是,乍一看,您可能必须遍历所有可能的订单才能找到最小订单。如果当前总和大于先前的总和,您可能会将其短路。

        我得试一试,看看我能想出什么。

        【讨论】:

          【解决方案10】:

          未经测试和未经证实:

          1. 对列表进行排序
          2. 从排序列表创建对,获取第一个数字和最后一个数字,然后是第二个和第二个到最后一个,等等(即 1,2,3,4 变为 1,4 和 2,3)
          3. 对于每一对,保存它的 a^b 值。对该列表进行反向排序
          4. 用您的 a & b 值替换新列表中的 a^b 值

          我猜它不可能那么简单......但它适用于您的示例,这不是真正重要的吗?

          【讨论】:

          • 不,问题实际上是关于通用案例的。第 3 点的一部分也应阅读“反转此列表”,而不是“反转排序此列表”。此外,您不需要第 3 点的前半部分,也根本不需要第 4 点。
          猜你喜欢
          • 2016-06-03
          • 2016-09-11
          • 2019-08-07
          • 1970-01-01
          • 2013-02-10
          • 2011-07-07
          • 1970-01-01
          • 2023-03-24
          • 1970-01-01
          相关资源
          最近更新 更多