JS中的算法與數據結構——排序(Sort)

排序算法(Sort)

引言

咱們平時對計算機中存儲的數據執行的兩種最多見的操做就是排序和查找,對於計算機的排序和查找的研究,自計算機誕生以來就沒有中止過。現在又是大數據,雲計算的時代,對數據的排序和查找的速度、效率要求更高,所以要對排序和查找的算法進行專門的數據結構設計,(例如咱們上一篇聊到的二叉查找樹就是其中一種),以便讓咱們對數據的操做更加簡潔高效。算法

這一篇咱們將會介紹一些數據排序的基本算法和高級算法並利用JavaScript來逐一實現,讓大夥對計算機中常見的排序算法的思想和實現有基本的瞭解,起到一個拋磚引玉的做用。數組

關於排序算法的說明

在介紹各個算法以前,咱們有必要了解一下評估算法優劣的一些術語:數據結構

穩定:若是a本來在b前面,當a=b時,排序以後a仍然在b的前面
不穩定:若是a本來在b的前面,當a=b時,排序以後a可能會出如今b的後面學習

內排序:全部排序操做都在內存中完成
外排序:因爲數據太大,所以把數據放在磁盤中,而排序經過磁盤和內存的數據傳輸才能進行測試

時間複雜度:一個算法執行所耗費的時間
空間複雜度:運行完一個程序所需內存的大小大數據

有想要了解更多,關於時間空間複雜度的,我推薦一篇文章,請戳這裏這裏ui

基本排序算法

基本排序算法的核心思想就是對一組數據按照必定的順序從新排序,其中重排時通常都會用到一組嵌套的 for 循環,外循環會遍歷數組的每一項元素,內循環則用於進行元素直接的比較。雲計算

1.冒泡排序(BubbleSort).net

冒泡排序是比較經典的算法之一,也是排序最慢的算法之一,由於它的實現是很是的容易的。設計

冒泡排序的算法思想以下(升序排序):

  1. 比較相鄰的元素。若是第一個比第二個大,就交換它們兩個;
  2. 對每一對相鄰元素做一樣的工做,從開始第一對到結尾的最後一對,這樣最終最大數被交換到最後的位置
  3. 除了最後一個元素之外,針對全部的元素重複以上的步驟
  4. 重複步驟1~3,直到排序完成

下面我借用網上一張動圖,來展現冒泡排序的過程:

冒泡排序
冒泡排序

具體的JS實現以下:

//冒泡排序
function bubbleSort ( data ) {
    var temp = 0;
    for ( var i = data.length ; i > 0 ; i -- ){
        for( var j = 0 ; j < i - 1 ; j++){
           if( data[j] > data[j + 1] ){
               temp = data[j];
               data[j] = data [j+1];
               data[j+1] = temp;
           }
        }
    }
    return data;
}

咱們先設定一組數據,後面咱們將都用這組數據來測試 :

var dataStore = [ 72 , 1 , 68 , 95 , 75 , 54 , 58 , 10 , 35 , 6 , 28 , 45 , 69 , 13 , 88 , 99 , 24 , 28 , 30 , 31 , 78 , 2 , 77 , 82 , 72 ];

console.log( '原始數據:' + dataStore );
console.log( '冒泡排序:' + bubbleSort( dataStore) );

// 原始數據:72,1,68,95,75,54,58,10,35,6,28,45,69,13,88,99,24,28,30,31,78,2,77,82,72
// 冒泡排序:1,2,6,10,13,24,28,28,30,31,35,45,54,58,68,69,72,72,75,77,78,82,88,95,99

2.選擇排序(SelctionSort)

選擇排序是一種比較簡單直觀的排序算法。它的算法思想是,從數組的開頭開始遍歷,將第一個元素和其餘元素分別進行比較,記錄最小的元素,等循環結束以後,將最小的元素放到數組的第一個位置上,而後從數組的第二個位置開始繼續執行上述步驟。當進行到數組倒數第二個位置的時候,全部的數據就完成了排序。

選擇排序一樣會用到嵌套循環,外循環從數組第一個位置移到倒數第二個位置;內循環從第二個位置移動到數組最後一個位置,查找比當前外循環所指向的元素還要小的元素,每次內循環結束後,都會將最小的值放到合適的位置上。

一樣,我借用網上一張動圖,來展現選擇排序的過程 :

選擇排序
選擇排序

瞭解了算法思想,具體實現應該也不成問題:

