JavaScript 版各大排序算法

最近看到了不少公司都在準備明年的實習校招,雖然離三月份還有一段時間,感受已經能夠準備了。在網上看了一些排序算法和數組去重操做,感受都寫的很好,心血來潮,也來寫一寫。javascript

images

排序算法的設計和實現

說到排序,以前在作百度前端學院的題目的時候,也碰到過,並把它整理到 github 上。這是一個可視化的排序展現,支持冒泡、插入和選擇排序,具體使用先 隨機添加 40 個,而後點排序,就能夠看到可視化的效果。php

推薦一下,HTML5 Canvas Demo: Sorting Algorithms,這裏還有個可視化的排序博客,各大排序算法的實現都栩栩如生。html

javascript 寫排序算法也比較奇葩,主要是參數的問題,好比 javascript 算法函數能夠扔給 Array 原型:Array.prototype.sort = function,也能夠直接寫個函數帶參數:function sort(array){},在我看來,哪一種方法都同樣,須要注意的是兼容性的問題,若是能夠考慮對全部可遍歷對象都能排序(好比 arguments),才大法好。前端

好了,直接入主題了(下面的排序均是從小到大的順序)。java

插入排序

插入排序是一種基本排序,它的基本思路是構建有序序列,對於未排序的數據,在已排序的基礎上,從右向左(或者二分查找)選擇位置插入,維基百科-插入排序git

function insert_sort(input){
  var i, j, temp;
  for(i = 1; i < input.length; i++){
    temp = input[i];
    for(j = i-1; j >= 0 && input[j] > temp; j--)
      input[j+1] = input[j];
    input[j+1] = temp;
  }
  return input;
}

若是以比較次數和移動次數來衡量算法的效率,最好狀況下,比較 n-1 次,移動 0 次,最壞狀況,比較 n*(n-1)/2 次,移動 n*(n-1)/2 次。github

二分插入排序

思路基本同上,只是在查找插入位置的時候,不是依次查找,而是採用二分法:面試

function bin_insert_sort(input){
  var i, j, low, high, mid, temp;
  for(i = 1; i < input.length; i++){
    temp = input[i];
    high = i - 1;
    low = 0;
    while(low <= high){
      mid = parseInt((low + high) / 2);
      if(temp < input[mid]){
        high = mid - 1;
      }else{
        low = mid + 1;
      }
    }
    // low 位置就是要插入的位置
    for(j = i-1; j >= low; j--)
      input[j+1] = input[j];
    input[low] = temp;
  }
  return input;
}

希爾排序

希爾排序實際上是增強版的插入排序,就是在原先插入排序的基礎上,加入了步長,原先插入排序的步長是 1,並且步長不一樣,效率也有差別,選擇一個合適的步長也很重要。並且,希爾排序的最後一步,也一定是步長爲 1 的插入排序,只不過此時整個排序已經基本穩定。維基百科-希爾排序算法

function shell_sort(input){
  var gap, i, j, temp;
  gap = input.length >> 1;
  while(gap > 0){
    for (i = gap; i < input.length; i++) {
      temp = input[i];
      for (j = i - gap; j >= 0 && input[j] > temp; j -= gap)
        input[j + gap] = input[j];
      input[j + gap] = temp;
    }
    gap = gap >> 1;
  }
  return input;
}

選擇排序

選擇排序的工做原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,而後,再從剩餘未排序元素中繼續尋找最小(大)元素,而後放到已排序序列的末尾。以此類推,直到全部元素均排序完畢。維基百科-冒泡排序shell

function select_sort(input){
  var i, j, min, temp;
  for(i = 0; i < input.length - 1; i++){
    min = i;
    for(j = i + 1; j < input.length; j++){
      if(input[min] > input[j])
        min = j;
    }
    temp = input[min];
    input[min] = input[i];
    input[i] = temp;
  }
  return input;
}

選擇排序在最好狀況下,也要比較 n*(n-1)/2,移動 n-1 次(這裏能夠加個判斷,移動 0 次),最差狀況下,比較 n*(n-1)/2 次,移動 n-1 次。全部最好,最壞狀況下,比較次數是同樣的。

冒泡排序

冒泡排序的基本原理:對於帶排序列,它會屢次遍歷序列,每次都會比較相鄰的兩個元素,若順序相反,即交換它們,維基百科-冒泡排序

function bubble_sort(input){
  var i, j, temp, flag;
  for(i = 0; i < input.length - 1; i++){
    flag = true;
    for(j = 0; j < input.length - i; j++){
      if(input[j] > input[j + 1]){
        temp = input[j];
        input[j] = input[j + 1];
        input[j + 1] = temp;
        flag = false;
      }
    }
    if(flag)
      // 提早結束
      break;
  }
  return input;
}

有 flag 時,最好狀況比較 n-1 次,移動 0 次,最壞狀況,比較 n*(n-1)/2 次,交換 n*(n-1)/2。

快排

記得我一個同窗去百度面試,百度面試官上來就讓他手寫了一個快排,可見對快排的掌握很重要呀,並且快排理解起來也不容易。

維基百科-快排。快排的基本思路就是選擇一個元素,而後按照與這個元素的比較,將大於這個元素的都拿到右邊,小於這個元素的都拿到左邊,並找到這個元素的位置,這個元素的左右兩邊遞歸。

