快速排序—三路快排 vs 双基准

快速排序被公认为是本世纪最重要的算法之一,这已经不是什么新闻了。对很多语言来说是实际系统排序,包括在Java中的Arrays.sort

那么快速排序有什么新进展呢?

好吧,就像我刚才提到的那样(Java 7发布两年后)快速排序实现的Arrays.sort双基准(dual-pivot)排序的一种变体取代了。这篇文章不仅展示了为什么这个变化如此优秀,而且让我们看到Jon Bentley和Joshua Bloch的谦逊。

我当时做了什么?

与所有人一样,我想实现这个算法并且对一千万个数值排序(随机数据和重复数据)。奇怪的是,我得到了下面的结果:

随机数据:

  • 基本排序:1222ms。
  • 三路(Three-way)快速排序:1295ms(我是认真的!)。
  • 双基准快速排序:1066ms。

重复数据:

  • 基本排序:378ms。
  • 三路快速排序:15ms。
  • 双基准快速排序:6ms。

愚蠢的问题1

我担心自己在实现三路快速排序的时候遗漏了什么。在多次执行随机输入一千万个数值后,可以看到单点排序始终运行更良好。尽管在执行一千万个数值的时候差距小于100ms。

我现在明白了,用三路快速排序作为默认排序工具的目的。因为在重复数值时,它的时间复杂度没有0(n2)。当我在输入重复值数据时,结果非常明显。但是真的为了处理重复数据的缘故,三路快速排序会受到性能损失吗?或者是我实现方式有问题?

愚蠢的问题2

我的双基准快速排序在实现重复数据的时候并没有处理好,它执行时耗费了0(n2)的时间复杂度。有什么好的办法可以避免吗?实现数组排序时我发现,在实际排序前升序序列和重复就已经能得到很好地消除。所以,作为一种应急的办法,如果定位的数字与比较的数字相等,则增长lowerIndex 去比较下一位数直到与pivot2不相等为止。这种实现会没有问题吗?


1

2

3

4

5

6

else if (pivot1==pivot2){

       while (pivot1==pivot2 && lowIndex<highIndex){

           lowIndex++;

           pivot1=input[lowIndex];

       }

   }

这就是所有内容吗?我究竟做了哪些?

我一直觉得算法跟踪很有趣,但是双基准快速排序中出现的变量个数让我眼花缭乱。所以,接下来我在(三种)实现中都加入了调试信息,这样就可以看出实际运行中不同。

这些可跟踪的类只负责追踪数组下方的指针。希望你能发现这些类是很有用的。

例如一个双基准迭代器:

你可以从哪里下载代码?

整个项目(连同一些蹩脚的DSA实现)的实现可以在GitHub上找到。快速排序类就可以在这里找到。

这是我的实现单基准(Hoare),三路快排(Sedgewick)和新双基准(Yaroslavskiy)。

单基准:


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

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;

  }

}

三基准


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

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);

  }

}

双基准


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

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);

  }

}

http://www.importnew.com/8445.html

快速排序—三路快排 vs 双基准

时间: 2025-01-06 10:43:45

快速排序—三路快排 vs 双基准的相关文章

快排,随机快排,双路快排,三路快排的理解

