排序圖解:js排序演算法實現

weixin_34023863發表於2015-08-23
615809-3bf3a38c16826acf.jpg
array_sort.jpg

之前寫過js實現陣列去重, 今天繼續研究陣列: 排序演算法實現。 排序是資料結構主要內容,並不限於語言主要在於思想;大學曾經用C語言研究過一段時間的排序實現, 這段時間有空用JS再將排序知識點熟悉一遍。

理解排序不得不提的是日本人實現的一個排序動畫站, 該站對於研究排序大有益處。當然本文的排序演算法並不與其一致, 本文是9種js排序實現**的實踐與完善: 理解其9種演算法然後使每種演算法程式碼均能正常執行。

1.插入排序

最普通的排序演算法, 從陣列下標1開始每增1項排序一次,越往後遍歷次數越多;
原理圖:

615809-bd7f2c13adb76758.png
sort1.png

程式碼:

// 插入排序 從下標1開始每增1項排序一次,越往後遍歷次數越多
function sort1(array) {
  var len = array.length,
      i, j, tmp, result;
  
  // 設定陣列副本
  result = array.slice(0);
  for(i=1; i < len; i++){
    tmp = result[i];
    j = i - 1;
    while(j>=0 && tmp < result[j]){
      result[j+1] = result[j];
      j--;
    }
    result[j+1] = tmp;
  }
  return result;
}

2.二分插入排序

插入排序的一種優化實現, 通過二分法減少遍歷時間。
原理圖:

615809-771d11316d4d2892.jpg
sort2.jpg

程式碼:

// 先在有序區通過二分查詢的方法找到移動元素的起始位置,
// 然後通過這個起始位置將後面所有的元素後移
function sort2(array) {
  var len = array.length,
      i, j, tmp, low, high, mid, result;
  // 賦予陣列副本
  result = array.slice(0);
  for(i = 1; i < len; i++){
    tmp = result[i];
    low = 0;
    high = i - 1;
    while(low <= high){
      mid = parseInt((low + high)/2, 10);
      if(tmp < result[mid]) high = mid - 1;
      else low = mid + 1;
    }
    for(j = i - 1; j >= high+1; j--){
      result[j+1] = result[j];            
    }
    result[j+1] = tmp;
  }
  return result;
}

3.希爾排序

其排序思路有點複雜, 需花多點時間理解;排序思路:先將整個待排序記錄序列分割成若干個子序列,在序列內分別進行直接插入排序,待整個序列基本有序時,再對全體記錄進行一次直接插入排序。
原理圖:

615809-68758a9e524183d9.jpg
sort3.jpg

程式碼:

// 希爾排序:先將整個待排序記錄序列分割成若干個子序列
// 在序列內分別進行直接插入排序,待整個序列基本有序時,
// 再對全體記錄進行一次直接插入排序
function sort3(array){
  var len = array.length, gap = parseInt(len/2), 
      i, j, tmp, result;
  // 複製陣列
  result = array.slice(0);
  while(gap > 0){
    for(i = gap; i < len; i++){
      tmp = result[i];
      j = i - gap;
      while(j>=0 && tmp < result[j]){
        result[j + gap] = result[j];
        j = j - gap;
      }
      result[j + gap] = tmp;
    }
    gap = parseInt(gap/2);
  }
  return result;
}

4.氣泡排序

很常見很容易理解的排序演算法, 排序思路:遍歷陣列,每次遍歷就將最大(或最小)值推至最前。越往後遍歷查詢次數越少, 跟插入排序剛好相反。

原理圖:

615809-709b27ca8b041a7e.png
sort4.png

程式碼:

// 氣泡排序 每次將最小元素推至最前
function sort4(array) {
  var len = array.length,
  i, j, tmp, result;
  result = array.slice(0);
  for (i = 0; i < len; i++) {
    for (j = len - 1; j > i; j--) {
      if (result[j] < result[j - 1]) {
        tmp = result[j - 1];
        result[j - 1] = result[j];
        result[j] = tmp;
      }
    }
  }
  return result;
}

5.改進氣泡排序

對上述氣泡排序的一種優化, 優化思路:當一次遍歷前後陣列不產生變化時,說明該陣列已經有序,結束排序。
原理圖:

615809-7bdc9973f59383c1.jpg
sort5.jpg

程式碼:

// 如果在某次的排序中沒有出現交換的情況,
// 那麼說明在無序的元素現在已經是有序了,就可以直接返回了。
function sort5(array) {
  var len = array.length,
  i, j, tmp, exchange, result;

  result = array.slice(0);
  for (i = 0; i < len; i++) {
    exchange = 0;
    for (j = len - 1; j > i; j--) {
      if (result[j] < result[j - 1]) {
        tmp = result[j];
        result[j] = result[j - 1];
        result[j - 1] = tmp;
        exchange = 1;
      }
    }
    if (!exchange) return result;
  }
  return result;
}

6.快速排序

快速排序在諸多演算法排序中可能不是最好的, 但個人認為在JS語言實現中是最快的!以前公司專案中對比過二分插入排序優化氣泡排序快速排序的JS實現執行時間,幾千條資料的陣列在firefox下快速排序的速度比冒泡、插入排序快3至4秒(陣列元素為複雜的物件,根據物件某一屬性值排序)。阮一峰老師研究JS實現排序時曾只針對該種排序進行講解:javascript的快速排序實現**

