稳定的排序算法,用于4个元素,需要5次比较和5次交换。

huangapple go评论81阅读模式
英文:

stable sorting algorithm for 4 elements, with 5 comparisons and 5 swaps

问题

有没有一个短小、稳定的排序算法,可以在进行5次比较和5次交换的情况下对4个元素进行排序?

我正在寻找一个类似于 https://stackoverflow.com/a/50303723/97248 的算法,它在长度和复杂性方面类似,但是要求是稳定的。这个算法不是稳定的:例如,对于输入 [60, 61, 52, 73],如果比较函数忽略最后一位数字,结果是 [52, 61, 60, 73],而正确的稳定结果应该是 [52, 60, 61, 73]。

英文:

Is there a short, stable sorting algoritm for 4 elements with 5 comparisons and 5 swaps?

I'm looking for an algorithm like https://stackoverflow.com/a/50303723/97248 in terms of length and complexity, but which is stable. This one is not stable: for example, for input [60, 61, 52, 73], with comparison function which ignores the last digit, the result is [52, 61, 60, 73], and the correct stable result would be [52, 60, 61, 73].

答案1

得分: 2

以下是您提供的文本的中文翻译:

看起来,如果我们跟踪可能引入两个相等值反转的交换,那是可能的。当交换涉及非相邻的值时,这种情况至少会发生一次,如果我们想要保持比较/交换次数为五次。这种潜在的反转将确定是否应使用>还是> =进行下一次比较。

为了测试“稳定性”,我使用了(键,索引)对进行了测试,其中只有键影响比较,而索引可以用来验证在键相同时原始相对顺序是否被改变。

以下是JavaScript中的实现:

function sort(arr)  {
    function swapIfGreater(arr, i, j, orEqual) {
        const isGreater = orEqual ? arr[i][0] >= arr[j][0] : arr[i][0] > arr[j][0];
        if (isGreater) [arr[i], arr[j]] = [arr[j], arr[i]]; // 交换
        return isGreater;
    }
    // 5次对swapIfGreater的调用:
    swapIfGreater(arr, 1, 2, false);
    const outerInversion = swapIfGreater(arr, 0, 3, false);
    const innerInversion1 = swapIfGreater(arr, 0, 1, outerInversion);
    const innerInversion2 = swapIfGreater(arr, 2, 3, outerInversion);
    swapIfGreater(arr, 1, 2, (innerInversion1 || innerInversion2) && outerInversion);
}

function verify(arr) {
    for (let i = 1; i < arr.length; i++) {
        const diff = arr[i][0] - arr[i-1][0] || arr[i][1] - arr[i-1][1];
        if (diff < 0) throw "排序不正确!";
    }
}

function tagWithIndex(arr) { // 用于检测相等值的反转
    for (let i = 0; i < arr.length; i++) {
        arr[i] = [arr[i], i]; // 条目变成一对:值和索引
    }
}

// 测试所有无序输入的可能性:
for (let a = 0; a < 4; a++) {
    for (let b = 0; b < 4; b++) {
        for (let c = 0; c < 4; c++) {
            for (let d = 0; d < 4; d++) {
                const arr = [a, b, c, d];
                tagWithIndex(arr);
                sort(arr);
                verify(arr);
            }
        }
    }
}

console.log("所有测试都通过了");

请注意,“反转”变量只是为了避免嵌套的if ... else块,以模拟相同的状态。

英文:

It seems to be possible if we track swaps that could (potentially) introduce an inversion of two equal values. This can happen when a swap concerns non-adjacent values, which will be needed at least once if we want to keep the number of comparisons/swaps at five. Such potential inversion will then determine whether a next comparison should be done with &gt; or &gt;=.

To test "stableness", I tested with (key, index) pairs where only the keys influence the comparison, while the index can be used to verify that the original relative order was not altered when keys are the same.

Here is an implementation in JavaScript:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

function sort(arr)  {
function swapIfGreater(arr, i, j, orEqual) {
const isGreater = orEqual ? arr[i][0] &gt;= arr[j][0] : arr[i][0] &gt; arr[j][0];
if (isGreater) [arr[i], arr[j]] = [arr[j], arr[i]]; // swap
return isGreater;
}
// 5 calls to swapIfGreater:
swapIfGreater(arr, 1, 2, false);
const outerInversion = swapIfGreater(arr, 0, 3, false);
const innerInversion1 = swapIfGreater(arr, 0, 1, outerInversion);
const innerInversion2 = swapIfGreater(arr, 2, 3, outerInversion);
swapIfGreater(arr, 1, 2, (innerInversion1 || innerInversion2) &amp;&amp; outerInversion);
}
function verify(arr) {
for (let i = 1; i &lt; arr.length; i++) {
const diff = arr[i][0] - arr[i-1][0] || arr[i][1] - arr[i-1][1];
if (diff &lt; 0) throw &quot;Not sorted correctly!&quot;;
}
}
function tagWithIndex(arr) { // To detect inversions of equal values
for (let i = 0; i &lt; arr.length; i++) {
arr[i] = [arr[i], i]; // Entry becomes a pair: the value, and the index
}
}
// Test all possibilities of unordered inputs:
for (let a = 0; a &lt; 4; a++) {
for (let b = 0; b &lt; 4; b++) {
for (let c = 0; c &lt; 4; c++) {
for (let d = 0; d &lt; 4; d++) {
const arr = [a, b, c, d];
tagWithIndex(arr);
sort(arr);
verify(arr);
}
}
}
}
console.log(&quot;all tests passed&quot;);

