📜  睡眠排序–懒惰之王/睡眠时排序

📅  最后修改于: 2021-04-29 12:43:42             🧑  作者: Mango

在此算法中,我们为输入数组中的每个元素创建不同的线程,然后每个线程休眠一段时间,该时间与相应数组元素的值成比例。

因此,具有最少睡眠时间的线程首先被唤醒,然后编号被打印,然后是第二最小的元素,依此类推。最大的元素会在很长一段时间后唤醒,然后在最后打印该元素。因此,输出为已排序的输出。

所有这些多线程过程都发生在后台以及操作系统的核心。我们对后台发生的事情一无所知,因此这是一种“神秘的”排序算法。

示例:(为方便起见)假设我们有一台速度非常慢的计算机,它需要3秒钟来处理每个元素:

INPUT: 8 2 9 

3s: sleep 8
6s: sleep 2
8s: "2" (2 wakes up so print it)
9s: sleep 9
11s: "8" (8 wakes up so print it)
18s: "9" (9 wakes up so print it)

OUTPUT: 2 8 9

执行
要实现睡眠排序,我们需要多线程函数,例如_beginthread()WaitForMultipleObjects() 。因此,我们需要包括windows.h才能使用这些功能。这不会在Online IDE上编译。我们必须在您的PC上运行它(请注意,此代码适用于WINDOWS,而不适用于LINUX)。

为了执行睡眠排序,我们需要为输入数组中的每个值创建线程。我们使用函数_beginthread()进行此操作。

在每个线程中,我们分配两条指令:

1)Sleep :休眠此线程直到arr [i]毫秒(其中arr [i]是与此线程相关联的数组元素)。我们使用Sleep()函数执行此操作。 Sleep(n)函数将与该线程关联的活动挂起,直到“ n”毫秒。因此,如果我们编写Sleep(1000),则意味着线程将休眠1秒(1000毫秒= 1秒)

2)打印:当线程在睡眠后“醒来”时,请打印与该线程关联的数组元素– arr [i]。

创建线程之后,我们将处理这些线程。我们使用WaitForMultipleObjects()进行此操作。

// C implementation of Sleep Sort
#include 
#include 
#include 
  
// This is the instruction set of a thread
// So in these threads, we "sleep" for a particular
// amount of time and then when it wakes up
// the number is printed out
void routine(void *a)
{
    int n = *(int *) a; // typecasting from void to int
  
    // Sleeping time is proportional to the number
    // More precisely this thread sleep for 'n' milliseconds
    Sleep(n);
  
    // After the sleep, print the number
    printf("%d ", n);
}
  
/* A function that performs sleep sort
_beginthread() is a C run-time library call that creates a new
'thread' for all the integers in the array and returns that
thread.
  
Each of the 'thread' sleeps for a time proportional to that
integer and print it after waking.
  
We pass three parameters to _beginthread :-
1) start_address --> start address of the routine/function
                     which creates a new thread
2) stack_size --> Stack Size of the new thread (which is 0)
3) arglist --> Address of the argument to be passed
  
The return value of _beginthread() function is a handle to the
thread which is created. So we must accept is using the datatype-
'HANDLE' which is included in windows.h header
'HANDLE' datatype is used to represent an event/thread/process etc
So 'HANDLE' datatype is used to define a thread
We store the threads in an array - threads[] which is declared
using 'HANDLE' datatype.
  
WaitForMultipleObjects() is a function that processes the threads
and has four arguments-
1) no_of_threads --> Number of threads to be processed
2) array_of_threads --> This is the array of threads which should be
                        processed. This array must be of the type
                        'HANDLE'
3) TRUE or FALSE --> We pass TRUE if we want all the threads in the
                     array to be processed
4) time_limit --> The threads will be processed until this time limit
                  is crossed. So if we pass a 0 then no threads will
                  be processed, otherwise if we pass an INFINITE, then
                  the program will stop only when all the threads
                  are processed. We can put a cap on the execution
                  time of the program by passing the desired time
                  limit */
void sleepSort(int arr[], int n)
{
    // An array of threads, one for each of the elements
    // in the input array
    HANDLE threads[n];
  
    // Create the threads for each of the input array elements
    for (int i = 0; i < n; i++)
        threads[i] = (HANDLE)_beginthread(&routine, 0,  &arr[i]);
  
    // Process these threads
    WaitForMultipleObjects(n, threads, TRUE, INFINITE);
    return;
}
  
// Driver program to test above functions
int main()
{
    // Doesn't work for negative numbers
    int arr[] = {34, 23, 122, 9};
    int n = sizeof(arr) / sizeof(arr[0]);
  
    sleepSort (arr, n);
  
    return(0);
}

睡眠排序

局限性
1)该算法不适用于负数,因为线程无法在负数的时间内休眠。

