繁体   English   中英

替换选择排序v。选择排序

[英]Replacement Selection Sort v. Selection Sort

我一直在做一些关于替换选择排序的研究,我找不到它的任何实现或者在任何地方进行更换选择排序的良好,彻底的实现! 也许我看起来不够努力,但谷歌将替换选择排序与选择排序混淆......所以这让我感到疑惑:

选择排序和替换选择排序之间的真正区别是什么?

我在哪里可以找到替换选择排序的实现(或编写它的指南)?

替换选择排序的特征是什么,使其比其他排序算法更理想?

这个算法是否被其他任何名称所知?

我之前没有详细描述过这个算法,并且是我从阅读这套讲义中收集的内容的基础。

根据我的理解,选择排序和替换选择排序之间的关键区别在于选择排序被设计为对主存储器中保存的完整序列进行排序,而替换选择排序被设计为将未排序的序列转换为太大以适合主存储器到可以存储在外部存储器中的一系列排序序列的“链”。 然后可以将这些外部链合并在一起以形成整体分类序列。 尽管它们的名称相似,并且在算法的操作中的一个或两个关键步骤中,它们旨在解决根本上不同的问题。

选择排序

有很多关于在线选择排序的好教程,所以我不会花太多时间讨论它。 直觉上,该算法的工作原理如下:

  • 找到最小的元素并将其交换到数组的位置0。
  • 找到第二小的元素并将其交换到数组的第1位。
  • 找到第三个最小的元素并将其交换到数组的第2位
  • ...
  • 找到第n个最小元素并将其交换到数组的n - 1位置。

这假设阵列可以完全保存在内存中,如果是这种情况,则该算法在Θ(n 2 )时间内运行。 它不是很快,对于大型数据集是不可取的。

替换选择排序

该算法由Donald Knuth于1965年描述,因此它被设计为在与我们目前习惯的计算环境非常不同的计算环境中工作。 计算机的内存非常少(通常是一些固定数量的寄存器),但可以访问大型外部驱动器。 构建将一些值加载到寄存器中的算法,在那里处理它们,然后将它们直接刷回外部存储器是很常见的。 (有趣的是,这类似于处理器现在的工作方式,除了主存储器而不是外部存储器)。

让我们假设我们在内存足够的空间容纳两列:第一阵列Values大小为n,可容纳一堆数值,以及第二阵列Active大小n保存布尔值。 我们将尝试获取未分类值的大输入流并尽最大努力对其进行排序,因为我们在内存中只有足够的空间来容纳ActiveValues数组,以及一些额外的存储空间变量。

算法背后的想法如下。 首先,将包含未排序序列的外部源中的n值直接加载到Values数组中。 然后,将所有Active值设置为true 例如,如果n = 4 ,我们可能会进行以下设置:

Values:    4    1    0    3
Active:    Yes  Yes  Yes  Yes

替换选择排序算法通过重复查找Values数组中的最小值并将其写入输出流来工作。 在这种情况下,我们首先找到0值并将其写入流。 这给了

Values:    4    1         3
Active:    Yes  Yes  Yes  Yes

Output:    0

现在,我们的Values数组中有一个空白点,因此我们可以从外部源中提取另一个值。 假设我们得到2.在这种情况下,我们有这样的设置:

Values:    4    1    2    3
Active:    Yes  Yes  Yes  Yes

Output:    0

请注意,由于2> 0和0是这里的最小元素,我们保证当我们将0写入输出时,2应该不会出现在它之前。 那很好。 因此,我们继续算法的下一步,再次找到最小的元素。 这是1,所以我们将它发送到输出设备:

Values:    4         2    3
Active:    Yes  Yes  Yes  Yes

Output:    0  1

现在,从外部源读取另一个值:

Values:    4    -1   2    3
Active:    Yes  Yes  Yes  Yes

Output:    0  1

现在我们遇到了麻烦。 这个新值(-1)低于1,这意味着如果我们真的希望这个值按排序顺序进入输出,它应该在1之前。但是,我们没有足够的内存来重新读取输出设备并修复它。 相反,我们将做以下事情。 现在,让我们将-1保留在内存中。 我们将尽力对其余元素进行排序,但是当我们这样做时,我们将进行第二次迭代生成排序序列,并将-1放入该序列中。 换句话说,我们将生产两个,而不是生成一个排序的序列。

为了在内存中指出我们还不想写出-1,我们将标记保持-1为无效的槽。 这显示在这里:

Values:    4    -1   2    3
Active:    Yes  NO   Yes  Yes

Output:    0  1

从现在开始,我们只是假装-1不在这里。

让我们继续吧。 我们现在在内存中找到仍处于活动状态的最小值(2),并将其写入设备:

Values:    4    -1        3
Active:    Yes  NO   Yes  Yes

Output:    0  1  2

我们现在从输入设备中提取下一个值。 我们假设它是7:

Values:    4    -1   7    3
Active:    Yes  NO   Yes  Yes

Output:    0  1  2

从7> 2开始,它出现在输出中的2之后,所以我们什么都不做。

在下一次迭代中,我们找到最低的活动值(3)并将其写出:

Values:    4    -1   7    
Active:    Yes  NO   Yes  Yes

