Python 重新排列列表以最大化相邻元素的差异

Python 重新排列列表以最大化相邻元素的差异,python,algorithm,permutation,Python,Algorithm,Permutation,我感兴趣的是以这样一种方式重新排列列表,以最大化相邻元素之间差异的平方和(循环)。下面是一段Python代码,它在阶乘时间内强制执行解决方案,因此您可以了解我的意思: def maximal_difference_reorder(input): from itertools import permutations best_sum = 0 best_orderings = [] for x in permutations(input): d = np

我感兴趣的是以这样一种方式重新排列列表,以最大化相邻元素之间差异的平方和(循环)。下面是一段Python代码,它在阶乘时间内强制执行解决方案,因此您可以了解我的意思:

def maximal_difference_reorder(input):

   from itertools import permutations

   best_sum = 0
   best_orderings = []

   for x in permutations(input):
        d = np.sum(np.diff(x)**2) + (x[0] - x[-1])**2
        if d > best_sum:
            best_orderings = [x]
            best_sum = d
        elif d == best_sum:
            best_orderings.append(x)

   return best_orderings
对于
最大差异重排序(范围(4))
,这将产生以下结果:

如您所见,所有结果都是循环旋转和相互反射。如果分数是用差值之和而不是平方来确定的,我相信所有的排列都会得到均匀的分数,给定一个均匀间隔的输入

暴力强迫很有效,但O(n!)很糟糕,所以有可能在较小的渐近计算时间内完成吗?如果它适用于不均匀的输入网格或其他计分功能,则可获得额外积分


顺便说一句,这不是家庭作业或面试问题,尽管这可能是个好问题。相反,我试图为一系列参数化数据生成一个颜色光谱,并试图避免颜色彼此相邻。

您的问题只是一个稍微掩饰的例子

调用输入列表
c
(用于“城市”)。选择任何
M
,这是
(c[i]-c[j])**2的上限。这很容易在线性时间内完成,因为列表的最小值和最大值可以在一次传递中计算,在这种情况下
M=(max-min)**2
起作用。通过以下方式定义从
c[i]
c[j]
的距离
d[i,j]

d(i,j) = 0 if i == j else M - (c[i]-c[j])**2
很容易看出,对于任何循环置换,该置换的成本(根据
d
计算)是
n*M-差平方和
,因此,当且仅当差平方和最大化时,该成本最小化

有很多方法可以解决TSP问题。尽管它是NP难的,但在实践中,最先进的方法在解决实践中出现的问题方面表现得非常出色。此外,好的启发式方法通常可以达到最优解的百分之一以内

您的特定问题是TSP的一个特例。因此,这种特殊情况可能更容易,而且实际上有一个多项式时间解,但我对此表示怀疑。我猜想它也是NP难的,但没有证据。而且——即使它是NP难的,也可能有一个解决方案(也许是一个整数规划公式)比将其简化为上面提到的TSP更有效


关于编辑:根据Dave Gavin的评论和@SergeBallesta的回答,我现在认为多项式时间算法是可能的。我将保留这个答案,如果没有其他原因,除非多项式时间算法有效,那么这个问题将是一个很好的例子,表明TSP的某些子类有更简单的解决方案

如果您试图以循环方式最大化连续元素之间差异的平方,我会说您应该尝试使最大元素接近最小元素,因为从概念上讲
a²+b²>=2*((a+b)/2)²
。这就是你用蛮力在
范围(4)
中发现的

我认为可以通过归纳法来说明这一点,但这一部分应该问得更好,但我敢打赌,解决方案只是:

  • 对列表排序
  • 取最大元素,将其放入结果列表的索引0中
  • 取最小的一个放在索引1上
  • 取剩余的最小值并将其放在索引-1上
  • 取剩余的最大值并将其放在索引2上
并在右边和左边交替迭代一次剩余元素的最大值和最小值

