认识sort函数

173 阅读6分钟

sort函数作用:

(1)直接修改原数组。

(2)默认按照数组元素第一位的ASCII码从小到大排列。

(3)可以设置下列的比较器函数来控制升序,降序或者打乱。

在使用sort()函数时,函数参数如果不设置的话,会以默认方式进行排序,就是以字母顺序进行排序,准确的讲就是按照字符编码的顺序进行排序。

var arr = [3,2,3,34,12,23,234,84,9];
arr.sort();

// 结果: 12,2,23,234,3,3,34,84,9

sort函数基本信息

语法:
arr.sort([compareFunction])
参数:
compareFunction [可选]
用于数组排序规则的比较函数。如果不含有该参数,数组元素按照转换字符串的各个字符的Unicode编码顺序进行排序。
    compareFunction 参数:
    firstElement   用于比较的第一个元素
    secondElement  用于比较的第二个元素``
返回值:
排序后的数组,返回的是当前数组。

默认情况下按升序排列数组,但是是根据字符串UniCode码进行比较。sort()方法进行排序时会在每一项上调用String()转型函数,然后通过字符串UniCode码来决定顺序,即使数组的元素都是number类型,也会把它转换成字符串再比较。

在V8 引擎 sort 函数使用了两种排序算法,分别是,插入排序InsertionSort 和 快速排序QuickSort。

  • 当 n<=10 时,采用插入排序
  • 当 n>10 时,采用三路快速排序
  • 10<n <=1000,采用中位数作为哨兵元素;
  • n>1000,每隔 200~215 个元素挑出一个元素,放到一个新数组中,然后对它排序,找到中间位置的数,以此作为中位数。

在v8引擎中,对sort方法提供了2种排序算法:插入排序及快排序。 作者:dxylilac
链接:zhuanlan.zhihu.com/p/33626637\ 来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
\

sort使用方法:

var arr=[];
arr.sort();//默认排序
arr.sort(comparefn(a,b));//自定义排序比较方法

当没有参数传入的时候,其排序顺序默认为,将待排序数据转换为字符串,并按照Unicode序列排序;当然,比较函数可以自定义,自定义排序函数需要返回值,其返回值为-1,0,1,分别表示a<b, a=b, a>b.

当数组长度小于等于10的时候,采用插入排序,大于10的时候,采用快排。

插入排序:

v8源码

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

a表示数组,from表示数组开始排序的位置下标,to表示数组结束位置下标。根据上述代码,var arr=[7,3,5,2,4], 那么arr.sort()排序过程如下:

7 3 5 2 4

3 7 5 2 4

3 5 7 2 4

2 3 5 7 4

2 3 4 5 7

虽然插入排序的复杂度是n^2,但是由于数据量很小,因此是常量的复杂度,效率很高,而且插入排序是一个稳定的排序算法。

快速排序:

v8源码

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) {//数组长度小于等于10的时候,插入排序
        InsertionSort(a, from, to);
        return;
      }
      if (to - from > 1000) {//数组长度大于1000的时候,获取关键值
        third_index = GetThirdIndex(a, from, to);
      } else {//长度大于10小于等于1000的时候,取数组中间的元素作为关键值
        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;
      }
    }
  };

可见,v8的sort对于长度大于1000的数组,采用的是快排与插入排序混合的方式进行排序的,因为,当数据量很小的时候,插入排序效率优于快排。

快排需要选择一个关键值,并且以关键值作为基准开始排序,比关键值的值大的放在关键值右边,小的放在关键值左边,由此完成一趟排序;然后再对关键值左侧的数据以及右侧的数据分别执行快排。如果每次关键字选择都是一组数据的最小值或者最大值,那么快排的复杂度将会达到n^2,就跟冒泡没什么区别了。在快排算法中,最优的关键值,是这组数据最中间位置的值,这样才能可以使得排序算法复杂度达到nlogn. 因此,关键值的选择尤为重要。

