我们有一个未分类的N个数字序列(1,2,3,4,... N)。我们可以通过按特定顺序交换相邻元素来对整个序列进行排序。给定序列,如何计算对序列进行排序所需的最小可能交换。
例如,考虑序列{4,2,5,3,1}。
对此进行排序的最佳方法是按以下顺序使用7次交换
贪婪的算法并没有成效。一个反例很容易构建。接近解决方案的下一个明显选择是动态编程。
假设我们有一个未排序的序列:{A1,A2,... Ai,A(i + 1),...,An}。我们知道对序列{Ai,A(i + 1),...,An}进行排序所需的最小交换次数是Min [Ai,A(i + 1),...,An}。问题是找到Min [A(i-1),Ai,...,An]。
好吧,我想到的第一个想法是添加将A(i-1)放入已经排序的序列{Ai,...,An}中正确位置所需的步骤数。这是有效的:问题中给出的例子已经使用完全相同的方法解决了。
但我无法证明此解决方案的有效性。这种情况经常发生在我身上。当我认为我已经解决了问题时,我能做的最好的就是获得一个“直观”的证据。我在高中并且没有正确的算法训练。我纯粹出于兴趣而这样做。
是否有严格的数学符号表明这个问题可以正式转换并证明?这种符号可以扩展到其他问题吗?怎么样?如果能够以高中生可以理解的形式呈现,我将不胜感激。
答案 0 :(得分:24)
这是一个经典的算法问题。如果交换的最小数量等于数组中的反转数。如果我们有索引i和索引j使得 i > a j ,i<那么这被称为反转。让我们证明这个说法!我将在途中需要一些引理:
引理1:如果没有两个相邻元素的反转,则对数组进行排序。
证明:让我们假设没有两个相邻的元素形成反转。这意味着对于区间[0,n-1]中的所有i, i &lt; = a i + 1 。由于<=
是传递性的,这意味着数组已经排序。
引理2:两个相邻元素的单次交换将使阵列中的反转总数减少最多1个。
证明:当我们交换两个相邻元素时, i 和 i + 1 它们相对于数组中所有其他元素的相对位置将维持不变。这适用于 i + 1 之后的所有元素,它们仍将位于 i + 1 之后,并且位于 i 之前的所有元素之后,他们仍然会在 i 之前。这也意味着如果 i 或 i + 1 与元素a j 形成反转,那么它们仍将形成反转它在交换之后。因此,如果我们交换 i 和 i + 1 ,我们将只影响这两个元素用于形成的反转。由于两个元素可能只参与一个反演,我们也证明了这个引理。
引理3:我们需要至少执行相邻元素的NI交换,以便对数组进行排序,其中NI是数组中反转的数量
证明:在排序数组中没有反转。同样根据引理2,单个交换可以将反转次数减少至多一次。因此,我们需要执行至少与反转次数一样多的交换。
引理4:我们总是可以对执行相邻元素的NI交换的数组进行排序,其中NI就像数组中的反转数一样。
证明:如果我们假设在我们的数组中没有两个相邻元素的反转,那么根据引理1,数组将被排序并且我们完成了。
否则,至少有一对相邻元素形成反转。我们可以交换它们,从而减少反转总数一次。我们可以在NI时间继续执行此操作。
现在我从答案的开头证明了我的陈述。
剩下的唯一问题是如何计算给定数组中的反转次数。您可以使用稍微修改合并排序来执行此操作,您可以在合并阶段累积反转。您可以查看this answer,了解有关如何实现该功能的详细信息。算法的总体复杂性为O(n*log(n))
。
答案 1 :(得分:1)
感谢@Ivaylo Strandjev的解释,为了使答案更加完整,这里是Java实现:
// http://stackoverflow.com/questions/20990127/sorting-a-sequence-by-swapping-adjacent-elements-using-minimum-swaps
// The minimum number if swaps is equal to the number of inversions in the array
public static long sortWithSwap(int [] a) {
return invCount(a, 0, a.length-1);
}
private static long invCount(int[] a, int left, int right) {
if(left >= right) return 0;
int mid = left + (right-left)/2;
long cnt = invCount(a, left, mid) + invCount(a, mid+1, right);
cnt += merge(a, left, mid, right);
return cnt;
}
private static long merge(int[] a, int left, int mid, int right) {
long cnt = 0;
int i = left, j = mid+1, k = left;
int[] b = new int[a.length];
while(i<=mid && j<=right) {
if(a[i] <= a[j]) b[k++] = a[i++];
else {
b[k++] = a[j++];
cnt += mid - i + 1;
}
}
while(i <= mid) {
b[k++] = a[i++];
}
while(j <= right) {
b[k++] = a[j++];
}
for(i=left; i<=right; i++) a[i] = b[i];
return cnt;
}