Статьи

Quicksorting — 3-way и Dual Pivot

Ни для кого не новость, что Quicksort считается одним из самых важных алгоритмов столетия и что это сортировка по умолчанию для многих языков, включая Arrays.sort в Java.

Итак, что нового в быстрой сортировке?

Ну, ничего, кроме того, что я понял только сейчас (после двух проклятых лет выпуска Java 7), что реализация Arrays.sort была заменена вариантом, названным Dual-Pivot QuickSort . Эта тема удивительна не только по этой причине, но и насколько скромны на самом деле Джон Бентли и Джошуа Блох.

Что я сделал дальше?

Как и все остальные, я хотел реализовать его и провести сравнительный анализ — с примерно 10 миллионами целых чисел (случайных и повторяющихся).

Как ни странно, я нашел следующие результаты:

Случайные данные:

  • Быстрая сортировка Basic: 1222 миллис
  • Быстрая сортировка 3 способа: 1295 миллис (серьезно!)
  • Quick Sort Dual Pivot: 1066 миллис

Дубликаты данных:

  • Быстрая сортировка Basic: 378 миллис
  • Быстрая сортировка 3 способа: 15 миллис
  • Quick Sort Dual Pivot: 6 миллис

Глупый Вопрос 1:

Я боюсь, что мне что-то не хватает при реализации 3-х стороннего раздела. Через несколько прогонов против случайных входов (из 10 миллионов) чисел, я мог видеть, что одиночный стержень всегда работает лучше (хотя разница составляет менее 100 миллисекунд для 10 миллионов чисел).

Я понимаю, что основная цель сделать трехстороннюю сортировку в качестве быстрой сортировки по умолчанию до сих пор состоит в том, что она не дает производительности 0 (n 2) для дублирующих ключей — что очень очевидно, когда я запускаю ее для дублированного ввода. Но правда ли, что ради обработки дубликатов данных, 3-х сторонний сбор небольшой штраф? Или моя реализация плохая?

Глупый вопрос 2

Моя реализация Dual Pivot (ссылка ниже) плохо обрабатывает дубликаты. Для исполнения требуется сладкое навсегда (0 (n 2)) . Есть хороший способ избежать этого? Обращаясь к реализации Arrays.sort , я выяснил, что восходящая последовательность и дубликаты удаляются задолго до того, как будет выполнена фактическая сортировка. Таким образом, как грязное исправление, если стержни равны, я ускоряю перемотку LowerIndex, пока он не станет отличным от pivot2. Это справедливая реализация?

1
2
3
4
5
6
else if (pivot1==pivot2){
        while (pivot1==pivot2 && lowIndex<highIndex){
            lowIndex++;
            pivot1=input[lowIndex];
        }
    }

Вот и все. Это все, что я сделал?

Я всегда нахожу трассировку алгоритма интересной, но с учетом количества переменных в быстрой сортировке Dual Pivot, мои глаза находили его подавляющим при отладке. Итак, я также пошел дальше и создал реализации с поддержкой трассировки (для всех 3), чтобы я мог видеть, где в данный момент находятся указатели переменных.

Эти классы с поддержкой трассировки просто накладываются, когда указатели находятся ниже значений массива. Я надеюсь, что вы найдете эти занятия полезными.

например. для итерации Dual Pivot

QuickSortTracing

Весь проект (вместе с несколькими неудачными реализациями DSA) доступен на github здесь . Только классы быстрой сортировки можно найти здесь .

Вот моя реализация SinglePivot (Hoare), 3-way (Sedgewick) и нового Dual-Pivot (Yaroslavskiy)

Одиночный пивот

Оптимизированный-SinglePivot.png

01
02
03
04
05
06
07
08
09
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package basics.sorting.quick;
 
import static basics.sorting.utils.SortUtils.exchange;
import static basics.sorting.utils.SortUtils.less;
import basics.shuffle.KnuthShuffle;
 
public class QuickSortBasic {
 
