v8--sort 方法 源码 (2) 快速排序法

v8 sort方法部分关于快速排序法的源码:

function QuickSort(a, from, to) {
    // Insertion sort is faster for short arrays.
    if (to - from <= 22) {
      InsertionSort(a, from, to);
      return;
    }
    var pivot_index = $floor($random() * (to - from)) + from;
    var pivot = a[pivot_index];
    // Pre-convert the element to a string for comparison if we know
    // it will happen on each compare anyway.
    var pivot_key =
      (custom_compare || %_IsSmi(pivot)) ? pivot : ToString(pivot);
    // Issue 95: Keep the pivot element out of the comparisons to avoid
    // infinite recursion if comparefn(pivot, pivot) != 0.
    a[pivot_index] = a[from];
    a[from] = pivot;
    var low_end = from;   // Upper bound of the elements lower than pivot.
    var high_start = to;  // Lower bound of the elements greater than pivot.
    // From low_end to i are elements equal to pivot.
    // From i to high_start are elements that haven't been compared yet.
    for (var i = from + 1; i < high_start; ) {
      var element = a[i];
      var order = Compare(element, pivot_key);
      if (order < 0) {
        a[i] = a[low_end];
        a[low_end] = element;
        i++;
        low_end++;
      } else if (order > 0) {
        high_start--;
        a[i] = a[high_start];
        a[high_start] = element;
      } else {  // order == 0
        i++;
      }
    }
    QuickSort(a, from, low_end);
    QuickSort(a, high_start, to);
  }

快速排序(Quicksort)是对冒泡排序的一种改进

基本思想:

通过一趟循环将要排序的数据分割成独立的两部分。

其中一部分的所有数据都比另外一部分的所有数据都要小。

然后再按此方法对这两部分数据分别进行快速排序。

整个排序过程可以递归进行,以此达到整个数据变成有序序列。

操作流程:

1、首先设定一个分界值,通过该分界值将数组分成左右两部分。 
2、将大于或等于分界值的数据集中到数组右边或左边,小于分界值的数据集中到数组的左边或右边。
总之要做到以分界值为界,一边的值全部要小于或大于另一边。
继续对两边的数组采用上述1、2操作。
 

跟着快速排序的基本思路和操作流程,对源码进行梳理和理解:

该函数传入参数是数组,起始索引,截至索引。
然后函数的退出条件:源码出于性能的考虑,当索引差值小于23时,使用插入排序方法操作数组,然后return。
这里暂不理会插入排序,把退出条件改为索引差值小于2。即索引对应的值只有一个时,此时无需比较,直接return。

 接着是取分界值,在传入的起始索引和截至索引的差值中任取一个索引,该索引对应的值即为分界值,保存副本

需要注意的是,key不应该在循环时被循环出来和自身比较

这里交换数组起始位置值和key_index值。

创建两个变量记录已交换位置的索引。

一个初值等于截至索引,每交换一个值到右边,则该变量减-1。最后该变量和截至索引即为递归函数的from和to。

另一个初值等于开始索引,每交换一个值到左边,则该变量加1。最后开始索引和该变量即为递归函数的from和to。

 

然后从起始索引加+1开始循环,这很好理解,第一个值是key,本身不需要分,而且只有一个值时没有左边右边的概念。

 

 根据比较结果的不同,分别对大于0,小于0的数组值进行分边。

比较结果大于0,放右边。右边的逻辑:(升降序是通过比较函数来控制)

 比较结果小于0,放左边。左边的逻辑:

 

 值相同时,保持原位即可。操作下一个值,令i++。

 循环结束后,数组已被边界值分成了两部分,每部分的起始、截至索引都已经记录。

接着把这两部分按照上述逻辑进行操作。循环往复。

 以上都是在原数组中操作,并未引入新数组。

例子:

      * 数组[40, 2, 7, 11, 20, 15]。key_index = 3; key = 11。与数组起始值交换后。
      * 此时数组[11, 2, 7, 40, 20, 15]。循环开始,操作a[1],计算结果小于0。分左边。
      * 即a[1] = a[0] = 11; a[0] = el = 2; low_end = 1; i = 2。数组:[2, 11, 7, 40, 20, 15],接着操作a[2]。计算结果小于0。分左边。
      * 即a[2] = a[1] = 11; a[1] = el = 7; low_end = 2; i = 3。数组:[2, 7, 11, 40, 20, 15],接着操作a[3]。计算结果大于0。分右边。
      * 即a[3] = a[5] = 15; a[5] = el = 40; high_start = 5; i = 3。数组:[2, 7, 11, 15, 20, 40],继续操作a[3]。计算结果大于0。分右边。
      * 即a[3] = a[4] = 20; a[4] = el = 15; high_start = 4; i = 3。数组:[2, 7, 11, 20, 15, 40],继续操作a[3]。计算结果大于0。分右边。
      * 即a[3] = a[3] = 20; a[3] = el = 20; high_start = 3; i = 3。数组:[2, 7, 11, 20, 15, 40],i < high_start判断失败,退出循环。
      * 此时 low_end = 2; high_start = 3; from = 0; to = 6。传入quickSort(a, from, low_end)、quickSort(a, high_start, to)递归执行。
 

源码在对未传入比较函数的情况下,对key 进行了toString处理,保证行为的一致性。

源码对比较函数进行了封装。在未传入比较函数时,对两个参数进行了toString处理。

代码:

function quickSort(a, from, to) {
  // 递归退出条件,当传入的起始索引和截至索引差值小于2时,此时对应的数据只有一个。已经无需比较
  if (to - from < 2) return;

  const key_index = Math.floor(Math.random() * (to - from)) + from; // key的索引,值的范围在起始索引和截至索引中任取
  const key = a[key_index]; // key 用于比较

  // 需要注意的是,key不应该在循环时被循环出来和自身比较
  // 这里交换数组起始位置值和key值。
  a[key_index] = a[from];
  a[from] = key;

  let high_start = to; // 数组分边后其中一边的开始索引
  let low_end = from; // 数组分边后另一边的截至索引

  // 循环从起始位置+1开始,判断条件中high_start,每交换一个值到右边,该值就减1
  for (let i = from + 1; i < high_start;) {
    let el = a[i]; // 当前副本,需要被分边
    let result = el - key; // 比较。可封装成比较函数,实现升降序

    if (result > 0) {
      /**
      * 右边。high_start--;把high_start对应的值赋给a[i]。
      * 此时a[i]已经变了,需要重新比较。故不能i++。把el赋给a[high_start]。el完成分边
      */
      high_start--;
      a[i] = a[high_start];
      a[high_start] = el; 
    } else if (result < 0) {
      /**
      * 左边。
      * 把左边low_end对应的值赋给a[i]。然后el赋给a[low_end],el完成分边。然后low_end++。
      * 此时a[i]是key值。无需比较。继续操作下一个值。即i++
      * 当循环至边界索引key_index时,该索引对应的是数组起始值。
      * 由此数组每个值都可以与key进行比较,然后被分边。
      */
      a[i] = a[low_end];
      a[low_end] = el;
      low_end++;
      i++;
    } else {
      i++;
    }
  }
  // 此时递归,传入起始索引和截至索引,对两部分数据的进行上述操作
  quickSort(a, from, low_end) // 左边组数据
  quickSort(a, high_start, to) // 右边组数据
}

 

posted @ 2019-11-15 16:37  chulai9527  阅读(342)  评论(0编辑  收藏  举报