【问题标题】:What sorting algorithm is this 3 liner?这个 3 班轮是什么排序算法?
【发布时间】:2022-01-19 14:26:33
【问题描述】:

给定数组

a=[4,6,9,3,1,5,9,1,4,2,8]
[4, 6, 9, 3, 1, 5, 9, 1, 4, 2, 8]

排序算法

请注意,以防有人不知道。要交换 2 个数字 'a' 和 'b',您可以执行 a=a+b b=a-b a=a-b (也可以使用 * 或 / 来完成)。

for(i=0;i<a.length;i++)
   for(j=i+1;j<a.length;j++)
     if(a[i]>a[j]) {a[i]=a[i]+a[j]; a[j]=a[i]-a[j]; a[i]=a[i]-a[j];  }

或更简洁,使用 += 和 -=

for(i=0;i<a.length;i++)
   for(j=i+1;j<a.length;j++)
     if(a[i]>a[j]) {a[i]+=a[j]; a[j]=a[i]-a[j]; a[i]-=a[j];  }

结果数组

[1, 1, 2, 3, 4, 4, 5, 6, 8, 9, 9]

所以它工作,在 C 和 javascript 控制台中测试。我看到有人用 C 语言从他的头脑中做到这一点,我对此感到震惊,因为当我做排序算法时,我记得它们是你研究的棘手的东西,然后你可以从头到尾知道,写下数学陈述来描述它是如何工作的,不要不要记住它,然后忘记它或查找它或使用第三方。但这是一个简单的 3 行,可以蒙着眼睛输入。

我想到了我听说过的排序算法。我知道它不是什么。冒泡排序是冗长的,并且会扫描多次,所以绝对不是那样。合并排序和快速排序涉及递归,所以不是那样的。我认为它要么是插入排序,要么是选择排序。但是在维基百科上看它们,它们看起来都没有那么小……

我了解它的工作原理。从数组的已排序部分右侧的 i 向前扫描,查找与 a[i] 的第一个最小交换然后是下一个最小的交换并交换它,因此 a[i] 然后包含未排序和 i++ 中的最小

维基百科上有一个用于插入排序的,它有更多的行,可能相似,但似乎有不同的“逻辑”

for i = 1 to length(A) - 1
    x = A[i]
    j = i
    while j > 0 and A[j-1] > x
        A[j] = A[j-1]
        j = j - 1
    end while
    A[j] = x
 end for

它看起来一点也不简洁(尽管也许可以做到这一点),并且内部循环倒计时而不是倒计时。

或(仍在维基百科插入排序页面上)

for i ← 1 to length(A) - 1
    j ← i
    while j > 0 and A[j-1] > A[j]
        swap A[j] and A[j-1]
        j ← j - 1
    end while
end for

我试图制作一个简洁的表格(部分是为了测试逻辑有多相似),但它没有这样做

a=[4,6,9,3,1,5,9,1,4,2,8]

for(i=1;i<a.length-1;i++)
  for(j=i;j>0;j--)
      if(a[j-1]>a[j]) {a[j-1]+=a[j]; a[j]=a[j-1]-a[j]; a[j-1]-=a[j]; }

生产

[1, 1, 2, 3, 4, 4, 5, 6, 9, 9, 8] <-- spot the 8 at the end!

抛开我的失败尝试使维基百科上的插入排序算法与我所拥有的算法一样简洁,并且结构相似..

这表明它本质上是不同的。所以如果我一开始给出的 3 行算法不是插入排序,那么它是什么......为什么它与维基百科上的不同,内部循环以另一种方式计数,它不会进入等效的 3 行,这告诉我算法背后的逻辑也有点不同。

我看到维基百科上的选择排序看起来也不是很简洁。所以我确定我的算法不是那样的。

我看到了 wikipedia 上的插入排序是如何工作的。它递增 i,将元素添加到已排序的部分,然后从右到左向下扫描已排序的部分,以及第一个最大的项目 > a[i](那是也是排序部分中最大的),与 a[i] 交换所以现在再次对 0..i 中的所有内容进行排序和 i++。但这个逻辑是完全不同的。通过排序部分向下计数与通过未排序部分向上计数是完全不同的。而且我可以看到维基百科的效率很高,当它在内部循环中交换时会中断,这比我的效率更高,因为它可能会进行多次交换以将排序部分增加一。

所以我想我的问题是......我的名字是什么?我不会称它为垃圾排序,因为它在线条方面非常简洁......我认为它必须有一个正确的名称。他们甚至给冒泡排序起了个名字,但效率并不高。

wikipedia 插入排序能否像我所做的那样在行方面变得简洁..(或接近简洁)?

修正

moreon 发现了一个错字,我将 i&lt;a.length-1 现在修改为 i&lt;a.length 或 i&lt;=a.length-1 显然应该如此,所以现在插入排序的 3 行低效版本有效。不过,我已经制作了一个高效简洁的版本,请参阅我的答案。

