侧边栏壁纸
博主头像
玄近安

抬猪高手,略懂代码

  • 累计撰写 9 篇文章
  • 累计创建 9 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

V8引擎中JS数组的sort排序原理

玄近安
2018-04-06 / 0 评论 / 1 点赞 / 10 阅读 / 0 字

前言

最近时不时的刷一刷算法题,经常需要用到数组的排序,但是因为本人算法并不是很好,自己写的排序简直是入不得眼,性能也是被别人的完爆,后来干脆也不自己写排序了,就直接用了js的sort函数,发现性能还不错。所以就决定研究一下sort的排序原理(各浏览器实现并不一致,此文限于V8)。

V8引擎的sort排序原理

排序原理无非也就是算法。
那我们不妨来看看v8在排序算法上是怎么做的:(重点关注:第2-3行的注释,第17-31、51-134行)
详见源码:array.js#L668

function InnerArraySort(array, length, comparefn) {
  // In-place QuickSort algorithm.
  // For short (length <= 10) arrays, insertion sort is used for efficiency.

  if (!IS_CALLABLE(comparefn)) {
    comparefn = function (x, y) {
      if (x === y) return 0;
      if (%_IsSmi(x) && %_IsSmi(y)) {
        return %SmiLexicographicCompare(x, y);
      }
      x = TO_STRING(x);
      y = TO_STRING(y);
      if (x == y) return 0;
      else return x < y ? -1 : 1;
    };
  }
  function InsertionSort(a, from, to) {
    for (var i = from + 1; i < to; i++) {
      var element = a[i];
      for (var j = i - 1; j >= from; j--) {
        var tmp = a[j];
        var order = comparefn(tmp, element);
        if (order > 0) {
          a[j + 1] = tmp;
        } else {
          break;
        }
      }
      a[j + 1] = element;
    }
  };

  function GetThirdIndex(a, from, to) {
    var t_array = new InternalArray();
    // Use both 'from' and 'to' to determine the pivot candidates.
    var increment = 200 + ((to - from) & 15);
    var j = 0;
    from += 1;
    to -= 1;
    for (var i = from; i < to; i += increment) {
      t_array[j] = [i, a[i]];
      j++;
    }
    t_array.sort(function(a, b) {
      return comparefn(a[1], b[1]);
    });
    var third_index = t_array[t_array.length >> 1][0];
    return third_index;
  }

  function QuickSort(a, from, to) {
    var third_index = 0;
    while (true) {
      // Insertion sort is faster for short arrays.
      if (to - from <= 10) {
        InsertionSort(a, from, to);
        return;
      }
      if (to - from > 1000) {
        third_index = GetThirdIndex(a, from, to);
      } else {
        third_index = from + ((to - from) >> 1);
      }
      // Find a pivot as the median of first, last and middle element.
      var v0 = a[from];
      var v1 = a[to - 1];
      var v2 = a[third_index];
      var c01 = comparefn(v0, v1);
      if (c01 > 0) {
        // v1 < v0, so swap them.
        var tmp = v0;
        v0 = v1;
        v1 = tmp;
      } // v0 <= v1.
      var c02 = comparefn(v0, v2);
      if (c02 >= 0) {
        // v2 <= v0 <= v1.
        var tmp = v0;
        v0 = v2;
        v2 = v1;
        v1 = tmp;
      } else {
        // v0 <= v1 && v0 < v2
        var c12 = comparefn(v1, v2);
        if (c12 > 0) {
          // v0 <= v2 < v1
          var tmp = v1;
          v1 = v2;
          v2 = tmp;
        }
      }
      // v0 <= v1 <= v2
      a[from] = v0;
      a[to - 1] = v2;
      var pivot = v1;
      var low_end = from + 1;   // Upper bound of elements lower than pivot.
      var high_start = to - 1;  // Lower bound of elements greater than pivot.
      a[third_index] = a[low_end];
      a[low_end] = pivot;

      // From low_end to i are elements equal to pivot.
      // From i to high_start are elements that haven't been compared yet.
      partition: for (var i = low_end + 1; i < high_start; i++) {
        var element = a[i];
        var order = comparefn(element, pivot);
        if (order < 0) {
          a[i] = a[low_end];
          a[low_end] = element;
          low_end++;
        } else if (order > 0) {
          do {
            high_start--;
            if (high_start == i) break partition;
            var top_elem = a[high_start];
            order = comparefn(top_elem, pivot);
          } while (order > 0);
          a[i] = a[high_start];
          a[high_start] = element;
          if (order < 0) {
            element = a[i];
            a[i] = a[low_end];
            a[low_end] = element;
            low_end++;
          }
        }
      }
      if (to - high_start < low_end - from) {
        QuickSort(a, high_start, to);
        to = low_end;
      } else {
        QuickSort(a, from, low_end);
        from = high_start;
      }
    }
  };

主要注释如下:

// In-place QuickSort algorithm.
// For short (length <= 10) arrays, insertion sort is used for efficiency.

根据上面的代码及注释可以了解,v8引擎sort排序策略是在数组长度小于10时使用 InsertionSort(插入排序),在大于10时使用 In-place QuickSort(原地分区版的快速排序,即使用较少的空间实现的快速排序)

后记

虽然只是使用了两种排序算法,但已经可以满足大部分的场景,如果有自己的特殊的场景或者说不适合使用插入排序和快排时,建议还是自己实现一套有针对性的排序策略,而不是使用原生的sort来进行排序,毕竟sort在不同的浏览器下实现也是有不同的。

附录

插入排序(稳定)

时间复杂度: 最差 O(n2) 平均 O(n2)
空间复杂度:O(1)

快速排序(不稳定)

时间复杂度: 最差 O(n2) 平均 O(n*log2n)
空间复杂度:O(log2n) ~ O(n)

1

评论区