原理圖:

615809-61e8dcef25758c32.jpg
sort6.jpg

程式碼:

//(1)在資料集之中,選擇一個元素作為"基準"(pivot)。
//(2)所有小於"基準"的元素,都移到"基準"的左邊;所有大於"基準"的元素,都移到"基準"的右邊。
//(3)對"基準"左邊和右邊的兩個子集,不斷重複第一步和第二步,直到所有子集只剩下一個元素為止。
function sort6(array) {
  var tmp_array = array.slice(0), result,
  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));
  };
  result = quickSort(tmp_array);
  return result;
}

7.選擇排序

實現思路跟氣泡排序差不多, 可以說是氣泡排序的衍生版本;
原理圖:

615809-a24549dcf73b5989.jpg
sort7_1.jpg

程式碼:

// 在無序區中選出最小的元素,然後將它和無序區的第一個元素交換位置。
// 原理跟氣泡排序一樣,算是冒泡的衍生版本
function sort7(array) {
  var len = array.length,
  i, j, k, tmp, result;

  result = array.slice(0);
  for (i = 0; i < len; i++) {
    k = i;
    for (j = i + 1; j < len; j++) {
      if (result[j] < result[k]) k = j;
    }
    if (k != i) {
      tmp = result[k];
      result[k] = result[i];
      result[i] = tmp;
    }
  }
  return result;
}

8.堆排序

因為js模擬二叉樹比較麻煩,所以堆排序的優勢用js語言無法體現, 相對而言C語言的連結串列在實現上更能表現堆排序,堆排序或許更適合指標類的計算機語言。本文注重圖解各排序的基本思路,所以該排序的具體實現沒講太細, 如想深究實現細節請看:堆排序及其分析**
原理圖:
1.調整二叉樹,形成大根堆(子節點都比父節點小)。

615809-dcb51bb05369c4f7.png
sort8_1.png

2.交換堆第一元素跟最後元素位置,最後元素彈出堆。然後繼續回到1,調整堆。

615809-5270783efcedadc0.png
sort8_2.png

3.重複2, 當所有節點彈出堆後;彈出的節點值就是有序的了。

615809-ad2616f380ab2b72.png
sort8_3.png

程式碼:

// 1) 初始堆:將原始陣列調整成大根堆的方法——篩選演算法:子節點都比父節點小
// 2) 堆排序: 每次將堆頂元素與陣列最後面的且沒有被置換的元素互換。
// 參考程式碼: http://bubkoo.com/2014/01/14/sort-algorithm/heap-sort/
function sort8(array) {
  var result = array.slice(0);

  function swap(array, i, j) {
    var temp = array[i];
    array[i] = array[j];
    array[j] = temp;
  }

  function maxHeapify(array, index, heapSize) {
    var iMax, iLeft, iRight;
    while (true) {
      iMax = index;
      iLeft = 2 * index + 1;
      iRight = 2 * (index + 1);

      if (iLeft < heapSize && array[index] < array[iLeft]) {
        iMax = iLeft;
      }

      if (iRight < heapSize && array[iMax] < array[iRight]) {
        iMax = iRight;
      }

      if (iMax != index) {
        swap(array, iMax, index);
        index = iMax;
      } else {
        break;
      }
    }
  }

  function buildMaxHeap(array) {
    var i, iParent = Math.floor(array.length / 2) - 1;

    for (i = iParent; i >= 0; i--) {
      maxHeapify(array, i, array.length);
    }
  }

  function sort(array) {
    buildMaxHeap(array);

    for (var i = array.length - 1; i > 0; i--) {
      swap(array, 0, i);
      maxHeapify(array, 0, i);
    }
    return array;
  }

  return sort(result);
}

9.歸併排序

很容易理解且執行效率一般(js實現)的排序, 排序思路:將無序的陣列 拆成N部分進行有序處理,然後合併;

原理圖:

615809-e7a978dd6934ee27.jpg
sort9.jpg

程式碼:

// 合併排序:將無序的陣列 拆成N部分進行有序處理,然後合併;
// 參考程式碼: https://gist.github.com/paullewis/1982121
function sort9(array) {
  var result = array.slice(0);

  // 遞迴呼叫合併函式
  function sort(array) {
    var length = array.length,
    mid = Math.floor(length * 0.5),
    left = array.slice(0, mid),
    right = array.slice(mid, length);

    if (length === 1) {
      return array;
    }
    return merge(sort(left), sort(right));
  }

  // 合併 兩有序的陣列
  function merge(left, right) {
    var result = [];

    while (left.length || right.length) {

      if (left.length && right.length) {

        if (left[0] < right[0]) {
          result.push(left.shift());
        } else {
          result.push(right.shift());
        }

      } else if (left.length) {
        result.push(left.shift());
      } else {
        result.push(right.shift());
      }
    }
    return result;
  }

  return sort(result);
}

本文程式碼:
demo原始碼

參考文章:
1.排序動畫站
2.9種js排序實現
3.阮一峰:javascript的快速排序實現
4.堆排序及其分析
5.常見排序演算法
6.github: paullewis 歸併演算法實現

相關文章