zoukankan      html  css  js  c++  java
  • 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) // 右边组数据
    }
  • 相关阅读:
    解决在PDF文档中复制代码报错问题
    JAVA高级复习泛型
    SpringBoot高级监听原理
    SpringBoot整合其它框架整合Junit
    SpringBoot高级监控
    JAVA基础复习异常处理
    SpringBoot 整合 webservice 示例
    关于ScrollView的子View无法布满屏幕的问题
    Android开发中头疼的R文件问题
    博客园美化[SimpleMemory主题+tctip插件]
  • 原文地址:https://www.cnblogs.com/caimuguodexiaohongmao/p/11867659.html
Copyright © 2011-2022 走看看