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

时间:2015-12-08 11:10:22

标签: 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.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

这会产生maximal_difference_reorder(range(4))的以下结果:

[(0, 2, 1, 3),
 (0, 3, 1, 2),
 (1, 2, 0, 3),
 (1, 3, 0, 2),
 (2, 0, 3, 1),
 (2, 1, 3, 0),
 (3, 0, 2, 1),
 (3, 1, 2, 0)]

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

暴力强迫效果很好,但是O(n!)很糟糕,所以可以在更小的渐近计算时间内完成这个吗?如果它适用于不均匀的输入网格或其他评分函数,则可获得奖励。

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

4 个答案:

答案 0 :(得分:3)

您的问题是Traveling Salesman Problem的一个稍微伪装的实例。

调用输入列表c(对于" cities")。选择M作为(c[i]-c[j])**2上限的任何M = (max - min)**2这很容易在线性时间内完成,因为列表的最小值和最大值可以一次计算,在这种情况下d[i,j]作品。通过以下方式定义从c[i]c[j]的{​​{1}}距离:

d(i,j) = 0 if i == j else M - (c[i]-c[j])**2

很容易看出,对于任何循环置换,该置换的成本(根据d计算)是n*M - sum of squares of differences因此,当且并且仅差异的平方和为最大化。

解决TSP的方法很多。尽管它是NP难的,但在实践中,最先进的方法在解决实践中出现的问题方面非常擅长。此外,良好的启发式方法通常可以达到最佳百分比的一小部分。

您的特定问题是TSP的特例。因此,这种特殊情况可能更容易,实际上有一个多项式时间解决方案,但我对此表示怀疑。我猜想它也是NP难的,但没有证据。另外 - 即使它是NP难的,也许有一个解决方案(可能是一个整数编程公式)比如上所述将它减少到TSP更有效。

On Edit:根据Dave Gavin的评论和@SergeBallesta的回答,我现在认为可以使用多项式时间算法。我将这个答案留下来,如果没有其他原因,如果多项式时间算法工作,那么这个问题将是一个很好的例子,表明TSP的某些子类有更简单的解决方案。

答案 1 :(得分:2)

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

我认为它可以通过归纳显示出来,但应该在Mathematics上更好地询问这一部分,但我会打赌硬币解决方案只是为了:

  • 对列表进行排序
  • 把最大的元素放在结果列表的索引0中
  • 取最小的一个并将其放在索引1
  • 取剩余的最小值并将其放在索引-1
  • 占据剩余的最大数量并将其置于指数2

并向右和向左迭代一次,交替剩余元素的最大和最小

你结束于:

  • 使用快速排序或合并排序的排序的O(n * log(n))统计数据,或仅使用冒泡排序的O(n²/ 2)
  • 用于构建结果数组的线性

答案 2 :(得分:1)

我认为我们可以有一个O(n)解决方案

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

这意味着如果h i 是i th 最高数,那么h i 的邻居是(h ni- 1 ,h n-i + 1 )。因为序列是循环的,所以数字会环绕负指数,即h -1 = h 0 这将生成第一个种子列表[0, 6, 2, 4, 3, 5, 1, 7]

这个序列很容易通过交换每个奇数索引对来生成,即[(a 1 ,a n-1 ),(a 3 n-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)

答案 3 :(得分:1)

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

  

贪心算法怎么样?在每个步骤中,前置或附加数字以最大程度地增加分数(这将产生增加然后减小幅度的锯齿波)。尝试以最小数字或最大数字开头

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

# 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