Javascript和快速排序

Javascript和快速排序

這里以遞歸為例,參考自慕課網(wǎng)劉波波老師的C++版本實現(xiàn)

普通快排

上過大學數(shù)據(jù)結(jié)構(gòu)課程的人都知道,快速排序的核心就是選定一個哨兵,然后把它作為標準,對數(shù)據(jù)進行操作,把小的放前面,把大的放后面。然后執(zhí)行這個過程若干次,就得到了最終的結(jié)果。
這個過程,實踐了算法中的分治法,即把復雜的模塊分成幾個簡單的模塊分而治之,達到事半功倍的效果。
在這里,我推薦一個算法可視化網(wǎng)站:http://zh.visualgo.net/zh/sorting
很多常見的算法可以在上面快速的理解處理過程。
代碼如下:

var quickSort = function (arr) {
    __quickSort(arr, 0, arr.length - 1);
    return arr;
}
var __quickSort = function (arr, start, end) {
    if (start > end) {
        return;
    }
    // p是哨兵排序完畢時的位置
    var p = partition(arr, start, end);
    __quickSort(arr, start, p - 1);
    __quickSort(arr, p + 1, end);
    return arr;
}

function partition(arr, start, end) {
    // v是哨兵
    var v = arr[start];
    // j是分界線
    var j = start;
    //從第二個元素開始比較
    for (var i = start + 1; i <= end; i++) {
        if (arr[i] < v) {
            //如果第i個元素比哨兵小,就和右邊的大數(shù)交換,j就往右移了一位
            [arr[j + 1], arr[i]] = [arr[i], arr[j + 1]];
            // var tmp = arr[start];
            // arr[start] = arr[j];
            // arr[j] = tmp;
            j++;
        }
    }
    //把哨兵放在本應屬于他的位置。
    [arr[start], arr[j]] = [arr[j], arr[start]];
    // var tmp = arr[start];
    // arr[start] = arr[j];
    // arr[j] = tmp;
    return j;
}

快排性能測試

怎么知道我們算法的性能呢?我們可以新建一個模塊,自動生成測試用例進行測試并且打印出耗時。
test.js

/**
 * 
 * 自定義測試用例
 * @param {any} n 元素個數(shù)
 * @param {any} rangeL  范圍內(nèi)最小數(shù),開區(qū)間 
 * @param {any} rangeH  范圍內(nèi)最大數(shù),開區(qū)間
 * @returns arr 數(shù)組
 */
function genTest(n, rangeL, rangeH) {
    if (rangeH < rangeL) {
        return;
    }
    var arr = [];
    for (var i = 0; i < n; i++) {
        arr.push(Math.floor(Math.random() * (rangeH - rangeL)) + rangeL);
    }
    return arr;
}

/**
 * 
 * 耗時計算
 * @param {any} func 要測試的回調(diào)函數(shù)
 * @param {any} n 元素個數(shù)
 * @param {any} [m=n] 最大值,默認為n
 */
function dif(func,n,m=n) {
    console.time(`${func.name}算法耗時`);
    func(genTest(n, 0, m));
    console.timeEnd(`${func.name}算法耗時`);    
}

exports.genTest = genTest;
exports.dif = dif;

這里有一個genTest函數(shù)用于產(chǎn)生一個數(shù)組,還有一個dif函數(shù)用于打印耗時。
然后在之前寫的代碼里導入,測試一個100萬個數(shù)據(jù)的程序:

var test = require('./test');
...
test.dif(quickSort, 1000000)
解構(gòu)賦值交換法快排耗時
傳統(tǒng)交換變量快排耗時

優(yōu)化快排

快排常常有兩種邊界情況需要被考慮:
1.如果待排序的數(shù)組是有序的,復雜度會到O(n^2)。
2.數(shù)組元素重復個數(shù)過多也會造成性能上的損耗。
所以針對這兩種情況要進行優(yōu)化:哨兵要隨機選,針對重復的元素還要再加一個指針。

三路快排

這種快排通常被人叫做三路快排,因為它的代碼中有三個指針,分別標識著小于哨兵的部分/等于哨兵的部分/大于哨兵的部分。

代碼如下:

var test = require('./test');

//更加先進的:三路快排,可能是交換性能消耗大
var quickSortThreeWays = function (arr) {
    var len = arr.length;
    __quickSortThreeWays(arr, 0, len - 1);
    return arr;
}

function __quickSortThreeWays(arr, start, end) {
    if (start > end) {
        return;
    }
    var rand = Math.round(Math.random() * (end - start));
    //partition
    [arr[start], arr[rand + start]] = [arr[rand + start], arr[start]];
    var v = arr[start];

    var lt = start; //arr[start+1...lt]<v
    var gt = end + 1; //arr[gt...end]>v
    var i = start + 1;
    while (i < gt) {
        if (arr[i] < v) {
            [arr[i], arr[lt + 1]] = [arr[lt + 1], arr[i]];
            lt++;
            i++;
        } else if (arr[i] > v) {
            [arr[i], arr[gt - 1]] = [arr[gt - 1], arr[i]];
            gt--;
        } else { //arr[i]==v
            i++;
        }
    }
    [arr[start], arr[lt]] = [arr[lt], arr[start]];
    __quickSortThreeWays(arr, start, lt - 1);
    __quickSortThreeWays(arr, gt, end);
}

可以看到lt——i之間是小于哨兵的、i——gt之間是等于哨兵的,gt——end是大于哨兵的。

//測試三路快排性能
test.dif(quickSortThreeWays, 1000000)
三路快排耗時.png

由于我這里大量使用了解構(gòu)賦值交換元素,所以也造成了性能上的損耗,再加上js本身不太適合實現(xiàn)底層算法,所以看上去還沒有普通快排快,不過在C++寫法中是絕對快出一籌的。

特殊情況

大量重復元素

我們把數(shù)組限定在0~10的整數(shù)范圍內(nèi),同樣生成一百萬個。


三路快排重復元素耗時.png
快排重復元素耗時.png
快排解構(gòu)賦值交換元素耗時.png
test.dif(quickSort, 1000000,100)
test.dif(quickSortThreeWays, 1000000,100)

可以看出這時候三路快排已經(jīng)比快排快了,而且還是沒用解構(gòu)賦值的快排,如果用解構(gòu)賦值普通快排會溢出。
至于近乎有序的數(shù)組大家可以自己嘗試寫一個測試用例生成來測試~

其他寫法

阮一峰前輩的博客里也有相關(guān)的實現(xiàn),因為用的js原生api比較多,所以我稱它為js寫法,其實還有很多寫法,這里就不一一列舉了

var quickSort = function(arr) {
  if (arr.length <= 1) { return arr; }
  var pivotIndex = Math.floor(arr.length / 2);
  var pivot = arr.splice(pivotIndex, 1)[0];
  var left = [];
  var right = [];
  for (var i = 0; i < arr.length; i++){
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  return quickSort(left).concat([pivot], quickSort(right));
};

可以看出,阮前輩使用了splice、concat等眾多js本身的api,不過語義上好理解一些,那性能如何呢?我們來看看:

JS快排.png

可以看出,這種寫法的性能是比較低的。那兩種特殊情況同理。

JS的sort()函數(shù)

不過實際上,JS早已幫我們內(nèi)置好了排序函數(shù),那就是sort()函數(shù)。關(guān)于sort()函數(shù)的實現(xiàn),chrome是在元素大于10個時使用快排,小于10個的時候使用插入排序,其他的瀏覽器有用歸并排序的,有用選擇排序的...
那sort函數(shù)的性能如何呢?
我們來看看:

var jsSort = function (arr) {
    return arr.sort((a, b) => { return a - b; });
}

test.dif(jsSort, 1000000);
sort函數(shù)耗時.png

這是node環(huán)境下的sort,可以看出,性能也不咋地,有興趣的朋友可以去各個瀏覽器跑一下看看測試結(jié)果~~~
總之,快排雖然在jser們手下不需要手動實現(xiàn),但是了解其中蘊含的算法思想是極為重要的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

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