C++ 睡眠排序算法

2025年3月21日 | 阅读 9 分钟

引言

Sleep Sort 算法 是一种非传统且富有创意的数字排序方法,它依赖系统计时来间接实现所需的顺序。Sleep Sort 的想法基于这样一个事实:较大的数字可以“睡眠”或延迟比较小的数字更长的时间,从而使较小的数字在处理时先出现。

在 Sleep Sort 中,数组或列表中的每个数字都在并行线程中进行处理。每个线程负责“睡眠”一段与它所代表的数字值成比例的时间。睡眠期结束后,线程“唤醒”并打印或记录该数字。由于较小的数字具有较短的睡眠时间,因此它们会先输出,从而有效地对数组进行排序。

让我们看看 Sleep Sort 如何工作?

  • 您从一个未排序的数字列表开始。
  • 对于每个数字,都会创建一个单独的线程。
  • 每个线程“睡眠”的时间等于其关联的数字值。例如,值为 3 的数字对应的线程将睡眠 3 秒。
  • 线程唤醒后(睡眠时间结束后),它会输出或存储该数字。
  • 由于线程在不同时间完成,数字将按升序出现。

示例

考虑数组:[5, 2, 7, 1]

步骤:

为列表中的每个数字创建一个线程

  • 线程 5:将睡眠 5 秒。
  • 线程 2:将睡眠 2 秒。
  • 线程 7:将睡眠 7 秒。
  • 线程 1:将睡眠 1 秒。
  • 每个线程都并行运行,因此它们同时开始睡眠。
  • 1 秒后,线程 1 醒来并打印 1。
  • 2 秒后,线程 2 醒来并打印 2。
  • 5 秒后,线程 5 醒来并打印 5。
  • 7 秒后,线程 7 醒来并打印 7。

最终输出将是 1 2 5 7,这是输入数组的排序顺序。

方法 1:简单方法

实施

输出

 
Sorted output: 1 2 4 5 7   

说明

步骤 1:导入必要的库

程序首先导入提供线程功能和时间处理功能的库。这些库允许程序创建并发运行的线程,并控制每个线程的睡眠时间。

步骤 2:定义睡眠和打印函数

定义一个函数来处理排序逻辑。此函数以单个数字作为输入,使线程根据该数字的大小睡眠,然后打印该数字。此睡眠持续时间决定了数字的打印顺序。

步骤 3:创建排序函数

创建另一个函数来管理整个排序过程。此函数以数字列表作为输入。对于列表中的每个数字,都会创建一个新线程来执行睡眠和打印函数。这些线程并发运行,意味着它们同时运行而不是顺序运行。

步骤 4:存储线程

在创建每个线程时,都会将其存储在一个集合(如 vector)中,以便程序可以跟踪它已启动的所有线程。这使得主程序能够有效地管理这些线程。

步骤 5:加入线程

创建所有线程后,程序需要确保它们都完成执行。它通过调用一个在继续之前等待每个线程完成的方法来做到这一点。此步骤至关重要,因为它可防止主程序在所有数字都打印出来之前退出。

步骤 6:主函数执行

在程序的中心部分,定义了要排序的数字列表。使用此列表作为输入调用排序函数。函数执行时,它会为每个数字创建线程,使它们根据其值进行睡眠和打印。

步骤 7:显示输出

所有线程执行完成后,输出将显示在屏幕上。由于睡眠时间不同,数字将按升序打印——较小的数字睡眠时间较短,因此先唤醒。

复杂度分析

时间复杂度

睡眠持续时间:Sleep Sort 中的主要操作是每个线程根据要排序的数字值进行睡眠。因此,如果输入列表中的最大数字是 M,则最坏情况下的时间复杂度可以视为 O(M),因为最大数字对应的线程将需要这么长时间才能完成。

线程创建:该算法为输入列表中的每个数字创建一个单独的线程。如果 n 个数字,创建 n 个线程会产生开销,但这通常不是影响时间复杂度的主要因素。

整体时间复杂度:由于数字中的最大值决定了排序过程所需的时间,因此整体时间复杂度可以概括为 O(M),其中 M 是列表中最大的数字。如果 M 非常大,这可能会效率低下。

空间复杂度

线程存储:Sleep Sort 需要为每个创建的线程额外空间。因此,对于存储内存中的 n 个线程,空间复杂度为 O(n),其中 n 是输入列表中元素的数量。

其他变量:其他空间需求最少,主要包括输入数组和排序函数中使用的任何临时变量

整体空间复杂度:因此,由于线程的存储,整体空间复杂度为 O(n)。

方法 2:使用循环和条件变量

