溫故js系列(2)-快速排序&插入排序&選擇排序&冒泡排序算法&優化

前端學習:教程&開發模塊化/規範化/工程化/優化&工具/調試&值得關注的博客/Git&面試-前端資源彙總前端

歡迎提issues斧正:排序算法git

JavaScript-排序算法及簡易優化

快速排序

原理:在待排序序列中選一個分割元素,將待排序序列分隔成獨立的子序列,子序列1裏的元素比分割元素元素都小(大),子序列2反之,遞歸進行此操做,以達到子序列都有序。最後將子序列用concat方法鏈接起來便是排序好的序列。github

function quickSort(arr){
    if(arr.length <= 1){
        return arr;
    }    
    var num = Math.floor(arr.length/2);
    var numValue = arr.splice(num,1);

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

}
console.log(quickSort([1,45,43,4,56,7,20,1]));
//[1, 1, 4, 7, 20, 43, 45, 56]

優化:當待排序序列長度N < 10時,使用插入排序,能夠加速排序。面試

function quickSort(arr){
    if(arr.length <= 1){
        return arr;
    }
    if(arr.length < 10){
        insertSort(arr);
    }    
    var num = Math.floor(arr.length/2);
    var numValue = arr.splice(num,1);

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

}
console.log(quickSort([1,3,4,645,43,4,56,333,44,564,7,20,1]));
//[1, 1, 3, 4, 4, 7, 20, 43, 44, 56, 333, 564, 645]

插入排序

原理:經過構建有序序列,對於未排序元素,在已排序序列中從後向前掃描,找到相應位置並插入。通常能夠將第一個元素做爲有序序列,用未排序的元素與之相比,插入,直到排序完畢。算法

function insertSort(arr){
    var len = arr.length;
    if(len <= 1){
        return arr;
    }
    for(var i = 1;i<len;i++){
        var tmp = arr[i];
        var j = i;   
        while(arr[j-1] > tmp){
            arr[j] = arr[j-1];
            --j;
        }
        arr[j] = tmp;
    }
    return arr;
}
console.log(insertSort([1,45,43,4,56,7,20,1]));
//[1, 1, 4, 7, 20, 43, 45, 56]

優化:二分插入排序,在有序序列中使用二分查找法查找一個插入位置,減小元素比較次數api

function binaryInsertSort(arr){
    var len = arr.length;
    if(len <= 1){
        return arr;
    }
    for (var i = 1; i < len; i++) {
        var tmp = arr[i], left = 0, right = i - 1;
        while (left <= right) {
              var index = parseInt((left + right) / 2);
              if (tmp < arr[index]) {
                  right = index - 1;
              } else {
                left = index + 1;
              }
        }
        for (var j = i - 1; j >= left; j--) {
              arr[j + 1] = arr[j];
        }
        arr[left] = tmp;
      }
    return arr;
}
console.log(binaryInsertSort([1,45,43,4,56,7,20,1,2,3,4,56,3]));
//[1, 1, 2, 3, 3, 4, 4, 7, 20, 43, 45, 56, 56]

選擇排序

原理:在待排序序列中找到最小(大)元素,放在序列的起始位置,而後,再從剩餘元素中尋找最小(大)元素,而後放到已排序序列的末尾。重複,直到全部元素均排序完畢。數組

Array.prototype.selectionSort = Array.prototype.selectionSort || function(){
    var len = this.length;
    if(len <= 1){
        return this;
    }    
    var min,tmp;
    for(var i = 0;i<len;i++){
        min = i;
        for(var j = i+1;j<len;j++){
            if(this[j] < this[min]){
                min = j;
            }
        }
        if(i != min){
            tmp = this[i];
            this[i] = this[min];
            this[min] = tmp;
        }
    }
    return this;
}
console.log([1,45,43,4,56,7,20,1].selectionSort());
//[1, 1, 4, 7, 20, 43, 45, 56]

優化:堆排序,在直接選擇排序中,爲了從序列中選出關鍵字最小(最大)的記錄,必須進行n-1次比較,接着在剩下序列中選出最小(最大)的元素,又須要作n-2次比較。可是,在n-2次比較中,有的比較可能在前面的n-1次比較中已經作過,但因爲前一趟排序時未保留這些比較結果,因此後一趟排序時又重複執行了這些比較操做。堆積是一個近似徹底二叉樹的結構,並同時知足堆積的性質:即子結點的鍵值或索引老是小於(或者大於)它的父節點。堆排序可經過樹形結構保存部分比較結果,可減小比較次數。dom