//選擇排序
function selectionSort( data ) {
    for( var i = 0; i< data.length ; i++){
        var min = data[i];
        var temp;
        var index = i;
        for( var j = i + 1; j< data.length; j++){
            if( data[j] < min ){
                min = data[j];
                index = j;
            }
        }

        temp = data[i];
        data[i] = min;
        data[index]= temp;
    }
    return data;
}

它的測試結果以下:

console.log( '原始數據:' + dataStore );
console.log( '選擇排序:' + selectionSort( dataStore) );

// 原始數據:72,1,68,95,75,54,58,10,35,6,28,45,69,13,88,99,24,28,30,31,78,2,77,82,72
// 選擇排序:1,2,6,10,13,24,28,28,30,31,35,45,54,58,68,69,72,72,75,77,78,82,88,95,99

3.插入排序(insertionSort)

插入排序有點相似人類按字母順序對數據進行排序,就如同你打撲克牌同樣,將摸來的撲克按大小放到合適的位置同樣。它的原理就是經過嵌套循環,外循環將數組元素挨個移動,而內循環則對外循環中選中的元素及它後面的元素進行比較;若是外循環中選中的元素比內循環中選中的元素小,那麼數組元素會向右移動,爲內循環中的這個元素騰出位置。

實現步驟以下:

  1. 從第一個元素開始,該元素默認已經被排序
  2. 取出下一個元素,在已經排序的元素序列中從後向前掃描
  3. 若是該元素(已排序)大於新元素,將該元素移到下一位置
  4. 重複步驟3,直到找到已排序的元素小於或者等於新元素的位置
  5. 將新元素插入到該位置
  6. 重複步驟2~5,直到排序完成

它的實現效果圖以下:

插入排序
插入排序

具體實現代碼以下:

//插入排序

function insertionSort( data ) {
    var len = data.length;
    for (var i = 1; i < len; i++) {
        var key = data[i];
        var j = i - 1;
        while ( j >= 0 && data[j] > key) {
            data[j + 1] = data[j];
            j--;
        }
        data[j + 1] = key;
    }
    return data;
}

排序結果以下:

console.log( '原始數據:' + dataStore );
console.log( '插入排序:' + insertionSort( dataStore) );

// 原始數據:72,1,68,95,75,54,58,10,35,6,28,45,69,13,88,99,24,28,30,31,78,2,77,82,72
// 插入排序:1,2,6,10,13,24,28,28,30,31,35,45,54,58,68,69,72,72,75,77,78,82,88,95,99

咱們已經學習了三種基本的排序算法,其中冒泡排序是最慢的,插入排序是最快的,咱們能夠在運行的過程當中經過 console.time('sortName') 和 console.timeEnd('sortName') 兩個輸出來看他們的效率如何,我這裏給出一組值做爲參考,實際中須要大量的數據測試和反覆實驗,進行數理統計後才能被視爲有效的統計;

排序時間比較
排序時間比較

高級排序算法

4.希爾排序(Shell Sort)

咱們首先要學習的就是希爾排序,又稱縮小增量排序,這個算法是在插入排序的基礎上作了很大的改善,與插入排序不一樣的是,它首先會比較位置較遠的元素,而非相鄰的元素。這種方案可使離正確位置很遠的元素可以快速回到合適的位置,當算法進行遍歷時,全部元素的間距會不斷的減少,直到數據的末尾,此時比較的就是相鄰元素了。

該方法的基本思想是:先將整個待排元素序列分割成若干個子序列(由相隔某個「增量」的元素組成的)分別進行直接插入排序,而後依次縮減增量再進行排序,待整個序列中的元素基本有序(增量足夠小)時,再對全體元素進行一次直接插入排序。由於直接插入排序在元素基本有序的狀況下(接近最好狀況),效率是很高的,所以希爾排序在時間效率上有較大提升。

好吧,我仍是用個案例來解釋,這樣會更清晰,咱們如下面一組數據爲例:

數據集
數據集
  • 第一次 gap(增量) = 10 / 2 = 5 , 會按照下面進行分組獲得五組數據(49,13)、(38,27)、(65,49)、(97,55)、(26,4),這樣進行組內排序以後(13,49)、(27,38)、(49,65)、(55,97)、(4,26)
第一次分組
第一次分組

此時,數據會排成以下結構

第一次排序
第一次排序
  • 第二次 gap = 5 / 2 = 2 , 此時能夠獲得兩個分組,以下
第二次分組
第二次分組

