排序算法 JavaScript

一、冒泡排序

算法介紹:

  1. 比較相鄰的兩個元素,如果前一個比後一個大,則交換位置。
  2. 第一輪把最大的元素放到了最後面。
  3. 由於每次排序最後一個都是最大的,所以之後按照步驟1排序最後一個元素不用比較。
function bubble_sort(arr){
  var swap;  
  for(var i=0;i<arr.length-1;i++){
    for(var j=0;j<arr.length-i-1;j++){
      if(arr[j]>arr[j+1]){
        swap=arr[j];
        arr[j]=arr[j+1];
        arr[j+1]=swap;
      }
    }
  }
}

冒泡算法改進:

  1. 設置一個標誌,如果這一趟發生了交換,則爲true。否則爲false。如果這一趟沒有發生交換,則說明排序已經完成。代碼如下:
function bubble_sort_1(arr) {
  var n = arr.length,
  flag = true,
  swap;
  while(flag){
    flag = false;
    for(var j = 1; j<n; j++){
      if(arr[j - 1]>arr[j]) {
        swap = arr[j-1];
        arr[j-1] = arr[j];
        arr[j] = swap;
       flag = true;
      }0
    }
    n --;  
  }
  return arr;
}
  1. 假如數組長度是20,如果只有前十位是無序排列的,後十位是有序且都大於前十位,所以第一趟遍歷排序的時候發生交換的位置必定小於10,且該位置之後的必定有序,我們只需要排序好該位置之前的就可以,因此我們要來標記這個位置就可以了,即可以記錄每次掃描中最後一次交換的位置,下次掃描的時候只要掃描到上次的最後交換位置就行了,因爲後面的都是已經排好序的,無需再比較,代碼如下:
function bubble_sort_2(arr) {
  var n=arr.length;
  var j,k;  
  var flag=n;
  var swap;
  while(flag>0) {  
    k=flag;  
    flag=0;  
    for(j=1;j<k;j++){ 
        if (arr[j - 1] > arr[j])  
        {  
            swap=arr[j-1];
            arr[j-1]=arr[j];
            arr[j]=swap;
            flag=j;  
        }  
    }  
  } 

}
  1. 每一次循環從兩頭出發算出最大和最小值,代碼如下:
function bubble_sort_3(arr) {
  var low = 0;
  var high= arr.length-1; //設置變量的初始值
  var swap,j;
  while (low < high) {
    for (j= low; j< high; ++j) {         //正向冒泡,找到最大者
      if (arr[j]> arr[j+1]) {
        swap = arr[j]; arr[j]=arr[j+1];arr[j+1]=swap;
      }
    }
    --high;  //修改high值, 前移一位
    for (j=high; j>low; --j) {          //反向冒泡,找到最小者
      if (arr[j]<arr[j-1]) {
        swap = arr[j]; arr[j]=arr[j-1];arr[j-1]=swap;
      }
    } 
    ++low;  //修改low值,後移一位
  }
  return arr;
}
  1. 在代碼3的基礎上記錄每次掃描最後一次交換的位置,下次掃描的時候只要掃描到上次的最後交換位置就行,同代碼2,代碼如下:
function bubble_sort_3(arr) {
  var low = 0;
  var high= arr.length-1; //設置變量的初始值
  var swap,j;
  while (low < high) {
    var pos1 = 0,pos2=0; 
    for (let i= low; i< high; ++i) { //正向冒泡,找到最大者
      if (arr[i]> arr[i+1]) {
        swap = arr[i]; arr[i]=arr[i+1];arr[i+1]=swap;
        pos1 = i ;
      }
    }

    high = pos1;// 記錄上次位置

    for (let j=high; j>low; --j) { //反向冒泡,找到最小者
      if (arr[j]<arr[j-1]) {
        swap = arr[j]; arr[j]=arr[j-1];arr[j-1]=swap;  
        pos2 = j;
      }
    }   
    
    low = pos2; //修改low值
  }
  return arr;
}

冒泡排序動圖演示:

二、快速排序

算法介紹:

快速排序是對冒泡排序的一種改進,第一趟排序時將數據分成兩部分,一部分比另一部分的所有數據都要小。然後遞歸調用,在兩邊都實行快速排序。
function quick_sort(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 quick_sort(left).concat([pivot],quick_sort(right));
}

快速排序動圖演示:

三、選擇排序

算法介紹:

選擇排序就是從一個未知數據空間裏,選取之最放到一個新的空間

