[英]Sort an array so the difference of elements a[i]-a[i+1]<=a[i+1]-a[i+2]
自从我上周开始尝试按条件对N个元素的数组进行排序时,我的思绪就被吹了:2个元素之间的差异总是小于或等于接下来的2个元素。 例如:
Α[4] = { 10, 2, 7, 4}
可以通过这种方式重新排列该数组:
{2, 7, 10, 4}
因为(2 - 7 = -5) < (7 - 10 = -3) < (10 - 4 = 6)
{4, 10, 7, 2}
因为(4 - 10 = -6) < (10 - 7 = 3) < (7 - 2 = 5)
我考虑的一个解决方案就是改组阵列并每次检查它是否符合条件,这是一种有效的方法,适用于少量元素,但对于大量元素而言耗时甚至不可能。
另一个尝试用循环移动数组周围的元素,希望再次满足要求,但这种方法再次非常耗时,有时也不可能。
试图找到一个算法似乎没有任何结果,但必须有一些东西。
非常感谢你提前。
我通常不只是提供代码,但这个问题引起了我的兴趣,所以这里有一个强力解决方案,可能会让你开始。
概念总是很慢,因为要排序的列表中的各个元素彼此不相互独立,因此不能使用传统的O(N log N)算法对它们进行排序。 但是,可以通过这种方式对差异进行分类,这简化了对解决方案的检查,并且可以并行检查排列以加速处理。
import os,sys
import itertools
def is_diff_sorted(qa):
diffs = [qa[i] - qa[i+1] for i in range(len(qa)-1)]
for i in range(len(diffs)-1):
if diffs[i] > diffs[i+1]:
return False
return True
a = [2,4,7,10]
#a = [1,4,6,7,20]
a.sort()
for perm in itertools.permutations(a):
if is_diff_sorted(perm):
print "Solution:",str(a)
break
这种情况与分化有关。 相邻元素之间的(负)差异必须是稳定的或随着指数的增加而增加。 将条件乘以-1
得到
a[i+1]-a[i] => a[i+2]-a[i+1]
要么
0 => (a[i+2]-a[i+1])- (a[i+1]-a[i])
所以二阶导数必须是0或负数,这与一阶导数保持相同或向下变化相同,例如圆的上半部分。 这并不意味着一阶导数本身必须从正面或负面开始,只是它永远不会向上变化。
算法上的问题是它不能简单排序,因为你从不比较列表中的2个元素,你必须一次比较三个(i,i + 1,i + 2)。
所以除了随机排列之外你唯一知道的就是Klas的答案(价值首先上升,如果有的话,然后下降,如果有的话),但他不是一个充分的条件,因为你可以在他的两套中有一个正的二阶导数(上升/下降)。
那么有一个解决方案比随机shuffle快得多吗? 我只能想到以下论点(类似于Klas的回答)。 对于给定的向量,如果将数据分成上升或稳定(不下降)的第一段和下降或稳定(未上升)的第二段并且两者都不为空,则更有可能采用该解决方案。 可能会争论说两个部分的大小应该大致相等。 上升段应该具有更靠近的数据,下降段应该包含更远的数据。 因此,可以从均值开始,寻找接近它的数据,将它们移动到第一组,然后寻找更宽间距的数据并将它们移动到第二组。 所以直方图可能有所帮助。
[4 7 10 2] - > diff [3 3 -8] - > 2diff [0 -11]
这是一个基于回溯算法的解决方案。
下面是Python实现(它并不完美,最糟糕的缺陷是递归实现:虽然递归对于回溯算法很常见,但这种特殊算法似乎在线性时间内工作,而递归对于非常大的输入数组来说并不好)。
def is_concave_end(a, x):
return a[-2] - a[-1] <= a[-1] - x
def append_element(sa, halves, labels, which, x):
labels.append(which)
halves[which].append(x)
if len(labels) == len(sa) or split_to_halves(sa, halves, labels):
return True
if which == 1 or not is_concave_end(halves[1], halves[0][-1]):
halves[which].pop()
labels.pop()
return False
labels[-1] = 1
halves[1].append(halves[0][-1])
halves[0].pop()
if split_to_halves(sa, halves, labels):
return True
halves[1].pop()
labels.pop()
def split_to_halves(sa, halves, labels):
x = sa[len(labels)]
if len(halves[0]) < 2 or is_concave_end(halves[0], x):
return append_element(sa, halves, labels, 0, x)
if is_concave_end(halves[1], x):
return append_element(sa, halves, labels, 1, x)
def make_concave(a):
sa = sorted(a, reverse = True)
halves = [[sa[0]], [sa[0], sa[1]]]
labels = [0, 1]
if split_to_halves(sa, halves, labels):
return list(reversed(halves[1][1:])) + halves[0]
print make_concave([10, 2, 7, 4])
生成一个好的数据集来测试这个算法并不容易:普通的随机数集对于这个算法来说太简单了,或者没有任何解决方案。 在这里,我试图通过将两个排序列表混合在一起来生成一个“足够困难”的集合,每个列表都满足“差异”条件。 该数据集仍以线性时间处理。 我不知道如何准备任何数据集来证明这种算法的时间复杂度超过线性...
不是因为差异应该不断上升,所以任何解决方案都将首先按升序排列,然后按降序排列。 两个“子序列”中任一个的长度可以是0,因此解决方案可以包括严格上升或严格下降的序列。
以下算法将找到任何解决方案:
将集合分为两组,A和B.允许空集。
按升序排序A,按降序排序B.
连接两个有序集:AB
检查您是否有解决方案。
对所有可能的划分为A和B执行此操作。
扩展@ roadrunner66分析,解决方案是采用原始数组的两个最小元素,并使它们在目标数组中首先和最后; 取两个下一个最小的元素,使它们成为第二个,倒数第二个; 继续前进,直到所有元素都放入目标。 请注意哪一个向左移动,哪一个向右移动无关紧要。
对原始数组进行排序有利于该过程(找到最小的元素变得微不足道),因此时间复杂度为O(n log n)
。 空间复杂度为O(n)
,因为它需要目标数组。 如果可以在现场进行,我不知道副手。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.