现在我最近看到了这个问题(无法准确记住在哪里),通过专门翻转子列表来排序数字列表所需的操作很少。
这是一个例子:
未排序的输入:3, 1, 2, 5, 4
可能的答案之一是:
1.反转索引0到3,给出5, 2, 1, 3, 4
2.反转索引0到4,给出4, 3, 1, 2, 5
3.反转索引0到3,给出2, 1, 3, 4, 5
4.反转索引0到1,给出1, 2, 3, 4, 5
然而,在尝试了解这个问题之后,对于没有经验算法的人来说,实际上创建一段找到最佳解决方案的代码是非常困难的。上面提到的答案只是通过强制执行所有可能的组合来完成,但是当列表长于10个数字时(10次需要<2s,14次花费超过10分钟),这变得无法忍受。重新安装任何现有的排序算法都不起作用,因为它们被构建为一次只交换单个元素(并且通过反转子列表交换2个数字将需要1-2次操作,这根本不是最佳的)。我也尝试过排序网络,因为在运行程序之前已经确定了最大大小,但我也没有太多运气(它们也依赖于交换,当你有能力交换多个时,效率很低)一段时间)。我还尝试制作一个能够优化&#34;一个操作列表,作为尝试使现有的排序算法可行,但这些答案也远远超过最佳答案(想想16对6)。
所以,经过相当长的一段时间后,我找不到更好的方法来找到最简单的解决方案,而不是强制它。由于我是一名学生,我在排序,算法和其他数学艺术方面没有太多经验,但是我想知道这里有没有人可以尝试一下。当然,最好的答案就是给出一个提示,因为我不会想到在StackOverflow周围浮现一些更聪明的思想来解决它。
让我们说所有数字都是唯一的,在0 - 100范围内(均为独家)。数组的长度类似于3 < N < 15
,因为我似乎记得原始问题也没有使用大数组。
答案 0 :(得分:2)
没有什么是最佳的,但在某些类似案例中使用的想法。
一个想法是使用递归并保持之前遇到的“最佳”分数,以避免探索更多无用的组合。
对于长度为n的列表,第一个限制显然是n-1:没有“路径”应该比n-1长,因为存在得分为n-1的普通解决方案。
然后让排序函数多次调用自身,参数为:
每次调用该函数(单独)时,它可以执行大约n²次操作,并通过在路径长度上加1(当然也可以修复其他参数)再次调用自身但是只有当这个增加时长度仍低于最佳分数。
由于使用递归与探索树或多或少类似,如果你足够幸运地找到“好”的解决方案,你将避免探索一些分支。
它适用于尺寸3 < N < 15
,但您肯定能找到更好的效果。
答案 1 :(得分:0)
我找到了一个解决方案,使用较少数量的交换为您的示例:
似乎@greybeard建议的是最好的方法。甚至可能会想到一种基于贪婪+优化函数的启发式算法,这些函数基于“成本”的概念。
例如,对于每个元素,您可以计算它与排序列表中的原始索引的“距离”,并首先反转最高成本列表(最好是最小化后续交换),直到所有内容都已排序。 / p>
当然,还没有真正测试过它,但是,作为一个开始,它可能对你有帮助。
最佳, 布鲁诺
答案 2 :(得分:0)
这是一个使用贪婪方法的想法(未被证明是最优的)。这个想法是在每个步骤中寻找最大程度地增加顺序的回归。让我们将列表的顺序定义为该列表中最长的有序部分的长度,并且如果它按向前或向后排序,则说明列表的一部分是有序的。
E.g。列表[3 4 5 1 2]
有两个有序部分:[3 4 5]
和[1 2]
。从这个状态的最佳移动将使前4个元素恢复它们。结果是列表[1 5 4 3 2]
。新列表的顺序增加了,因为现在最大有序子序列长度为4个元素(它是子序列[5 4 3 2]
)。
如果没有可以增加列表顺序的移动,则更喜欢按正确顺序对列表(的一部分)进行排序的移动。 (也用它来打破关系。)
在上面的示例中,此策略的工作方式如下:
[3 1 2 5 4]
(订单2) - &gt; [3 4 5 2 1]
(订单3) - &gt; [5 4 3 2 1]
(订单5) - &gt; [1 2 3 4 5]
(订单5,正确排序)。
导致结果的时间比OP的移动顺序短。此算法的复杂性为O(n^3)
或更高,因为每个步骤都需要比较所有O(n^2)
可能的移动。
这种情况发生故障的点是当有多个动作给出相同的顺序时。例如,如果&#34; 6&#34;添加到原始示例的末尾,有一个移动通过序列[4 5 6]
导致顺序4,但该移动是次优选择。如何解决这个问题的一个可能的想法是在计算得分时不要考虑列表两端已经排序的位置中的元素。很明显,从来没有理由触及这些元素,因此算法应该只考虑列表中间但未分类的部分。
答案 3 :(得分:-1)
我的直觉是,问题可以归结为:
关于1),我使用以下java代码和提供的数组[3,1,2,5,4]对此进行了测试:
private static int[] reverseSort(int[] array) {
int temp;
int count = 0;
boolean reversed = false;
do {
reversed = false;
for (int i = 0; i < array.length-1; i++) {
if (array[i] > array[i+1]) {
temp = array[i];
array[i] = array[i+1];
array[i+1] = temp;
count++;
System.out.println(count + ": reverse [" + i + ", " + (i + 1) + "]");
reversed = true;
}
}
} while (reversed);
return array;
}
这产生以下(瞬时)输出:
1: reverse [0, 1]
2: reverse [1, 2]
3: reverse [3, 4]
我们已经有了一个更简单的解决方案,只涉及2元组! (检查一下)。
关于2),我会留给其他人来弄清楚一般情况下的复合。我太累了,我想上床睡觉......
关于复合的提示(这就是所要求的):对于3元组,遍历每组连续的3个2元组以查看它们是否复合为3元组。因此,例如开关[1,2] [2,3] [1,2]和[2,3] [1,2] [2,3]都将从[3,...,3]生成[1,2,3] 2,1],可以用化合物反向取代。推广到n元组,并在n =数组长度时停止。