v8快排关键值的获取:

  1. 获取临时关键值tmp:
    1. 对于大于10小于等于1000的数据量,tmp为这组数据中间位置的值
    2. 对于大于1000的数据,根据一定步长从待排序的数组里面获取一组临时数据,对临时数据排序,再获得临时数据中最中间位置的值,作为待排序数组的tmp。步长的计算跟数组的长度有关系,其计算方法如下:
      步长 = 200 + 数组长度&15;
    3. 将数组的长度转换为二进制后,与1111按位与,其结果与200相加,作为步长。
  2. 计算关键值key:
    获取数组第一个数a0, 最后一个数an;
    比较a0, tmp, an, 赋值给v0, v1, v2, 保证v0<=v1<=v2;
    关键值 = v1.

通过对关键值的选取,能最大程度保证快排的复杂度趋近于平均复杂度,即nlogn.

例1: var arr=[3,6,2,7,9,23,5,13,12,6,73,34,55,22,34],arr.sort()排序顺序如下

  • 第一趟排序:
    • 数组长度为15,获取临时关键值tmp=(15-0)>>1+0=7;
    • a0=arr[0]=3, an=arr[14]=34, tmp=arr[7]=13, 因此v0=3, v1=13, v2=34, 因此key=13;
    • 排序结果:3,2,7,9,6,12,5,6,13,23,73,34,55,22,34,小于13的都在其左侧,大于13的都在其右侧;
  • 第二趟排序:
    分别对13左侧的数据,及13右侧的数据调用快排方法;
    由于13左侧及右侧数据量都小于10,因此会调用插入排序,因此接下来排序结果如下:
    • 13左侧:
      2,3,7,9,6,12,5,6
      2,3,7,9,6,12,5,6
      2,3,7,9,6,12,5,6
      2,3,6,7,9,12,5,6
      2,3,6,7,9,12,5,6
      2,3,5,6,7,9,12,6
      2,3,5,6,6,7,9,12
    • 13右侧:
      23,73,34,55,22,34
      23,34,73,55,22,34
      23,34,55,73,22,34
      22,23,34,55,73,34
      22,23,34,34,55,73
    • 最终结果2,3,5,6,6,7,9,12,13,22,23,34,34,55,73

快排的平均时间复杂度是nlogn,在排序算法中属于效率最高的。快排是一种不稳定的排序算法,但是一般情况下稳定或者不稳定对我们没有特别大的影响,但是对稳定性要求高的排序,就不能使用快排了。

源码中的sort的基本结构

    CHECK_OBJECT_COERCIBLE(this,"Array.prototype.sort");
    var array = TO_OBJECT(this);
    var length = TO_LENGTH(array.length);
    return InnerArraySort(array, length, comparefn);
}
function InnerArraySort(array, length, comparefn) {
// 比较函数未传入
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) {   // 元素个数大于1000时寻找哨兵元素
  var t_array = new InternalArray();
  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) {
        if (to - from <= 10) {
          InsertionSort(a, from, to); // 数据量小,使用插入排序,速度较快
          return;
        }
        if (to - from > 1000) {
          third_index = GetThirdIndex(a, from, to);
        } else {
          // 小于1000 直接取中点
          third_index = from + ((to - from) >> 1);
        }
        // 下面开始快排
        var v0 = a[from];
        var v1 = a[to - 1];
        var v2 = a[third_index];
        var c01 = comparefn(v0, v1);
        if (c01 > 0) {
          var tmp = v0;
          v0 = v1;
          v1 = tmp;
        }
        var c02 = comparefn(v0, v2);
        if (c02 >= 0) {
          var tmp = v0;
          v0 = v2;
          v2 = v1;
          v1 = tmp;
        } else {
          var c12 = comparefn(v1, v2);
          if (c12 > 0) {
            var tmp = v1;
            v1 = v2;
            v2 = tmp;
          }
        }
        a[from] = v0;
        a[to - 1] = v2;
        var pivot = v1;
        var low_end = from + 1; 
        var high_start = to - 1;
        a[third_index] = a[low_end];
        a[low_end] = pivot;
        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;
        }
    }
  }

引用文章来源:

作者:Z_Maple
链接:juejin.cn/post/697798… 来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

标题: JSsort函数底层原理 - 知乎 网址:www.zhihu.com/search?type…

标题:javascript中sort()函数的理解 - 知乎 网址:zhuanlan.zhihu.com/p/22931269