再經過組內排序以後,能夠獲得

第二次排序
第二次排序
  • 第三次 gap = 2 / 2 = 1 , 即不用分組,進行排序後
第三次排序
第三次排序
  • 第四次 gap = 1 / 2 = 0 ,便可獲得排序完成的數組
排序完成
排序完成

如今,可能對希爾排序有了必定得了解了,用JS實現以下:

//希爾排序

function shallSort(array) {
    var increment = array.length;
    var i
    var temp; //暫存
    do {
        //設置增量
        increment = Math.floor(increment / 3) + 1;
        for (i = increment ; i < array.length; i++) {
            if ( array[i] < array[i - increment]) {
                temp = array[i];
                for (var j = i - increment; j >= 0 && temp < array[j]; j -= increment) {
                    array[j + increment] = array[j];
                }
                array[j + increment] = temp;
            }
        }
    }
    while (increment > 1)

    return array;
}

效果以下:

console.log( '原始數據:' + dataStore );
console.log( '希爾排序:' + shallSort( dataStore) );

// 原始數據:72,1,68,95,75,54,58,10,35,6,28,45,69,13,88,99,24,28,30,31,78,2,77,82,72
// 希爾排序:1,2,6,10,13,24,28,28,30,31,35,45,54,58,68,69,72,72,75,77,78,82,88,95,99

5.歸併排序(Merge Sort)

將兩個的有序數列合併成一個有序數列,咱們稱之爲"歸併",歸併排序的思想就是將一系列排序好的子序列合併成一個大的完整有序的序列。

實現步驟以下:

  1. 把長度爲n的輸入序列分紅兩個長度爲n/2的子序列;
  2. 對這兩個子序列分別採用歸併排序;
  3. 將兩個排序好的子序列合併成一個最終的排序序列

一張動圖來講明歸併排序的過程:

歸併排序
歸併排序

具體的JS代碼實現以下:

//歸併排序

function mergeSort ( array ) {
    var len = array.length;
    if( len < 2 ){
        return array;
    }
    var middle = Math.floor(len / 2),
        left = array.slice(0, middle),
        right = array.slice(middle);
    return merge(mergeSort(left), mergeSort(right));
}

function merge(left, right)
{
    var result = [];
    while (left.length && right.length) {
        if (left[0] <= right[0]) {
            result.push(left.shift());
        } else {
            result.push(right.shift());
        }
    }
    while (left.length)
        result.push(left.shift());
    while (right.length)
        result.push(right.shift());
    return result;
}

測試結果以下 :

console.log( '原始數據:' + dataStore );
console.log( '希爾排序:' + mergeSort( dataStore) );

// 原始數據:72,1,68,95,75,54,58,10,35,6,28,45,69,13,88,99,24,28,30,31,78,2,77,82,72
// 希爾排序:1,2,6,10,13,24,28,28,30,31,35,45,54,58,68,69,72,72,75,77,78,82,88,95,99

6.快速排序(Quicksort)

快速排序是處理大數據最快的排序算法之一,它也是一種分而治之的算法,經過遞歸方式將數據依次分解爲包含較小元素和較大元素的不一樣子序列,會不斷重複這個步驟,直到全部的序列所有爲有序的,最後將這些子序列一次拼接起來,就可獲得排序好的數據。

該算法首先要從數列中選出一個元素做爲基數(pivot)。接着全部的數據都將圍繞這個基數進行,將小於改基數的元素放在它的左邊,大於或等於它的數所有放在它的右邊,對左右兩個小數列重複上述步驟,直至各區間只有1個數。

整個排序過程以下:

快速排序
快速排序

具體實現以下:

//快速排序

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

測試結果以下:

console.log( '原始數據:' + dataStore );
console.log( '快速排序:' + quickSort( dataStore) );

// 原始數據:72,1,68,95,75,54,58,10,35,6,28,45,69,13,88,99,24,28,30,31,78,2,77,82,72
// 快速排序:1,2,6,10,13,24,28,28,30,31,35,45,54,58,68,69,72,72,75,77,78,82,88,95,99

至此,咱們已基本介紹過一些常見的排序算法的思想和具體實現(基數排序在以前的文章已經介紹過,想要了解戳這裏),排序算法博大精深,咱們不只要學習理論,也要不斷去實踐,你們加油!

做者:Cryptic連接:http://www.jianshu.com/p/8d30da8b832e來源:簡書著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
相關文章
相關標籤/搜索