  public void sort (int[] input){
 
      //KnuthShuffle.shuffle(input);
      sort (input, 0, input.length-1);
  }
 
  private void sort(int[] input, int lowIndex, int highIndex) {
 
      if (highIndex<=lowIndex){
          return;
      }
 
      int partIndex=partition (input, lowIndex, highIndex);
 
      sort (input, lowIndex, partIndex-1);
      sort (input, partIndex+1, highIndex);
  }
 
  private int partition(int[] input, int lowIndex, int highIndex) {
 
      int i=lowIndex;
      int pivotIndex=lowIndex;
      int j=highIndex+1;
 
      while (true){
 
          while (less(input[++i], input[pivotIndex])){
              if (i==highIndex) break;
          }
 
          while (less (input[pivotIndex], input[--j])){
              if (j==lowIndex) break;
          }
 
          if (i>=j) break;
 
          exchange(input, i, j);
 
      }
 
      exchange(input, pivotIndex, j);
 
      return j;
  }
 
}

3-полосная

Оптимизированный-3Way.png

01
02
03
04
05
06
07
08
09
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
41
42
43
44
package basics.sorting.quick;
 
import static basics.shuffle.KnuthShuffle.shuffle;
import static basics.sorting.utils.SortUtils.exchange;
import static basics.sorting.utils.SortUtils.less;
 
public class QuickSort3Way {
 
  public void sort (int[] input){
      //input=shuffle(input);
      sort (input, 0, input.length-1);
  }
 
  public void sort(int[] input, int lowIndex, int highIndex) {
 
      if (highIndex<=lowIndex) return;
 
      int lt=lowIndex;
      int gt=highIndex;
      int i=lowIndex+1;
 
      int pivotIndex=lowIndex;
      int pivotValue=input[pivotIndex];
 
      while (i<=gt){
 
          if (less(input[i],pivotValue)){
              exchange(input, i++, lt++);
          }
          else if (less (pivotValue, input[i])){
              exchange(input, i, gt--);
          }
          else{
              i++;
          }
 
      }
 
      sort (input, lowIndex, lt-1);
      sort (input, gt+1, highIndex);
 
  }
 
}

Dual Pivot

Оптимизированный-DualPivot.png

01
02
03
04
05
06
07
08
09
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package basics.sorting.quick;
 
import static basics.shuffle.KnuthShuffle.shuffle;
import static basics.sorting.utils.SortUtils.exchange;
import static basics.sorting.utils.SortUtils.less;
 
public class QuickSortDualPivot {
 
  public void sort (int[] input){
      //input=shuffle(input);
      sort (input, 0, input.length-1);
  }
 
  private void sort(int[] input, int lowIndex, int highIndex) {
 
      if (highIndex<=lowIndex) return;
 
      int pivot1=input[lowIndex];
      int pivot2=input[highIndex];
 
      if (pivot1>pivot2){
          exchange(input, lowIndex, highIndex);
          pivot1=input[lowIndex];
          pivot2=input[highIndex];
          //sort(input, lowIndex, highIndex);
      }
      else if (pivot1==pivot2){
          while (pivot1==pivot2 && lowIndex<highIndex){
              lowIndex++;
              pivot1=input[lowIndex];
          }
      }
 
      int i=lowIndex+1;
      int lt=lowIndex+1;
      int gt=highIndex-1;
 
      while (i<=gt){
 
          if (less(input[i], pivot1)){
              exchange(input, i++, lt++);
          }
          else if (less(pivot2, input[i])){
              exchange(input, i, gt--);
          }
          else{
              i++;
          }
 
      }
 
      exchange(input, lowIndex, --lt);
      exchange(input, highIndex, ++gt);
 
      sort(input, lowIndex, lt-1);
      sort (input, lt+1, gt-1);
      sort(input, gt+1, highIndex);
 
  }
 
}

Справка: Quicksorting — 3-way и Dual Pivot от нашего партнера JCG Аруна Маниваннана в блоге Rerun.me .