代碼如下:

function selection_sort(arr) {
  var len = arr.length;
  var minIndex, swap;
  for (var i = 0; i < len - 1; i++) {
    minIndex = i;
    for (var j = i + 1; j < len; j++) {
      if (arr[j] < arr[minIndex]) { //尋找最小的數
        minIndex = j; //將最小數的索引保存
      }
    }
    swap = arr[i];
    arr[i] = arr[minIndex];
    arr[minIndex] = swap;
  }
  return arr;
}

選擇排序動圖演示:

四、插入排序

算法介紹:

  1. 從第一個默認被排好序的元素開始
  2. 取出下一個元素,在已經排序的元素序列中從後向前掃描
  3. 如果已排序的元素大於取出的元素,則將其分別向後移動一位
  4. 直到找到已排序的元素中小於或等於取出的元素,將取出的元素放到它的後一位
  5. 重複步驟2

代碼如下:

function insertion_sort(arr) {
  for (var i = 1; i < arr.length; i++) {
    var key = arr[i];
    var j = i - 1;
    while ( arr[j] > key) {
      arr[j + 1] = arr[j];
         j--;
    }
    arr[j + 1] = key;
  }
  return arr;
}

插入排序算法改進-二分法插入排序:

function binaryInsertion_sort(arr) {
  for (var i = 1; i < arr.length; i++) {
    var key = arr[i], left = 0, right = i - 1;
    while (left <= right) {
      var middle = parseInt((left + right) / 2);
      if (key < arr[middle]) {
        right = middle - 1;
      } else {
        left = middle + 1;
      }
    }
    for (var j = i - 1; j >= left; j--) {
      arr[j + 1] = arr[j];
    }
    arr[left] = key;
  }
  return arr;
}

插入排序法動圖演示:

五、希爾排序

算法介紹:

希爾排序是冒泡排序的一種更高效率的實現。它與冒泡排序的不同之處在於,它會優先比較距離較遠的元素。希爾排序的核心在於間隔序列的設定。

上圖中先每差5爲一組進行比較,之後再每差2爲一組驚醒比較,最後就是兩兩比較。代碼如下:

function shell_sort(arr) {
  var len = arr.length,
  temp,
  gap = 1;
  while(gap < len/5) { //動態定義間隔序列
    gap =gap*5+1;
  }
  for (gap; gap > 0; gap = Math.floor(gap/5)) {
    for (var i = gap; i < len; i++) {
      temp = arr[i];
      for (var j = i-gap; j >= 0 && arr[j] > temp; j-=gap) {
        arr[j+gap] = arr[j];
      }
      arr[j+gap] = temp;
    }
  }
  return arr;
}

六、歸併排序

算法介紹:

作爲一種典型的分而治之思想的算法應用,歸併排序的實現由兩種方法:
  • 自上而下的遞歸(所有遞歸的方法都可以用迭代重寫,所以就有了第2種方法)
  • 自下而上的迭代

代碼如下:

function merge_sort(arr) {  //採用自上而下的遞歸方法
    var len = arr.length;
    if(len < 2) {
        return arr;
    }
    var middle = Math.floor(len / 2),
        left = arr.slice(0, middle),
        right = arr.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;
}

歸併排序動圖演示 :

七、堆排序

首先明白什麼是堆,堆其實可以這麼理解,類似金字塔,一層有一個元素,兩層有兩個元素,三層有四個元素,每層從數組中取元素,從左到右的順序放到堆相應的位置上,也就是說每一層元素個數爲2n-1 ;(n 代表行數),這就完成了建堆。

堆排序可以說是一種利用堆的概念來排序的選擇排序。分爲兩種方法:
  • 大頂堆:每個節點的值都大於或等於其子節點的值,在堆排序算法中用於升序排列
  • 小頂堆:每個節點的值都小於或等於其子節點的值,在堆排序算法中用於降序排列

代碼如下:

var len;    //因爲聲明的多個函數都需要數據長度,所以把len設置成爲全局變量

function buildMaxHeap(arr) {   //建立大頂堆
    len = arr.length;
    for (var i = Math.floor(len/2); i >= 0; i--) {
        heapify(arr, i);
    }
}

function heapify(arr, i) {     //堆調整
    var left = 2 * i + 1,
        right = 2 * i + 2,
        largest = i;

    if (left < len && arr[left] > arr[largest]) {
        largest = left;
    }

    if (right < len && arr[right] > arr[largest]) {
        largest = right;
    }

    if (largest != i) {
        swap(arr, i, largest);
        heapify(arr, largest);
    }
}

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