2)由于此算法取决于输入元素,因此输入数组中的数字很大会使该算法急剧减慢(因为与该数字关联的线程必须长时间休眠)。因此,即使输入数组元素仅包含2个元素,例如-{1,100000000},我们也必须等待更长的时间才能进行排序。

3)此算法不会每次都产生正确的排序输出。当输入数组中非常大的数字的左边有非常小的数字时,通常会发生这种情况。
例如– {34,23,1,12253,9}。
睡眠排序后的输出为{9,1,23,34,1223}

当最初对输入数组进行反向排序时,例如-{10,9,8,7,6,5},也会产生错误的输出。

出现此类意外输出的原因是,在扫描每个元素以及进行其他一些OS操作(例如将每个线程插入优先级队列以进行调度)之间需要花费一些时间。我们不能简单地忽略所有这些事情所花费的时间。

我们使用以下示例对此进行描述-

Let's assume (for convenience) we have a computer that's
so slow it takes 3 seconds to work through each element: 
INPUT: 10 9 8 7 6 5

3s: sleep 10
6s: sleep 9
9s: sleep 8
12s: sleep 7
13s: "10" (10 wakes up so print it)
15s: sleep 6
15s: "9" (9 wakes up so print it)
17s: "8" (8 wakes up so print it)
18s: sleep 5
19s: "7" (7 wakes up so print it)
21s: "6" (6 wakes up so print it)
23s: "5" (5 wakes up so print it)

OUTPUT: 10 9 8 7 6 5 

上面的输出只是一个例子。
显然,现代计算机的计算机并没有那么慢(要花费3秒来扫描每个元素)。
实际上,在上述阵列上的现代计算机上运行睡眠排序可提供输出– {9,5,7,10,8,6}

如何解决这个问题?
1)我们可以通过对新输出重复进行睡眠排序直到输出被排序来解决此问题。每次它将更准确地对元素进行排序。

2)前面讨论的错误输出是由于其他OS工作以及扫描每个元素所花费的时间而发生的。

在我们的程序中,我们使用了函数Sleep(arr [i]) ,这意味着与数组元素关联的每个线程都睡眠“ arr [i]”毫秒。由于毫秒的数量很少,因此其他操作系统任务可能比“ arr [i]”毫秒花费更多的时间,而这最终会导致睡眠排序出错。将睡眠时间增加甚至10倍,就可以得到排序的输出,因为OS任务将在如此多的睡眠之间完成所有任务,因此不会产生任何错误。

如果我们使用Sleep(10 * arr [i])而不是Sleep(arr [i]),那么我们肯定会获得比后者更精确的输出。例如,如果我们使用Sleep(10 * arr [i],则输入数组– {10,9,8,7,6,5}将给出正确的排序输出– {5,6,7,8,9,10} ),而不只是Sleep(arr [i])。

但是,对于某些测试用例,Sleep(10 * arr [i])仍然可能给出错误的结果。要使其更精确地增加睡眠时间,请说类似– Sleep(20 * arr [i])。

因此,最重要的是,睡眠时间越长,结果越准确。 (听起来很有趣,是吗?)。但这又会增加该算法的运行时间。

练习给读者-
1)上述算法尝试按升序对其进行排序。您可以使用睡眠排序按降序对输入数组进行排序吗?考虑一下。

2)它是基于比较的排序算法吗?该算法进行多少次比较?
[答案:否,它作零比较]

3)我们可以不使用windows.h标头和不使用Sleep()函数来进行睡眠排序吗?
[一个想法可以是创建一个优先级队列,其中根据唤醒和打印之前剩余的时间来排列元素。优先级队列前面的元素将是第一个被唤醒的元素。但是实现起来并不容易。考虑一下。]

时间复杂度
尽管关于睡眠排序的时间复杂度有很多相互矛盾的观点,但是我们可以使用以下推理来估算时间复杂度:

由于Sleep()函数和创建多个线程是由操作系统使用优先级队列(用于调度目的)在内部完成的。因此,将所有数组元素插入优先级队列需要O(Nlog N)时间。同样,只有在处理完所有线程后,即所有元素“醒来”时,才获得输出。由于唤醒第i个数组元素的线程需要O(arr [i])时间。因此,要唤醒数组中的最大元素,将需要最多O(max(input))。因此,可以将整体时间复杂度假定为O(NlogN + max(input)),
其中,N =输入数组中元素的数量,而input =输入数组元素

辅助空间
所有事情都由操作系统的内部优先级队列完成。因此,辅助空间可以忽略。

结论
睡眠排序与操作系统的关系比任何其他排序算法都多。这种排序算法是OS完成的多线程和调度的完美演示。

短语“睡觉时排序”听起来很独特。总的来说,这是一个有趣,懒惰,怪异的算法。但是正如某人正确地说的那样:“如果成功,那就不懒惰”。