Output:    0  1  2  3

我们从输入设备中提取下一个值。 让我们假设它也是 3.在这种情况下,我们知道由于3是最小的值,我们可以直接将它写入输出流,因为3是这里所有值中最小的,我们可以保存自己的迭代:

Values:    4    -1   7    
Active:    Yes  NO   Yes  Yes

Output:    0  1  2  3  3

我们现在从输入设备中提取下一个值。 假设它是2.在这种情况下,和以前一样,我们知道2应该在3之前。就像前面的-1一样,这意味着我们现在需要在内存中保持2; 我们稍后会写出来的。 现在我们的设置如下:

Values:    4    -1   7    2
Active:    Yes  NO   Yes  NO

Output:    0  1  2  3  3

现在,我们找到最小的活动值(4)并将其写入输出设备:

Values:         -1   7    2
Active:    Yes  NO   Yes  NO

Output:    0  1  2  3  3  4

假设我们现在读入1作为下一个输入。 因此,我们将其置于Values ,但将其标记为无效:

Values:    1    -1   7    2
Active:    NO   NO   Yes  NO

Output:    0  1  2  3  3  4

只有一个有效值,即7,所以我们写出来:

Values:    1    -1        2
Active:    NO   NO   Yes  NO

Output:    0  1  2  3  3  4  7

让我们假设我们现在读取5.在这种情况下,像以前一样,我们存储它但标记插槽不活动:

Values:    1    -1   5    2
Active:    NO   NO   NO   NO

Output:    0  1  2  3  3  4  7

请注意, 所有值现在都处于非活动状态。 这意味着我们已经从内存中清除了可以进入当前输出运行的所有值。 现在,我们需要写下我们以后持有的所有值。 为此,我们将所有值标记为活动,然后像以前一样重复:

Values:    1    -1   5    2
Active:    Yes  Yes  Yes  Yes

Output:    0  1  2  3  3  4  7

-1是最小值,因此我们输出它:

Values:    1         5    2
Active:    Yes  Yes  Yes  Yes

Output:    0  1  2  3  3  4  7  -1

假设我们读取了一个3. -1 <3,所以我们将它加载到Values数组中。

Values:    1    3    5    2
Active:    Yes  Yes  Yes  Yes

Output:    0  1  2  3  3  4  7 -1

1是这里的最小值,所以我们删除它:

Values:         3    5    2
Active:    Yes  Yes  Yes  Yes

Output:    0  1  2  3  3  4  7 -1  1

我们假设我们现在没有输入值。 我们将此插槽标记为已完成:

Values:    ---  3    5    2
Active:    Yes  Yes  Yes  Yes

Output:    0  1  2  3  3  4  7 -1  1

接下来是2:

Values:    ---  3    5    ---
Active:    Yes  Yes  Yes  Yes

Output:    0  1  2  3  3  4  7 -1  1  2

然后3:

Values:    ---  ---  5    ---
Active:    Yes  Yes  Yes  Yes

Output:    0  1  2  3  3  4  7 -1  1  2  3

最后,5:

Values:    ---  ---  ---  ---
Active:    Yes  Yes  Yes  Yes

Output:    0  1  2  3  3  4  7 -1  1  2  3  5

我们完成了! 请注意,结果序列没有排序,但它比以前好很多。 它现在由两个按顺序排列的链组成。 将它们合并在一起(与我们为mergesort进行合并的方式相同)将对生成的数组进行排序。 这个算法可能会产生更多的链,但由于我们的样本输入很小,我们只有两个。


那有多快? 好吧,循环的每次迭代总共最多进行n次比较(在内存中),一次读取和一次写入。 因此,如果流中存在N个总值,则算法进行O(nN)比较和O(N)个存储器操作。 如果内存操作很昂贵,这也不算太糟糕,尽管你最后需要第二次传递来将所有内容合并在一起。

在伪代码中,算法如下所示:

Make Values an array of n elements.
Make Active an array of n booleans, all initially true.

Read n values from memory into Values.
Until no values are left to process:
    Find the smallest value that is still active.
    Write it to the output device.
    Read from the input device into the slot where the old element was.
    If it was smaller than the old element, mark the old slot inactive.
    If all slots are inactive, mark them all active.

如果有任何理由现在编写这个算法,我会感到震惊。 几十年前,当记忆非常非常小时,这是有道理的。 如今,有更好的外部排序算法可用,它们几乎肯定胜过这个算法。

希望这可以帮助!

替换排序仍然用于外部排序,因为它将产生最小数量的字符串以进行合并,合并是排序中成本最高的部分。 使用的方法比提供的优秀示例templatetypedef稍微复杂一些,但基本上它会缓冲许多记录,对它们进行排序,收集无序记录(如-1 1等)并将它们保存在缓冲区中,写出低阶部分在序列中,填充空的空间,再次排序,从缓冲区合并任何适合的记录,并重复直到序列无法继续,然后它转储序列,获取缓冲的序列记录和新的输入记录,从下一个字符串开始。 重复输入结束。

在一些应用程序中,不需要合并,因为替换排序扫描出序列记录,然后在正确的位置重新插入它们。 1964年霍尼韦尔和IBM推出此类产品时,这给许多商业客户带来了惊喜。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM