关于算法:使用最少的比较对数组进行排序

Sorting an array with minimal number of comparisons

我的CS作业需要帮助。我需要编写一个排序例程,在最坏的情况下,使用7个比较对长度为5的数组进行排序(因为决策树的高度,我已经证明需要7个)。

我考虑过使用"硬编码"的决策树,但这意味着算法非常复杂,而且我的导师也暗示过这不是应该做的事情。

我检查过Quicksort、Merge Sort、Heap Sort、D-ary Heap Sort、Insertion Sort、Selection Sort,都没有满足要求,这让我相信对于长度为5的数组需要一个特定的算法。

我真的很想得到一些指向正确方向的提示。


唐纳德·克努斯的《计算机编程艺术》,第三卷有一个关于这个主题的章节。我这里没有书,但我很确定Knuth给出了5个元素的算法。正如您所怀疑的,没有一个通用的算法能够为许多大小提供最少数量的比较,但是在这种算法中使用了许多常见的技巧。

从模糊回忆出发,对5个元素的算法进行了重构,并进行了7次比较。首先,取两个独立的对,在它们内部进行比较,并比较每对中较小的一对。然后,将剩下的一个和较大的一个比较。现在根据剩下的元素是小还是大,将其分为两种情况,但在所有情况下,都可以在三种比较中完成。

我建议画画来帮助你。克努斯的照片是这样的:

1
2
3
   o---o
  /
 o---o

它显示前三次比较后的结果(据我所知,这种图片以许多最小的比较类型出现)。一条线连接两个元素,我们知道它们的顺序。拥有这样的图片可以帮助您确定要与哪些元素进行比较,因为您希望进行比较,从而获得最大的信息量。

附录:由于有一个可接受的实际代码的答案,我想整理这些图表没有什么坏处,它们可能是对答案的一个有用的补充。所以,从上面的一个开始,将丢失的元素与左上角的元素进行比较。如果它更大,这将导致

1
2
3
4
5
    /--o
   o
  / \--o
 o
  \--o

现在,比较右上角的两个大元素,结果是

1
2
3
   o---o---o
  /
 o---o

现在,通过将右下角元素先与顶部中间元素进行比较,然后与它所属的任何一侧进行比较,我们使用剩下的两个比较将其正确放置。

如果最初的比较导致剩余的元素变小,则图表将变为

1
2
3
 o---o---o
    /
   o---o

现在,比较一下还没有比它们小的两个。一个选项是上面的最后一个图表,它可以用剩下的两个比较来解决。另一种情况是

1
2
3
       o---o
      /
 o---o---o

在这里,还可以通过两个比较来正确地放置一个还没有与其他的顺序一致的对象。


是的,它在Knuth第3卷第185页(第5.3.1节)中。这是第一次记录在博士论文中,所以你的教授对你很严厉!没有真正简单、优雅的方法;您必须将其作为部分有序的树来跟踪。

它在里斯普。测试正常(Linux上的sbcl)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
(defun small-sort (a)  
 "Sort a vector A of length 5"  
  (if (> (aref a 0) (aref a 1))  
      (rotatef (aref a 0) (aref a 1)))  
  (if (> (aref a 2) (aref a 3))  
      (rotatef (aref a 2) (aref a 3)))  
  (if (> (aref a 0) (aref a 2))  
      (progn  
        (rotatef (aref a 0) (aref a 2))  
        (rotatef (aref a 1) (aref a 3))))  
  (if (> (aref a 4) (aref a 2))  
      (if (> (aref a 4) (aref a 3))  
          (progn)  
          (rotatef (aref a 3) (aref a 4)))  
      (if (> (aref a 4) (aref a 0))  
          (rotatef (aref a 2) (aref a 4) (aref a 3))  
          (rotatef (aref a 0) (aref a 4) (aref a 3) (aref a 2))))  
  (if (> (aref a 1) (aref a 3))  
      (if (> (aref a 1) (aref a 4))  
          (rotatef (aref a 1) (aref a 2) (aref a 3) (aref a 4))  
          (rotatef (aref a 1) (aref a 2) (aref a 3)))  
      (if (> (aref a 1) (aref a 2))  
          (rotatef (aref a 1) (aref a 2))  
          (progn)))  
  a)  

(defun check-sorted (a)  
  (do ((i 0 (1+ i)))  
      ((>= i (1- (array-dimension a 0))))  
    ;;(format t"~S ~S~%" (aref a i) (aref a (+ 1 i)))  
    (assert (<= (aref a i) (aref a (+ 1 i))))))  

(defun rr ()  
  (dotimes (i 100000)  
    (let ((a (make-array 5 :initial-contents (list (random 1.0) (random 1.0) (random 1.0) (random 1.0) (random 1.0) ))))  
      ;;(format t"A=~S~%" a)  
      (let ((res (small-sort a)))  
        (check-sorted res)  
        ;;(format t"Res=~S~%" res)  
        ))))


桶排序可以作为一种比较算法来实现,如下所示:

取一个元素。

把它和所有的桶比较一下。

把它放进匹配的桶里。<-需要比较。

如果找不到存储桶,请创建一个新的存储桶。

所以这是我刚才描述的一个动态桶排序算法。

我以前就在新闻组上发明/描述过这个。


我认为硬编码解决方案不需要那么复杂:

  • 比较(元素)2和3,必要时交换
  • 比较3和4,必要时交换
  • 比较1和3,如果1小于1,则比较1和2,否则比较1和4。将1放在正确的插槽中。
  • 重复步骤3,第3和第5项除外。
  • 这将始终使用7个比较。

    编辑:

    我不认为这会奏效:步骤4被打破,可能需要第8个比较。考虑:

    1
    2
    Index | 1 | 2 | 3 | 4 | 5 |
    Value | 2 | 3 | 4 | 5 | 1 |

    第4步:

  • 比较3&5==4与1==5小于3
  • 比较2&5==3与1==5小于2
  • ????需要比较1和5以知道将元素5放在哪里。

  • 7听起来也可能是贝壳式的。


    看一下桶式分拣机。