Web高級 JavaScript中的算法

算法

排序算法

  • 穩定排序
    待排序序列中相等元素在排序完成後,原有前後順序不變。
  • 非穩定排序
  • 有序度
    待排序序列中有序關係的元素對個數。
  • 逆序度

1. 插入排序

  • 遍歷有序數組,對比待插入的元素大小,找到位置。把該位置後的元素依次後移。
  • 時間複雜度: O(N2)

2. 選擇排序

  • 區分已排序區間和未排序區間,每次從未排序區間選擇最小的放在已排序區間的最後。
  • 時間複雜度: O(N2)

3. 歸併排序

  • 將待排序元素從中間分爲二半,對左右分別遞歸排序,最後合併在一塊兒。
  • 思想: 分治思想
  • 時間複雜度: O(nLogN)
  • 常見實現: 遞歸
  • 特色: 非原地排序,須要藉助額外存儲空間,在數據量較大時空間複雜度較高。

4. 快速排序

  • 選擇一個pivot分區點,將小於pivot的數據放到左邊,大於的放到右邊,在用一樣方法遞歸排序左右。
  • 思想: 分治思想
  • 時間複雜度: O(nLogN)
  • 常見實現: 遞歸
  • 特色: 非穩定原地排序,另外pivot選擇比較重要,常見的有隨機選擇,前中後三值取中值等。

5. 桶排序

  • 將待排序數據根據值區間劃分m個桶,再對桶內進行排序,最後合併
  • 要求: 待排序數據值範圍能比較輕鬆劃分爲m個區間(桶)
  • 思想: 分治思想
  • 時間複雜度: O(n)
  • 場景: 外部排序, 如TB級別數據,設置m個桶,將符合值區間的元素分別放入不一樣桶,再對桶分別排序

6. 基數排序

  • 使用穩定排序算法,從最後一位開始進行排序。
  • 要求: 帶排序數據能夠分割出獨立的'位'來進行比較,並且位之間有遞進關係
  • 時間複雜度: O(m*n)
  • 場景: 電話號碼,英文字典排序等

7. 二分查找

  • 待查找數據與中間數對比,若小與則在左邊遞歸二分,若大於則在右邊遞歸二分
  • 要求:待查找集合爲有序'數組'
  • 時間複雜度: O(LogN)
  • 場景:數據量不能太大,也不能過小

JavaScript字符串查找算法

溫故而知新,最新在複習數據結構和算法,結合Chrome的V8源碼,看看JS中的一些實現。
首先咱們看看字符串查找在V8中是使用的哪一種算法。
咱們知道JS中的String是繼承於Object,源碼以下:javascript

// https://github.com/v8/v8/blob/master/src/objects/string.cc
// Line942 字符串查找方法定義
int String::IndexOf(Isolate* isolate, Handle<String> receiver,Handle<String> search, int start_index) {
...
// 省略多餘代碼,根據返回值可見調用SearchString方法
// Line968
  return SearchString<const uc16>(isolate, receiver_content, pat_vector,start_index);
}

// Line18
#include "src/strings/string-search.h"
// 根據頭引入查找該文件
// https://github.com/v8/v8/blob/master/src/strings/string-search.h
// 重點來了

// Line 20
*根據如下注釋咱們能夠知道, JS中的字符串查找使用的BM查找算法。模式串最小匹配長度爲7
class StringSearchBase {
 protected:
  // Cap on the maximal shift in the Boyer-Moore implementation. By setting a
  // limit, we can fix the size of tables. For a needle longer than this limit,
  // search will not be optimal, since we only build tables for a suffix
  // of the string, but it is a safe approximation.
  static const int kBMMaxShift = Isolate::kBMMaxShift;

  // Bad-char shift table stored in the state. It's length is the alphabet size.
  // For patterns below this length, the skip length of Boyer-Moore is too short
  // to compensate for the algorithmic overhead compared to simple brute force.
  static const int kBMMinPatternLength = 7;
};

// 重點的重點 Line 54
template <typename PatternChar, typename SubjectChar>
class StringSearch : private StringSearchBase {
 public:
  StringSearch(Isolate* isolate, Vector<const PatternChar> pattern)
      : isolate_(isolate),
        pattern_(pattern),
        start_(Max(0, pattern.length() - kBMMaxShift)) {
    if (sizeof(PatternChar) > sizeof(SubjectChar)) {
      if (!IsOneByteString(pattern_)) {
        strategy_ = &FailSearch;
        return;
      }
    }
    // 獲取模式串字符長度
    int pattern_length = pattern_.length();
    // 若是小於7
    // static const int kBMMinPatternLength = 7;
    if (pattern_length < kBMMinPatternLength) {
      if (pattern_length == 1) {
          //若是待查找字符串長度爲1,使用單字符查找
        strategy_ = &SingleCharSearch;
        return;
      }
      //不然使用線性查找
      strategy_ = &LinearSearch;

      return;
    }
    // 若是大於7,使用BM查找算法
    strategy_ = &InitialSearch;
  }

JavaScript數組排序算法

咱們先看看各瀏覽器的排序算法是不是穩定排序java