function quick_sort(input){
  function sort(start, end){
    if(start >= end){
      return;
    }
    var mid = partition(start, end);
    sort(start, mid - 1);
    sort(mid + 1, end);
  }
  function partition(start, end){
    var left = start, right = end, key = input[start], temp;
    while(left < right){
      while(left < right && input[right] >= key){
        right --;
      }
      input[left] = input[right];
      while(left < right && input[left] <= key){
        left ++;
      }
      input[right] = input[left];
    }
    input[left] = key;
    return left;
  }
  // main here
  sort(0, input.length - 1);
  return input;
}

partition 函數就是來找對應的 mid,sort 函數用來排序。

關於快排的優化,能夠從如下幾個方面來考慮:

  1. partition 函數的哨兵(比較值)除了 start 之外,用其餘位置(好比中位數)是否可行;

  2. 當 start 和 end 間距很小的時候,改用其餘高效算法

  3. 還有就是優化遞歸。

其實呢,上面的這個算法,並不屬於 JavaScript 版本,而更像 C 版本的,重在讓人理解快排,下面是 JS 版的快排,來體驗下 JS 的迷人特性吧:

// javascript 版
function quick_sort(input) {
  var len = input.length;
  if (len <= 1)
    return input.slice(0);
  var left = [];
  var right = [];
  // 基準函數
  var mid = [input[0]];
  for (var i = 1; i < len; i++)
    if (input[i] < mid[0])
      left.push(input[i]);
    else
      right.push(input[i]);
  return quick_sort(left).concat(mid.concat(quick_sort(right)));
};

這個 JS 版快排也比較好懂,找到那個基準(這裏是第一個元素 input[0])以後,遍歷,把小於基準的放到左邊,大於基準的放到右邊,而後返回拼接數組。

歸併排序

在學習分治算法時,典型的一個例子就是歸併。維基百科-歸併排序。思路就是先分後和,依舊是遞歸。

function merge_sort(input){
  function merge(left, right){
    var temp = [];
    var i = 0, j = 0;
    while(i < left.length && j < right.length){
      if(left[i] < right[j]){
        temp.push(left[i]);
        i++;
      }else{
        temp.push(right[j]);
        j++;
      }
    }
    if(i < left.length){
      temp = temp.concat(left.slice(i));
    }
    if(j < right.length){
      temp = temp.concat(right.slice(j));
    }
    return temp;
  }
  if(input.length <=1){
    return input;
  }
  var mid = parseInt(input.length / 2);
  return merge(merge_sort(input.slice(0, mid)), merge_sort(input.slice(mid)))
}

一樣,以上歸併仍然是相似 C 語言版本,JavaScript 版本以下:

// javascript 版
function merge_sort(input) {
  var merge = function(left, right) {
    var final = [];
    while (left.length && right.length)
      final.push(left[0] <= right[0] ? left.shift() : right.shift());
    return final.concat(left.concat(right));
  };
  var len = input.length;
  if (len < 2) return input;
  var mid = len / 2;
  return merge(merge_sort(input.slice(0, parseInt(mid))), merge_sort(input.slice(parseInt(mid))));
};

數組的一系列操做大大優化排序的過程。

堆排序

堆排序(Heapsort)是指利用堆這種數據結構所設計的一種排序算法。堆積是一個近似徹底二叉樹的結構,並同時知足堆積的性質:即子結點的鍵值或索引老是小於(或者大於)它的父節點。維基百科-堆排序

其實,對於堆排序,只要牢記幾個操做就能夠,好比找到最後一個父節點,如何找到子節點(初始爲 0),如何創建一個最大堆。

function heap_sort(input){
  var arr = input.slice(0);
  function swap(i, j) {
    var tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
  }
  // 上推操做
  function max_heapify(start, end) {
    var dad = start;
    var son = dad * 2 + 1;
    if (son >= end)
      return;
    if (son + 1 < end && arr[son] < arr[son + 1])
      son++;
    if (arr[dad] <= arr[son]) {
      swap(dad, son);
      max_heapify(son, end);
    }
  }

  var len = arr.length;
  // 創建一個最大堆
  for (var i = Math.floor(len / 2) - 1; i >= 0; i--)
    max_heapify(i, len);
  for (var i = len - 1; i > 0; i--) {
    swap(0, i);
    max_heapify(0, i);
  }

  return arr;
};

堆排序的過程大體以下:先生成一個最大堆,而後將根節點(最大元素)與最後一個元素交換,而後把剩下的 n-1 元素再次生成最大堆,交換,生成...

總結

那麼問題來了,到底這些算法寫的對不對,否則寫個測試腳原本試試:

// 兩種排序算法
var test = function(sort1, sort2){
  var arr1 = [], arr2 = [];
  // 隨機生成 100 個 1~100 隨機數
  function random_arr(a1, a2){
    var tmp;
    for(var i = 0; i < 100; i++){
      tmp = parseInt(Math.random()*100) + 1;
      a1.push(tmp);
      a2.push(tmp);
    }
  }

  var flag = true;
  for(var i = 0; i < 100; i++){
    random_arr(arr1, arr2);
    // 比較排序算法的結果
    if(sort1(arr1).toString() != sort2(arr2).toString()){
      flag = false;
      break;
    }
    arr1 = arr2 = [];
  }
  return flag ? "Ok!" : "Error!"
}

console.log(test(insert_sort, merge_sort)); //"Ok!"

若是已知插入排序是正確的狀況下,就能夠驗證歸併排序是否正確了。共勉!

參考

維基百科 排序搜索
聊一聊排序算法
秒殺9種排序算法(JavaScript版)
排序圖解:js排序算法實現

歡迎來個人博客交流

相關文章
相關標籤/搜索