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)是對冒泡排序的一種改進

基本思想:數組

經過一趟循環將要排序的數據分割成獨立的兩部分。app

其中一部分的全部數據都比另一部分的全部數據都要小。dom

而後再按此方法對這兩部分數據分別進行快速排序。函數

整個排序過程能夠遞歸進行,以此達到整個數據變成有序序列。性能

操做流程:ui

一、首先設定一個分界值,經過該分界值將數組分紅左右兩部分。  
二、將大於或等於分界值的數據集中到數組右邊或左邊,小於分界值的數據集中到數組的左邊或右邊。
總之要作到以分界值爲界,一邊的值所有要小於或大於另外一邊。
繼續對兩邊的數組採用上述一、2操做。
 

跟着快速排序的基本思路和操做流程,對源碼進行梳理和理解:

該函數傳入參數是數組,起始索引,截至索引。
而後函數的退出條件:源碼出於性能的考慮,當索引差值小於23時,使用插入排序方法操做數組,而後return。
這裏暫不理會插入排序,把退出條件改成索引差值小於2。即索引對應的值只有一個時,此時無需比較,直接return。

 接着是取分界值,在傳入的起始索引和截至索引的差值中任取一個索引,該索引對應的值即爲分界值,保存副本spa

須要注意的是,key不該該在循環時被循環出來和自身比較code

這裏交換數組起始位置值和key_index值。

建立兩個變量記錄已交換位置的索引。orm

一個初值等於截至索引,每交換一個值到右邊,則該變量減-1。最後該變量和截至索引即爲遞歸函數的from和to。blog

另外一個初值等於開始索引,每交換一個值到左邊,則該變量加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) // 右邊組數據
}
相關文章
相關標籤/搜索