<!-- end snippet -->

Note that the inversion variables are only there to avoid a nested tree of if ... else blocks to mimic the same states.

答案2

得分: 0

这个答案受到了@JimMischel的评论启发。它实现了对最多进行5次比较和6次交换的4个元素的稳定排序。这是一种使用二分查找来找到插入位置的插入排序变种的Python实现。

def stable_sort4(a):
  if len(a) != 4:
    raise ValueError
  if a[1] // 10 < a[0] // 10:
    a[1], a[0] = a[0], a[1]
  if a[2] // 10 < a[1] // 10:
    a[2], a[1] = a[1], a[2]
    if a[1] // 10 < a[0] // 10:
      a[1], a[0] = a[0], a[1]
  if a[3] // 10 < a[1] // 10:
    if a[3] // 10 < a[0] // 10:
      a[3], a[2] = a[2], a[3]  # 交换 #4。
      a[2], a[1] = a[1], a[2]  # 交换 #5。
      a[1], a[0] = a[0], a[1]  # 交换 #6。
    else:
      a[3], a[2] = a[2], a[3]
      a[2], a[1] = a[1], a[2]
  elif a[3] // 10 < a[2] // 10:
    a[3], a[2] = a[2], a[3]


def test():
  for i0 in range(4):
    for i1 in range(4):
      for i2 in range(4):
        for i3 in range(4):
          a = [i0 * 10, i1 * 10 + 1, i2 * 10 + 2, i3 * 10 + 3]
          stable_sort4(a)
          if a != sorted(a):
            raise AssertionError(a)


if __name__ == '__main__':
  test()

这个算法总是交换相邻的元素,每次交换都会减少总的逆序计数1。如果输入是反向的,那么最初有3 + 2 + 1 == 6个逆序,因此任何只交换相邻元素的算法都必须至少进行6次交换。等价地,要用5次交换来解决它,我们需要一种算法(如@trincot的答案中的算法),有时会交换非相邻的元素。

英文:

This answer is inspired by the comment of @JimMischel. It implements stable sorting of 4 elements with at most 5 comparisons and 6 swaps. It's a Python implementation of the variant of insertion sort which uses binary search to find the insertion location.

def stable_sort4(a):
  if len(a) != 4:
    raise ValueError
  if a[1] // 10 &lt; a[0] // 10:
    a[1], a[0] = a[0], a[1]
  if a[2] // 10 &lt; a[1] // 10:
    a[2], a[1] = a[1], a[2]
    if a[1] // 10 &lt; a[0] // 10:
      a[1], a[0] = a[0], a[1]
  if a[3] // 10 &lt; a[1] // 10:
    if a[3] // 10 &lt; a[0] // 10:
      a[3], a[2] = a[2], a[3]  # Swap #4.
      a[2], a[1] = a[1], a[2]  # Swap #5.
      a[1], a[0] = a[0], a[1]  # Swap #6.
    else:
      a[3], a[2] = a[2], a[3]
      a[2], a[1] = a[1], a[2]
  elif a[3] // 10 &lt; a[2] // 10:
    a[3], a[2] = a[2], a[3]


def test():
  for i0 in range(4):
    for i1 in range(4):
      for i2 in range(4):
        for i3 in range(4):
          a = [i0 * 10, i1 * 10 + 1, i2 * 10 + 2, i3 * 10 + 3]
          stable_sort4(a)
          if a != sorted(a):
            raise AssertionError(a)


if __name__ == &#39;__main__&#39;:
  test()

This algorithm always swaps adjacent elements, each swap decreasing the total inversion count by 1. If the input is reversed, then initially there are 3 + 2 + 1 == 6 inversions, thus any algorithm which swaps adjacent elements only must do at least 6 swaps. Equivalently, to solve it with 5 swaps, we need an algorithm (such as the one in @trincot's answer) which sometimes swaps non-adjacent elements.

huangapple
  • 本文由 发表于 2023年7月17日 17:01:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/76702902.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定