  • IE6+: stable
  • Firefox < 3: unstable
  • Firefox >= 3: stable
  • Chrome < 70: unstable
  • Chrome >= 70: stable
  • Opera < 10: unstable
  • Opera >= 10: stable
  • Safari 4: stable
  • Edge: unstable for long arrays (>512 elements)

在V8 v7.0/Chrome70之後,源碼不在包含/src/js目錄,相應的遷移到了/src/torque
關於V8 Torque的詳情能夠參考V8 Torquegit

咱們先看看Chrome70之前的Array.prototype.sort的實現

// https://github.com/v8/v8/blob/6.9.454/src/js/array.js
// Line 802 
// 如下能夠知道sort方法返回InnerArraySort結果
DEFINE_METHOD(
  GlobalArray.prototype,
  sort(comparefn) {
    if (!IS_UNDEFINED(comparefn) && !IS_CALLABLE(comparefn)) {
      throw %make_type_error(kBadSortComparisonFunction, comparefn);
    }

    var array = TO_OBJECT(this);
    var length = TO_LENGTH(array.length);
    return InnerArraySort(array, length, comparefn);
  }
);

// Line645
// 咱們接下來看InnerArraySort的定義
function InnerArraySort(array, length, comparefn) {
  // In-place QuickSort algorithm.
  // For short (length <= 10) arrays, insertion sort is used for efficiency.
  // 原地快排算法
  // 若是長度小於10,使用插入排序
  
  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 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 - high_start < low_end - from) {
        QuickSort(a, high_start, to);
        to = low_end;
      } else {
        QuickSort(a, from, low_end);
        from = high_start;
      }
    }
  };

  if (length < 2) return array;
  QuickSort(array, 0, num_non_undefined);
  return array;
}

快排所帶來的問題

  • 衆所周知快排是非穩定排序算法,由此帶來了不少問題
  • V8在7.0之後將JS的數組排序更改成穩定排序算法
  • 在V8的博客上有一篇詳細的介紹關於排序算法更改的文章

再看看Chrome70之後的排序實現

// https://github.com/v8/v8/blob/4b9b23521e6fd42373ebbcb20ebe03bf445494f9/third_party/v8/builtins/array-sort.tq

// Line1236
transitioning macro
  ArrayTimSortImpl(context: Context, sortState: SortState, length: Smi) {
    if (length < 2) return;
    let remaining: Smi = length;

    // March over the array once, left to right, finding natural runs,
    // and extending short natural runs to minrun elements.
    let low: Smi = 0;
    const minRunLength: Smi = ComputeMinRunLength(remaining);
    while (remaining != 0) {
      let currentRunLength: Smi = CountAndMakeRun(low, low + remaining);

      // If the run is short, extend it to min(minRunLength, remaining).
      // 當前執行長度小於最小長度
      if (currentRunLength < minRunLength) {
        const forcedRunLength: Smi = SmiMin(minRunLength, remaining);
        //使用插入排序
        BinaryInsertionSort(low, low + currentRunLength, low + forcedRunLength);
        currentRunLength = forcedRunLength;
      }

      // Push run onto pending-runs stack, and maybe merge.
      PushRun(sortState, low, currentRunLength);

      MergeCollapse(context, sortState);

      // Advance to find next run.
      low = low + currentRunLength;
      remaining = remaining - currentRunLength;
    }

    //其餘時候使用歸併排序
    MergeForceCollapse(context, sortState);
    assert(GetPendingRunsSize(sortState) == 1);
    assert(GetPendingRunLength(sortState.pendingRuns, 0) == length);
  }

  // Line485

  // BinaryInsertionSort is the best method for sorting small arrays: it
  // does few compares, but can do data movement quadratic in the number of
  // elements. This is an advantage since comparisons are more expensive due
  // to calling into JS.
  //
  //  [low, high) is a contiguous range of a array, and is sorted via
  // binary insertion. This sort is stable.
  //
  // On entry, must have low <= start <= high, and that [low, start) is
  // already sorted. Pass start == low if you do not know!.
  macro BinaryInsertionSort(implicit context: Context, sortState: SortState)(
      low: Smi, startArg: Smi, high: Smi) {
    assert(low <= startArg && startArg <= high);

    const workArray = sortState.workArray;

    let start: Smi = low == startArg ? (startArg + 1) : startArg;

    for (; start < high; ++start) {
      // Set left to where a[start] belongs.
      let left: Smi = low;
      let right: Smi = start;

      const pivot = workArray.objects[right];

      // Invariants:
      //   pivot >= all in [low, left).
      //   pivot  < all in [right, start).
      assert(left < right);

      // Find pivot insertion point.
      while (left < right) {
        const mid: Smi = left + ((right - left) >> 1);
        const order = sortState.Compare(pivot, workArray.objects[mid]);

        if (order < 0) {
          right = mid;
        } else {
          left = mid + 1;
        }
      }
      assert(left == right);

      // The invariants still hold, so:
      //   pivot >= all in [low, left) and
      //   pivot  < all in [left, start),
      //
      // so pivot belongs at left. Note that if there are elements equal
      // to pivot, left points to the first slot after them -- that's why
      // this sort is stable. Slide over to make room.
      for (let p: Smi = start; p > left; --p) {
        workArray.objects[p] = workArray.objects[p - 1];
      }
      workArray.objects[left] = pivot;
    }
  }

  // Regardless of invariants, merge all runs on the stack until only one
  // remains. This is used at the end of the mergesort.
  transitioning macro
  MergeForceCollapse(context: Context, sortState: SortState) {
    let pendingRuns: FixedArray = sortState.pendingRuns;

    // Reload the stack size becuase MergeAt might change it.
    while (GetPendingRunsSize(sortState) > 1) {
      let n: Smi = GetPendingRunsSize(sortState) - 2;

      if (n > 0 &&
          GetPendingRunLength(pendingRuns, n - 1) <
              GetPendingRunLength(pendingRuns, n + 1)) {
        --n;
      }
      MergeAt(n);
    }
  }
相關文章
相關標籤/搜索