function heapSort(arr) {
    buildMaxHeap(arr);

    for (var i = arr.length-1; i > 0; i--) {
        swap(arr, 0, i);
        len--;
        heapify(arr, 0);
    }
    return arr;
}

堆排序動圖演示:

八、計數排序

算法介紹:

計數排序的核心在於將輸入的數據值轉化爲鍵存儲在額外開闢的數組空間中。
作爲一種線性時間複雜度的排序,計數排序要求輸入的數據必須是有確定範圍的整數。

代碼如下:

function counting_sort(arr, maxValue) {
    var bucket = new Array(maxValue+1),
        sortedIndex = 0;
        arrLen = arr.length,
        bucketLen = maxValue + 1;

    for (var i = 0; i < arrLen; i++) {
        if (!bucket[arr[i]]) {
            bucket[arr[i]] = 0;
        }
        bucket[arr[i]]++;
    }

    for (var j = 0; j < bucketLen; j++) {
        while(bucket[j] > 0) {
            arr[sortedIndex++] = j;
            bucket[j]--;
        }
    }

    return arr;
}

計數排序動圖演示:

九、桶排序

桶排序是計數排序的升級版。它利用了函數的映射關係,高效與否的關鍵就在於這個映射函數的確定。
爲了使桶排序更加高效,我們需要做到這兩點:

在額外空間充足的情況下,儘量增大桶的數量
使用的映射函數能夠將輸入的N個數據均勻的分配到K個桶中
同時,對於桶中元素的排序,選擇何種比較排序算法對於性能的影響至關重要。
什麼時候最快(Best Cases):
  • 當輸入的數據可以均勻的分配到每一個桶中

什麼時候最慢(Worst Cases):

  • 當輸入的數據被分配到了同一個桶中

代碼演示:

function bucketSort(arr, bucketSize) {
    if (arr.length === 0) {
      return arr;
    }

    var i;
    var minValue = arr[0];
    var maxValue = arr[0];
    for (i = 1; i < arr.length; i++) {
      if (arr[i] < minValue) {
          minValue = arr[i];                //輸入數據的最小值
      } else if (arr[i] > maxValue) {
          maxValue = arr[i];                //輸入數據的最大值
      }
    }

    //桶的初始化
    var DEFAULT_BUCKET_SIZE = 5;            //設置桶的默認數量爲5
    bucketSize = bucketSize || DEFAULT_BUCKET_SIZE;
    var bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;   
    var buckets = new Array(bucketCount);
    for (i = 0; i < buckets.length; i++) {
        buckets[i] = [];
    }

    //利用映射函數將數據分配到各個桶中
    for (i = 0; i < arr.length; i++) {
        buckets[Math.floor((arr[i] - minValue) / bucketSize)].push(arr[i]);
    }

    arr.length = 0;
    for (i = 0; i < buckets.length; i++) {
        insertionSort(buckets[i]);                      //對每個桶進行排序,這裏使用了插入排序
        for (var j = 0; j < buckets[i].length; j++) {
            arr.push(buckets[i][j]);                      
        }
    }

    return arr;
}

十、基數排序

基數排序須知:

基數排序有兩種方法:
  • MSD 從高位開始進行排序
  • LSD 從低位開始進行排序

基數排序 vs 計數排序 vs 桶排序:

這三種排序算法都利用了桶的概念,但對桶的使用方法上有明顯差異:
  • 基數排序:根據鍵值的每位數字來分配桶
  • 計數排序:每個桶只存儲單一鍵值
  • 桶排序:每個桶存儲一定範圍的數值

代碼演示:

function radix_sort(arr, maxDigit) {
  var mod = 10;
  var dev = 1;
  var counter = [];
  for (var i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
    for(var j = 0; j < arr.length; j++) {
      var bucket = parseInt((arr[j] % mod) / dev);
      if(counter[bucket]== null) {
        counter[bucket] = [];
      }
    counter[bucket].push(arr[j]);
    }
    var pos = 0;
    for(var j = 0; j < counter.length; j++) {
      var value = null;
      if(counter[j]!=null) {
        while ((value = counter[j].shift()) != null) {
          arr[pos++] = value;
        }
      }
    }
  }
  return arr;
}

基數排序動圖演示:
圖片描述

歡迎關注

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章