再讲快排之前,首先对于任何一个数组,无论之前是多么杂乱,排完之后是不是一定存在一个数作为分界点(也就是所谓的支点),在支点左边全是小于等于这个支点的,然后在这个支点右边的全是大于等于这个支点的,快排过程就是寻找这个支点过程 先看普通的快排(普通单路快排) 代码如下 let findIndex = (arr, l, len) => { let par = arr[l], j = l for (let i = l + 1; i <= len; i++) { if (arr[i] < par)

普林斯顿大学算法课 Algorithm Part I Week 3 重复元素排序 - 三路快排 Duplicate Keys

很多时候排序是为了对数据进行归类,这种排序重复值特别多 通过年龄统计人口 删除邮件列表里的重复邮件 通过大学对求职者进行排序 若使用普通的快排对重复数据进行排序,会造成N^2复杂度,但是归并排序和三路快排就没有这样的问题. 归并排序对重复数据排序的比较在1/2NlgN和NlgN之间 三路快排 目标:将数据分成三个区间(3-way partitioning) lt和gt区间内的元素都和比较元素v相等 lt左边的元素都比v小 gt右边的元素都比v大 性能 三路快排的复杂度比普通快排小,主要取决于数据

普林斯顿公开课 算法3-3:三路快排

很多时候排序是为了对数据进行归类,比如对城市进行排序,对员工的职业进行排序.这种排序的特点就是重复的值特别多. 如果使用普通的快排对这些数据进行排序,会造成N^2复杂度,但是归并排序和三路快排就没有这样的问题. 三路快排 三路快排的基本思想就是,在对数据进行分区的时候分成左中右三个部分,中间都是相同的值,左侧小于中间,右侧大于中间. 性能 三路快排的复杂度比普通快排小,主要取决于数据中重复数据的数量.重复数据越多,三路快排的复杂度就越接近于N. 代码 public class Quick3 {

leetcode 75 Sort Colors 计数排序,三路快排

解法一:计数排序:统计0,1,2 的个数 时间复杂度:O(n) 空间复杂度:O(k)    k为元素的取值范围, 此题为O(1) class Solution { public: void sortColors(vector<int>& nums) { int count[3] = {0}; //存放0,1,2三个元素的频率 for(int i=0;i<nums.size();i++){ assert(nums[i] >=0 && nums[i]<=2

三路快排

1 void partition(int arr[], int l, int r, int num)//三路快排 2 { 3 int less = l - 1; 4 int more = r + 1; 5 int cur = 0; 6 while (cur < more) 7 { 8 if (arr[cur] < num)//左边交换,直接下一个数 9 { 10 swap(arr[++less], arr[cur++]); 11 } 12 else if (arr[cur] > num)

快速排序(快排)

快速排序由C. A. R. Hoare在1962年提出.它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列. 算法处理过程(截图参考 坐在马桶上看算法:快速排序): 代码: public class QuickSort { public static void sort(int[] arr, int low, int high){ i

普林斯顿公开课 算法3-4:快排的应用

排序的应用 排序算法有着广泛的应用. 典型的应用有 对名称进行排序 排序MP3音乐文件 显示Google的搜索结果 按标题顺序列出RSS订阅 排序之后下列问题就变得非常简单了 找出中位数 数据库中的二分查找 找出统计数据中的异常值 在邮箱中找出重复的邮件 不是特别典型的应用有 数据压缩 计算机图形 计算生物 负载平衡 编程语言中的排序算法 java中对于基本类型使用快排,对于引用类型使用归并排序.因为归并排序很稳定,而且保证复杂度为NlgN Java.C/C++中的快排都有以下特性: 对于小的子

LeetCode75----分类颜色(变相快排)

给定一个包含红色.白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色.白色.蓝色顺序排列. 此题中,我们使用整数 0. 1 和 2 分别表示红色.白色和蓝色. 注意:不能使用代码库中的排序函数来解决这道题. 示例: 输入: [2,0,2,1,1,0] 输出: [0,0,1,1,2,2] 进阶: 一个直观的解决方案是使用计数排序的两趟扫描算法.首先,迭代计算出0.1 和 2 元素的个数,然后按照0.1.2的排序,重写当前数组.你能想出一个仅使用常数空间的一趟

&lt;泛&gt; 多路快排

今天写一个多路快排函数模板,与STL容器兼容的. 我们默认为升序排序 因为,STL容器均为逾尾容器,所以我们这里采用的参数也是逾尾的参数 一.二路快排 基本思路 给你一个序列,先选择一个数作为基数,我们要做的是把小于该基数的数字放于左侧,大于该基数的数字放于右侧,最后将此基数放于中间,形成新的序列,我们把左侧序列和右侧序列分别像之前那样做,最后得到的序列即为顺序序列. 过程演示 比如给你一个序列   4  3  1  5  4  7  9  1  8  0 我们采用双指针扫描法,红色代表左指针所