你的结局是:

  • O(n*log(n))使用快速排序或合并排序的排序统计,或O(n²/2)仅使用冒泡排序
  • 用于构建结果数组的线性
    • 我想我们可以有一个O(n)解决方案

      解决这个问题的关键是为循环群生成第一个种子。考虑到我们应该配对元素,其中成对平方差和最大,如果我们将一个元素与其最远的邻居配对,这是可能的

      也就是说,如果hi是第i个最大的数,那么hi的邻居是(hn-i-1,hn-i+1)。由于序列是循环的,因此数字将围绕负指数,即h-1=h0 这将生成第一个种子列表作为
      [0,6,2,4,3,5,1,7]

      通过交换每个奇数索引对,即[(a1,an-1),(a3,an-3),…]可以很容易地生成该序列

      随后的序列可以通过生成一个奇异的顺序旋转,然后反射旋转后的序列来生成

      下面是一个示例实现

      def maximal_difference_reorder1(x):
          def maximal_difference_seeder(x):
              for i in range(1, len(x) / 2):
                  x[i:len(x) - i] = x[i:len(x) - i][::-1]
              return x
          def rotate_left(x):
              start = x
              while True:
                  x = x[1:] + x[0:1]
                  if x == start: break
                  yield x
      
          x = maximal_difference_seeder(x)
          rotated = [x] + (list(rotate_left(x)) if len(x) > 1 else [])
          reflected = [e[::-1] for e in rotated] if len(x) > 2 else []
          return map(tuple, rotated + reflected)  
      
      样本运行

      def display(lst, width = 80):
          it_lst = iter(lst)
          try:
              print '[',
              while True:
                  for _ in range(80/(len(lst[0])*3 + 2)):
                      print "{},".format(next(it_lst)), 
                  print '\n ',
          except StopIteration:
              print ']'
      
      display(maximal_difference_reorder1(range(10)))
      
      [ (0, 8, 2, 6, 4, 5, 3, 7, 1, 9), (8, 2, 6, 4, 5, 3, 7, 1, 9, 0), 
        (2, 6, 4, 5, 3, 7, 1, 9, 0, 8), (6, 4, 5, 3, 7, 1, 9, 0, 8, 2), 
        (4, 5, 3, 7, 1, 9, 0, 8, 2, 6), (5, 3, 7, 1, 9, 0, 8, 2, 6, 4), 
        (3, 7, 1, 9, 0, 8, 2, 6, 4, 5), (7, 1, 9, 0, 8, 2, 6, 4, 5, 3), 
        (1, 9, 0, 8, 2, 6, 4, 5, 3, 7), (9, 0, 8, 2, 6, 4, 5, 3, 7, 1), 
        (9, 1, 7, 3, 5, 4, 6, 2, 8, 0), (0, 9, 1, 7, 3, 5, 4, 6, 2, 8), 
        (8, 0, 9, 1, 7, 3, 5, 4, 6, 2), (2, 8, 0, 9, 1, 7, 3, 5, 4, 6), 
        (6, 2, 8, 0, 9, 1, 7, 3, 5, 4), (4, 6, 2, 8, 0, 9, 1, 7, 3, 5), 
        (5, 4, 6, 2, 8, 0, 9, 1, 7, 3), (3, 5, 4, 6, 2, 8, 0, 9, 1, 7), 
        (7, 3, 5, 4, 6, 2, 8, 0, 9, 1), (1, 7, 3, 5, 4, 6, 2, 8, 0, 9), 
        ]
      

      注意假设数据已排序。如果不是,那么对其进行排序就很简单了,其中解决方案的复杂性为O(nlog n)

      以下是我在评论中建议的贪婪算法:

      贪婪算法怎么样?在每一步中,预加或附加数字以最大程度地增加分数(这将形成一个先增加后减小振幅的锯齿波)。尝试从最小的数字或最大的数字开始

      研究贪婪算法不是最优算法的例子会很有趣

      # https://stackoverflow.com/questions/34154324/reordering-a-list-to-maximize-difference-of-adjacent-elements?s=2|0.0000
      import itertools
      
      def score(x):
          return sum((a-b)**2 for a,b in zip(x, x[1:]))
      
      assert score([0, 2, 5]) == 4 + 9
      
      def maximise(x):
          x = sorted(x)
          candidates = [greedy(x[:1], x[1:]), greedy(x[-1:], x[:-1])]
          return max(candidates, key=score)
      
      def greedy(current, remaining):
          while remaining:
              i, j = max(itertools.product((0, -1), repeat=2), key=lambda pair: score((current[pair[0]], remaining[pair[1]])))
              current.insert(i, remaining.pop(j))
      
          return current
      
      def cyclic_score(x):
          return sum((a-b)**2 for a,b in zip(x, x[1:] + x[:1]))
      
      assert cyclic_score([0, 2, 5]) == 4 + 9 + 25
      

      序列0,n-1,1,n-2,2,n-3,…,不保证范围(n)的最大平方差吗。。。?例如,你的0,3,1,2序列,例如0,4,1,3,2(5)等。我认为他在谈论一个随机数的列表。比如[n,2n+1,35n+1,x,y…]。然后你会得到66分,最大值实际上是77分。问题是,如果你能重复/省略不同的数字,那么你是围绕最大的数字还是最小的数字。。。我说我指的是范围(n),就像你在例子中使用的那样。@barny是的,这对sp是正确的
      # https://stackoverflow.com/questions/34154324/reordering-a-list-to-maximize-difference-of-adjacent-elements?s=2|0.0000
      import itertools
      
      def score(x):
          return sum((a-b)**2 for a,b in zip(x, x[1:]))
      
      assert score([0, 2, 5]) == 4 + 9
      
      def maximise(x):
          x = sorted(x)
          candidates = [greedy(x[:1], x[1:]), greedy(x[-1:], x[:-1])]
          return max(candidates, key=score)
      
      def greedy(current, remaining):
          while remaining:
              i, j = max(itertools.product((0, -1), repeat=2), key=lambda pair: score((current[pair[0]], remaining[pair[1]])))
              current.insert(i, remaining.pop(j))
      
          return current
      
      def cyclic_score(x):
          return sum((a-b)**2 for a,b in zip(x, x[1:] + x[:1]))
      
      assert cyclic_score([0, 2, 5]) == 4 + 9 + 25