sort 排序方法的實現(xiàn)原理

1. sort 方法的基本使用

sort 方法是對數(shù)組元素進(jìn)行排序散罕,默認(rèn)排序順序是先將元素轉(zhuǎn)換為字符串横腿,然后再進(jìn)行排序

arr.sort([compareFunction])

其中 compareFunction 用來指定按某種順序進(jìn)行排列的函數(shù)聊品,如果省略不寫,元素按照轉(zhuǎn)換為字符串的各個字符的 Unicode 位點(diǎn)進(jìn)行排序

const months = ['March', 'Jan', 'Feb', 'Dec'];
months.sort();
console.log(months); // ["Dec", "Feb", "Jan", "March"]

const array1 = [1, 30, 4, 21, 100000];
array1.sort();
console.log(array1); // [1, 100000, 21, 30, 4]

從上面的執(zhí)行結(jié)果可以看出百新,如果不加參數(shù)檬贰,在第二段代碼中,21 會排到 4 的前面遵湖。這樣按照從小到大的邏輯是行不通的悔政,如果想要按照從小到大排序或者從大到小排序,那么上面的代碼就需要調(diào)整為下面這樣

const array1 = [1, 30, 4, 21, 100000];
array1.sort((a,b) => b - a);
console.log(array1); // [100000, 30, 21, 4, 1]

const array1 = [1, 30, 4, 21, 100000];
array1.sort((a,b) => a - b);
console.log(array1); // [1, 4, 21, 30, 100000]

如果指明了 compareFunction 參數(shù) 延旧,那么數(shù)組會按照調(diào)用該函數(shù)的返回值排序谋国,即 a 和 b 是兩個將要被比較的元素:

  • 如果 compareFunction(a, b)小于 0,那么 a 會被排列到 b 之前
  • 如果 compareFunction(a, b)等于 0迁沫,a 和 b 的相對位置不變
  • 如果 compareFunction(a, b)大于 0芦瘾,b 會被排列到 a 之前

2. sort 方法的底層實現(xiàn)

下面的源碼均來自 V8 源碼中關(guān)于 sort 排序的摘要闷盔,地址:V8 源碼 sort 排序部分

如果要排序的元素個數(shù)是 n 的時候,那么就會有以下幾種情況:

  1. 當(dāng) n<=10 時旅急,采用插入排序
  2. 當(dāng) n>10 時,采用三路快速排序
  3. 10<n <=1000牡整,采用中位數(shù)作為哨兵元素
  4. n>1000藐吮,每隔 200~215 個元素挑出一個元素,放到一個新數(shù)組中逃贝,然后對它排序谣辞,找到中間位置的數(shù),以此作為中位數(shù)

2.1 為什么元素個數(shù)少的時候要采用插入排序

雖然插入排序理論上是平均時間復(fù)雜度為 O(n^2) 的算法沐扳,快速排序是一個平均 O(nlogn) 級別的算法泥从。但是它們也有最好的時間復(fù)雜度情況,而插入排序在最好的情況下時間復(fù)雜度是 O(n)

在實際情況中兩者的算法復(fù)雜度前面都會有一個系數(shù)沪摄,當(dāng) n 足夠小的時候躯嫉,快速排序 nlogn 的優(yōu)勢會越來越小。倘若插入排序的 n 足夠小杨拐,那么就會超過快排祈餐。而事實上正是如此,插入排序經(jīng)過優(yōu)化以后哄陶,對于小數(shù)據(jù)集的排序會有非常優(yōu)越的性能帆阳,很多時候甚至?xí)^快排。因此屋吨,對于很小的數(shù)據(jù)量蜒谤,應(yīng)用插入排序是一個非常不錯的選擇

2.2 為什么要花這么大的力氣選擇哨兵元素?

因為快速排序的性能瓶頸在于遞歸的深度至扰,最壞的情況是每次的哨兵都是最小元素或者最大元素鳍徽,那么進(jìn)行 partition(一邊是小于哨兵的元素,另一邊是大于哨兵的元素)時渊胸,就會有一邊是空的旬盯。如果這么排下去,遞歸的層數(shù)就達(dá)到了 n , 而每一層的復(fù)雜度是 O(n)翎猛,因此快排這時候會退化成 O(n^2) 級別

因此讓哨兵元素盡可能地處于數(shù)組的中間位置胖翰,讓最大或者最小的情況盡可能少

官方實現(xiàn)的 sort 排序算法的代碼基本結(jié)構(gòu)

function ArraySort(comparefn) {
  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) {
  // 比較函數(shù)未傳入
  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;
    }

  }

  // 元素個數(shù)大于1000時尋找哨兵元素
  function GetThirdIndex(a, from, to) {   
    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;
  }

  // 快速排序?qū)崿F(xiàn)
  function QuickSort(a, from, to) {  
    //哨兵位置
    var third_index = 0;

    while (true) {
      if (to - from <= 10) {
        InsertionSort(a, from, to); // 數(shù)據(jù)量小,使用插入排序切厘,速度較快
        return;
      }

      third_index = to - from > 1000
        ? GetThirdIndex(a, from, to)
        : from + ((to - from) >> 1); // 小于1000 直接取中點(diǎn)

      // 下面開始快排
      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++;
          }
        }
      }

      // 快排的核心思路萨咳,遞歸調(diào)用快速排序方法
      if (to - high_start < low_end - from) {
        QuickSort(a, high_start, to);
        to = low_end;
      } else {
        QuickSort(a, from, low_end);
        from = high_start;
      }
    }
  }
}

3. 總結(jié)

排序算法 時間復(fù)雜度(最好) 時間復(fù)雜度(平均) 時間復(fù)雜度(最差) 空間復(fù)雜度 穩(wěn)定性
快速排序 O(nlogn) O(nlogn) O(n^2) O(nlogn) 不穩(wěn)定
插入排序 O(n) O(n^2) O(n^2) O(1) 穩(wěn)定
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市疫稿,隨后出現(xiàn)的幾起案子培他,更是在濱河造成了極大的恐慌鹃两,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舀凛,死亡現(xiàn)場離奇詭異俊扳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)猛遍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門馋记,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人懊烤,你說我怎么就攤上這事梯醒。” “怎么了腌紧?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵茸习,是天一觀的道長。 經(jīng)常有香客問我壁肋,道長号胚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任墩划,我火速辦了婚禮涕刚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘乙帮。我一直安慰自己杜漠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布察净。 她就那樣靜靜地躺著驾茴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪氢卡。 梳的紋絲不亂的頭發(fā)上锈至,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機(jī)與錄音译秦,去河邊找鬼峡捡。 笑死,一個胖子當(dāng)著我的面吹牛筑悴,可吹牛的內(nèi)容都是我干的们拙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼阁吝,長吁一口氣:“原來是場噩夢啊……” “哼砚婆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起突勇,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤装盯,失蹤者是張志新(化名)和其女友劉穎坷虑,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體埂奈,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡迄损,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了账磺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片海蔽。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖绑谣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拗引,我是刑警寧澤借宵,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站矾削,受9級特大地震影響壤玫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜哼凯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一欲间、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧断部,春花似錦猎贴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蔑祟,卻和暖如春趁耗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背疆虚。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工苛败, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人径簿。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓罢屈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親牍帚。 傳聞我的和親對象是個殘疾皇子儡遮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容