function heapSort(arr) { 
     function swap(arr, i, j) {
          var temp = arr[i];
          arr[i] = arr[j];
          arr[j] = temp;
    }
 
     function maxHeapify(arr, index, heapSize) {
          var iMax,iLeft,iRight;
          while (true) {
               iMax = index;
               iLeft = 2 * index + 1;
               iRight = 2 * (index + 1);
 
               if (iLeft < heapSize && arr[index] < arr[iLeft]) {
                iMax = iLeft;
               }
 
               if (iRight < heapSize && arr[iMax] < arr[iRight]) {
                iMax = iRight;
               }
 
               if (iMax != index) {    
                swap(arr, iMax, index);
                index = iMax;
               } else {
                    break;
               }
          }
     }
 
     function buildMaxHeap(arr) {
          var i,iParent = Math.floor(arr.length / 2) - 1;
 
          for (i = iParent; i >= 0; i--) {
               maxHeapify(arr, i, arr.length);
          }
     }
 
     function sort(arr) {
          buildMaxHeap(arr);
 
          for (var i = arr.length - 1; i > 0; i--) {
               swap(arr, 0, i);
               maxHeapify(arr, 0, i);
          }
          return arr;
     }
 
     return sort(arr);
}
console.log(heapSort([1,45,43,4,56,7,20,1,2,3,4,56,3]));
//[1, 1, 2, 3, 3, 4, 4, 7, 20, 43, 45, 56, 56]

冒泡排序

原理:從第一個元素開始,一次比較兩個元素,若是arr[n]大於arr[n+1],就交換。重複遍歷直到沒有再須要交換,排序完成。模塊化

function bubbleSort(arr){
    var len = arr.length;
    if(len <= 1){
        return arr;
    }
    var tmp;
    for(var i = 0;i<len;i++){
        for(var j =0;j<len-1-i;j++){
            if(arr[j+1] < arr[j]){
                tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
        }
    }
    return arr;
}
console.log(bubbleSort([1,45,43,4,56,7,20,1]));
//[1, 1, 4, 7, 20, 43, 45, 56]

優化:上面代碼中,裏面一層循環在某次掃描中沒有發生交換,說明此時數組已經所有排好序,後面的步驟就無需再執行了。所以,增長一個標記,每次發生交換,就標記,若是某次循環完沒有標記,則說明已經完成排序。工具

function bubbleSort(arr)  {  
    var len = arr.length;
    if(len <= 1){
        return arr;
    }
    var bSwaped = true;  
    for (var i = 0; i < len -1; i++)  {  
        // 每次先重置爲false  
        bSwaped = false;  
        for (var j = len - 1; j > i ; j--)  {  
            if (arr[j-1] > arr[j])  {  
                var temp = arr[j-1];  
                arr[j-1] = arr[j];  
                arr[j] = temp;  
  
                bSwaped = true;  
            }  
        }  
        // 若是上一次掃描沒有發生交換,則說明數組已經所有有序,退出循環  
        if (!bSwaped){
            break;
        }              
    }  
    return arr;
}  
console.log(bubbleSort([1,45,43,4,56,7,20,1]));
//[1, 1, 4, 7, 20, 43, 45, 56]

在上一步優化的基礎上進一步思考:若是R[0..i]已經是有序區間,上次的掃描區間是R[i..n],記上次掃描時最後一次發生交換的位置是lastSwapPos,那麼lastSwapPos會在在i與n之間,因此R[i..lastSwapPos]區間是有序的,不然這個區間也會發生交換;因此下次掃描區間就能夠由R[i..n]縮減到[lastSwapPos..n] :

function bubbleSort(arr){ 
    var len = arr.length;
    if(len <= 1){
        return arr;
    } 
    var lastSwapPos = 0, lastSwapPosTemp = 0;  
    for (var i = 0; i < len - 1; i++){  
        lastSwapPos = lastSwapPosTemp;  
        for (var j = len - 1; j > lastSwapPos; j--){  
            if (arr[j - 1] > arr[j]){  
                var temp = arr[j - 1];  
                arr[j - 1] = arr[j];  
                arr[j] = temp;  
  
                lastSwapPosTemp = j;  
            }  
        }  
        if (lastSwapPos == lastSwapPosTemp){
            break;
        }                         
    }  
    return arr;
}
console.log(bubbleSort([1,45,43,4,56,7,20,1]));
//[1, 1, 4, 7, 20, 43, 45, 56]

這一些列優化都須要測速才知道有沒有優化成功,只是簡單的測試一兩個數組是不容易看出來的。咱們能夠造一些很大的數據去測試,再用一個比較簡單的測試時間的方法,隨機建立10萬個數:

var arr = [];
var num = 0;
for(var i = 0; i < 100000; i++){
    num = Math.floor(Math.random()*100000);
    arr.push(num);
}
console.time("testTime");
bubbleSort(arr);
console.timeEnd("testTime");
==> testTime: 21900.684ms (比較數目越多,差距越大,更好對比)
相關文章
相關標籤/搜索