【问题讨论】:

  • “冒泡排序是冗长的,并且会扫描多次,所以绝对不是那样”。是什么让您认为冒泡排序“啰嗦”?是什么让你认为这个算法不会扫描多次?
  • 如果你修复' for(i=1;i
  • @moreON 没有该算法的i=1 也在维基百科中(如我从那里的粘贴中所示),否则当它执行 j=i 并查看 a[j-1] 它时d 查看 a[-1] 并失败。
  • @barlop 那行有问题,只是那部分没有问题。我之前的评论中有提示。
  • @moreON 啊

标签: algorithm sorting


【解决方案1】:

这实际上是一种编码不佳的选择排序。第 i 遍从子数组 a[i..n-1] 中选择最小元素并将其放入 a[i] 中,因为 i=0...n-1。 (最后一遍是考虑一个长度为零的子数组,什么也不做。)它只是通过大量无用的交换找到最小值。

交换本身是避免使用临时变量的老技巧。大多数情况下,您会将 XOR 视为运算,但减法也可以。实际上,在几乎所有情况下使用温度都更有效。

大多数 O(n^2) 排序都有愚蠢的变化,相当于重新调整循环。插入排序是:

for (i = 1; i < n; i++)
  for (j = i; j > 0; j--)
    if (a[j] < a[j-1]) swap(a[j], a[j-1])

这很愚蠢,因为尽管它最终在已排序子数组 a[0..i-1] 的正确位置插入第 i 个元素时会停止,但它会继续运行。它也是交换而不是仅仅向上移动元素,“真正的”插入排序会这样做。

【讨论】:

  • 我发誓我以前见过这个问题,而且这里似乎很少有人能识别选择类型。它们并不难发现。
  • 我看你是对的。 en.wikipedia.org/wiki/Selection_sort 正确的选择排序会进行这些比较,但只需使用它们来查找最小值的索引并在找到最小值后进行交换。就行数而言,虽然还不错。它比冒泡排序效率低还是效率高?它可能具有最短排序算法的优势。 (比常规选择排序短)。
  • 以及选择排序和插入排序如何简洁。可能不如我的简洁,但有多简洁?
  • 我的也一样吗?一样或更糟?比冒泡排序?
  • @barlop 交换读取两个值并将两个值写入相同的位置。由交换组成的移位读取和写入每个位置两次。这是一个糟糕的内存访问用例。移位读取和写入每个位置一次。
【解决方案2】:

双 for 循环暗示它是 O(N^2) 运行时间。好老式的selection sort。最里面的三个语句(a[j-1]...)是一种执行交换的方式。

除了看起来很有趣之外,我通常建议不要使用此代码。几乎任何语言在其标准库中都有更快的排序例程。此外,通过加法/减法进行交换是不必要的,并且减少了允许的整数范围(由于溢出)。

【讨论】:

  • 这只是一个选择排序。它使用交换将当前候选最小成员存储在目标位置,而不是在其他地方使用额外的存储空间。
  • 正确。我阅读了中间实现(j & j-1)而不是顶部的实际问题(i & j)。答案在上面编辑。
  • 我要补充一点,@moreON 写道“而不是在其他地方使用额外的存储空间”,他的意思是而不是使用包含更新为最小候选索引的索引的变量。
  • 而且值得一看的是更快的排序算法更有趣:)
【解决方案3】:

插入排序可以变得高效而简洁

插入排序

for(i=1;i<a.length;i++)
   for(j=i;j>0;j--)
     if(a[j-1]>a[j]) {a[j-1]+=a[j]; a[j]=a[j-1]-a[j]; a[j-1]-=a[j]} else break;

插入排序涉及并且必须涉及多次交换,因为它通过不断地与其邻居交换新数字直到它的顺序将所有较大的数字向右移动。请参阅此图以获取解释“算法第 2 课:插入排序”https://www.youtube.com/watch?v=c4BRHC7kTaQ

选择排序https://www.youtube.com/watch?v=6nDMgr0-Yyo 不应该通过多次交换来完成,这是非常低效的。它应该找到最小的未排序索引并进行一次交换。你可以用 4 行 + 大括号来完成,

你不能在这里做 a+=b b=a-b a-=b 因为在哪里 i==minius 你正在“交换” a[i] 与 a[i] 可以使用临时变量,但使用算术破解你得到 a[i]==0 还有人指出,如果你的数字接近数据类型的限制,例如对于int,那么加减法可能会导致溢出。

正如 gregg 指出的那样,交换而不是轮班也是低效的

选择排序

// minius 是未排序/未放置中的最小索引

for(i=0;i<a.length;i++)  
{
  minius=i;  // min index in unsorted/unplaced   
  for(j=i+1;j<a.length;j++) if(a[minius]>a[j]) minius=j;   
  temp=a[i]; a[i]=a[minius]; a[minius]=temp;   
}

【讨论】:

    猜你喜欢
    • 2020-11-16
    • 2011-02-01
    • 1970-01-01
    • 2015-04-30
    • 2014-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多