在这种方法中,我们将使用一个简单的循环来模拟睡眠机制,并依赖于一个共享的条件变量来管理输出顺序。

实施

输出

 
Sorted output: 1 2 4 5 7    

说明

步骤 1:包含必要的库

程序首先包含提供多线程、时间操作和同步所需功能的库。这些库使程序能够创建线程、管理睡眠持续时间并安全地处理共享数据。

步骤 2:声明共享变量

代码声明了一个互斥锁和一个条件变量。互斥锁确保对共享数据的访问是线程安全的,而条件变量有助于管理线程之间的同步。还定义了一个变量来跟踪迄今为止处理的最大数字。

步骤 3:定义睡眠和信号函数

创建了一个函数,该函数以数字作为输入。此函数使线程根据数字的值睡眠。线程醒来后,它会更新共享变量以反映迄今为止处理过的最大数字,并通知任何等待的线程有一个数字已准备好打印。

步骤 4:定义打印函数

定义另一个函数来处理数字的打印。此函数在单独的线程中运行,并等待来自条件变量的通知。它持续检查是否可以按顺序打印下一个数字。它按升序打印数字,依赖于更新的最大处理数字。

步骤 5:创建排序函数

实现了一个排序函数来管理整体过程。此函数为输入数组中的每个数字创建一个线程。每个线程都将执行睡眠和信号函数。此外,还会创建一个单独的线程来处理数字的打印。

步骤 6:管理线程执行

创建所有线程后,程序会确保它们完成执行。它使用 join 方法等待所有线程完成任务,然后主程序继续。此步骤对于防止主函数在线程仍在运行时退出至关重要。

步骤 7:初始化主函数

在主函数中,程序初始化需要排序的数字数组。然后,它使用此数组作为输入调用排序函数。所有线程完成任务后,将显示输出。

步骤 8:显示输出

所有线程完成睡眠和信号后,排序后的数字将按升序打印到控制台。由于睡眠持续时间得到管理的方式,较小的数字会先出现,从而演示了算法实现的排序。

复杂度分析

时间复杂度

睡眠持续时间:此实现中的主要操作涉及基于每个数字的值睡眠一段时间。如果输入列表中的最大数字是 M,则最坏情况下的时间复杂度可以视为 O(M),因为最长的睡眠将对应于此最大值。

线程管理:与睡眠持续时间相比,创建线程所需的时间很短。实际的线程创建开销与睡眠时间相比微不足道。

打印:打印函数等待通知,这会引入少量开销,但不会显着影响整体时间复杂度。

整体时间复杂度:因此,整体时间复杂度仍然是 O(M),其中 M 是输入列表中的最大数字。这表明当处理较大的值时,该算法可能效率低下。

空间复杂度

线程存储:输入列表中的每个数字都会创建一个相应的线程,导致空间复杂度为 O(n),其中 n 是输入列表中元素的数量。每个线程都会为其堆栈消耗内存资源。

共享变量:共享变量(互斥锁、条件变量和 max_num)具有恒定的空间需求,与输入大小无关。

整体空间复杂度:因此,由于线程的存储,整体空间复杂度为 O(n)。

性质

  1. 概念上的简洁性
    • Sleep Sort 易于理解,因为它使用了非常简单的想法:每个数字“睡眠”的时间等于其值。例如,如果数字是 3,线程将暂停 3 秒。唤醒后,它会打印该数字。这使得基于时间理解排序如何发生变得容易。
  2. 非比较排序
    • 与传统的排序算法(如冒泡排序或快速排序)不同,后者会比较元素以确定它们的顺序,Sleep Sort 不依赖于比较。它而是使用 sleep 函数的计时来对数字进行排序。这种特性使其能够演示一种不同的数据组织方式。
  3. 并发执行
    • 每个数字都在其自己的单独线程中处理,允许多个数字同时排序。这种并行处理在某些情况下对性能有利,尤其是在设计用于高效处理多个线程的系统中。
  4. 稳定性排序
    • 排序中的稳定性意味着,如果两个数字相同,则它们的原始顺序将在排序输出中保留。Sleep Sort 是稳定的,因为相等数字的线程将睡眠相同的时间,从而确保它们按照处理的顺序打印。
  5. 非就地排序
    • Sleep Sort 不是就地排序算法,这意味着它不会在与初始占用空间相同的空间内对数字进行排序。相反,它会创建单独的线程,这会导致额外的内存使用。这可能会使其效率降低,尤其是在大型数据集上。
  6. 性能的可变性
    • Sleep Sort 的效率可能因输入值而异。对于较小的数字,它的表现尚可,但如果输入包含较大的数字,由于睡眠时间延长,排序可能需要很长时间。这使得它对于具有显著值的较大数据集来说不切实际。