排序算法的Javascript實現

1.冒泡排序:node

比較相鄰的兩個數,若是前一個數大於後一個數,就將這兩個數換位置。每一次遍歷都會將本次遍歷最大的數冒泡到最後。爲了將n個數排好序,須要n-1次遍歷。
若是某次遍歷中,沒有調整任何兩個相鄰的數的位置關係,說明此時數組已排好序,能夠結束程序。git

Array.prototype.bubbleSort = function () {
  let i, j;
  for (i = 1; i < this.length; i++) {  //表示本次是第i次遍歷
    let changed = false;
    for (j = 0; j < this.length - i; j++) {   //訪問序列爲arr[0:length-i]
      if(this[j] > this[j + 1]){  //發現前一個數大於後一個時,互換位置
        [this[j],this[j+1]] = [this[j+1],this[j]];
        changed = true;
      }
    }
    if(!changed) {      //若是本輪遍歷沒有發現位置調整,結束排序函數
      break;
    }
  }
};

let arr = [43, 21, 10, 5, 9, 15, 32, 57, 35];
arr.bubbleSort();
console.log(arr);

2.選擇排序shell

第i輪遍歷arr[0:n-i]選出最大的數,與arr[n-i]互換。數組

Array.prototype.selectSort = function () {
  let i, j;
  for (i = 1; i < this.length; i++) {     //表示本次是第i次遍歷
    let maxIndex = 0;
    for (j = 0; j <= this.length - i; j++) {  //訪問子序列爲arr[0:this.length-i]
      if (this[j] > this[maxIndex]) {   //當前值大於當前最大值時,記錄索引
        maxIndex = j;
      }
    }
    //將子數組最大值索引的值,與子數組末尾的值互換
    [this[this.length - i], this[maxIndex]] = [this[maxIndex], this[this.length - i]]
  }
};

let arr = [43, 21, 10, 5, 9, 15, 32, 57, 35];
arr.selectSort();
console.log(arr);

3.插入排序
數組的前面部分已經排好序,要把當前數字插入到前面已排好序的數組的相應位置。可能有人會有疑問爲何默認數組前面部分已排好序?是怎麼排好序的?是由於當排序開始時,從第2個數字開始進行向前插入,此時當前數字索引爲1,當前數字前面僅有一個數字,所以能夠認爲前面部分已經排好序,將這個數字插入到相應位置以後數組仍然是有序的。每次都將當前數字插入到對應的位置,所以每次插入以後前面的數組還是排好序的。dom

Array.prototype.insertSort = function () {
  let i, j;
  for (i = 1; i < this.length; i++) {   //i表示當前要向前插入的數字的索引,從1(即第2個數)開始前插
    let val = this[i];   //記錄當前要前插的數的大小
    /*
    * 用指針j來遍歷第i個數字前面的,已經排好序的子數組。當j沒有指到頭,而且j的數字大於要插入的數字時,說明
    * j還要向前遍歷,直到發現一個比要插入數字小的位置pos,而後將這個數字插到pos+1處。若是j已經指到頭了,
    * 到了-1了尚未找到比當前數字小的位置,就把當前數字放在索引0處。
    * */
    for (j = i - 1; j >= 0 && this[j] > val; j--) {  
      this[j + 1] = this[j];
    }
    this[j + 1] = val;
  }
};

let arr = [43, 21, 10, 5, 9, 15, 32, 57, 35];
arr.insertSort();
console.log(arr);

4.shell排序
加了step的插入排序。分別以索引數爲0,1,......step-1的元素爲起點,將其看作不一樣的組,0、0+step,0+2step,......,0+nstep爲一組,1,1+step,1+2step,.....,1+nstep爲一組依次分組,按照組爲單位進行插入排序。各組都已經插入排序一輪事後,將step除以2向下取整,再進行分組並將各組分別進行插入排序,直到step爲0。
step的取值與性能直接相關,須要思考後取值。
而且這裏的分組僅僅是邏輯上分組,並無開闢新的地址空間將其進行物理上的分組。函數

const {floor} = Math;

//這個和插入排序相同,只不過加了step
Array.prototype.shellInsertSort = function (startIndex, step) {
  let i, j;
  for (i = startIndex + step; i < this.length; i += step) {
    let val = this[i];
    for (j = i - step; j >= 0 && this[j] > val; j -= step) {
      this[j + step] = this[j];
    }
    this[j + step] = val;
  }
};

Array.prototype.shellSort = function () {
  let i, step;
  for (step = floor(this.length / 2); step > 0; step = floor(step / 2)) {
    for (i = 0; i < step; i++) {
      this.shellInsertSort(i, step);
    }
  }
};

let arr = [43, 21, 10, 5, 9, 15, 32, 57, 35];
arr.shellSort(true);
console.log(arr);

5.合併排序性能

舉個例子: 有 43 12 32 29 66 78 31這個數組要用合併排序。
先將相鄰兩數分爲一組進行合併 43|12 32|29 66|78 31
結果爲12 43 29 32 66 78 31ui

再將組的大小乘以二 (12 43|29 32) (66 78|31)
本次合併後結果爲 12 29 32 43 31 66 78this

再將組的大小乘以二 12 43 29 32 | 66 78 31
合併結果:12 29 31 32 43 66 78prototype

合併的過程當中要開闢新的數組arr,創建兩個指針i,j分別指向arr1與arr2,此時arr1與arr2都是排好序的,而後每次都將arr1[i]與arr2[j]較小的數加到arr中並將指針後移。最後哪一個數組有剩餘的數在追加到arr後面。

const {min} = Math;

function merge(arr1, arr2,) {
  let arr = [];
  let i = 0, j = 0;
  while (i < arr1.length && j < arr2.length) {
    arr1[i] < arr2[j] ? arr.push(arr1[i++]) : arr.push(arr2[j++]);
  }
  return i < arr1.length ? arr.concat(arr1.slice(i)) : arr.concat(arr2.slice(j))
}

Array.prototype.mergeSort = function () {
  let groupSize, i, secondPartSize, firstPart, secondPart, totalSize;
  //最初合併時,每組的大小僅爲1,而後將組的大小乘以2。
  for (groupSize = 1; groupSize < this.length; groupSize *= 2) {
    for (i = 0; i < this.length; i += 2 * groupSize) {
      //前半段大小必定是groupSize,後半段則不必定
      secondPartSize = min(groupSize, this.length - i - groupSize);
      totalSize = secondPartSize + groupSize;
      //截取先後部分數組,將其排序
      firstPart = this.slice(i, i + groupSize);
      secondPart = this.slice(i + groupSize, i + groupSize + secondPartSize);
      this.splice(i, totalSize, ...merge(firstPart, secondPart));
    }
  }
};

let arr = [43, 21, 10, 5, 9, 15, 32, 57, 35];
arr.mergeSort();
console.log(arr);

6.天然合併排序

合併排序的分組是死板的沒有利用到數組中本來就是順序的子序列。

若是數組爲 43 56 79 12 33 90 66
將其分組爲 43 56 79 | 12 33 90 | 66
再將相鄰的,本來就是從小到大的順序的數組進行合併,效果會更好。

function merge(arr1, arr2) {
  let arr = [], i = 0, j = 0;
  while (i < arr1.length && j < arr2.length) {
    arr.push(arr1[i] < arr2[j] ? arr1[i++] : arr2[j++])
  }
  return arr.concat(i < arr1.length ? arr1.slice(i) : arr2.slice(j));
}

function getSortedArrList(arr) {
  //記錄下已經本來就是從小到大順序的子數組
  let sortedArrList = [];
  let childArr = [arr[0]];
  for (let i = 1; i < arr.length; i++) {
    //當前值小於上一個值時,將childArr加入sortedArrList中,建立新的childArr,並加入當前值。
    if (arr[i] < arr[i - 1]) {
      sortedArrList.push(childArr);
      childArr = [arr[i]];
    }
    //不然,將當前值加入到childArr中
    else {
      childArr.push(arr[i]);
    }
  }
  sortedArrList.push(childArr);
  return sortedArrList;
}

Array.prototype.naturalMergeSort = function() {
  let sortedArrList = getSortedArrList(this);  //獲取本來從小到大順序的子數組

  while (sortedArrList.length > 1) {    //當還有兩個及以上的數組沒合併完成時
    let newSortedArrList = [];
    for (let i = 0; i < sortedArrList.length; i += 2) {
      if (i !== sortedArrList.length - 1) {
        newSortedArrList.push(merge(sortedArrList[i], sortedArrList[i + 1]));
      }
      else {
        newSortedArrList.push(sortedArrList[i]);
      }
    }
    sortedArrList = newSortedArrList;
  }
  this.splice(0,this.length,...sortedArrList[0]);
};

let arr = [43, 21, 10, 5, 9, 15, 32, 57, 35];
arr.naturalMergeSort();
console.log(arr);

7.基數排序(LSD least significant digit first)
LSD中沒有數值之間的比較。創建一個[10][]的二維數組arr。
挑選出要排序數組中最大的數字,計算該數字的位數記爲digitNum。將數組中的全部數字填充到digitNum位,位數不夠的高位補0。
而後遍歷digitNum次,從低位開始。第i次遍歷按照將數組中元素的第i位的數值,將元素num放到二維數組相應位置處,若是num第i位數值爲n,則執行arr[n].push(num)的操做。每次遍歷以後,將arr[0:9]各數組的元素依次取出,而且從新初始化二維數組。直到遍歷到最高位爲止,再取出的就是已經排好序的。

const {max} = Math;

function initBarrel() {
  let barrel = [];
  for (let i = 0; i < 10; i++) {
    barrel[i] = [];
  }
  return barrel;
}

function radixSort(arr) {
  let barrel = initBarrel();
  let figureNum = max(...arr).toString().length;  //計算最大的數字的位數
  arr = arr.map(num => num.toString().padStart(figureNum, '0'));  //將數字填充到figureNum位
  for (let i = 0; i < figureNum; i++) {
    let index = figureNum - i - 1;  //本次根據第index位來選擇放入哪一個桶
    arr.forEach(numStr => {         //將填充過的數組放入桶中
      let num = Number(numStr[index]);
      barrel[num].push(numStr);
    });
    arr = barrel.reduce((prevArr, curArr) => prevArr.concat(curArr), []);//彙總barrel中的數
    barrel = initBarrel();    //初始化barrel
  }
  return arr.map(num => Number(num));   //最終轉爲數字形式
}

Array.prototype.radixSort = function () {
  let arr = radixSort(this);
  this.splice(0, this.length, ...arr);
};

let arr = [1234342, 52165, 75, 1, 356, 575, 765433212, 57994, 3535];
arr.radixSort();
console.log(arr);

8.基數排序(MSD most significant digit first)
從高位開始,依然沒有數值之間的比較。
將最初的元素序列按照各元素最高位的數值進行分組,將分組後,組中只有一個元素或者多個相等元素的組拼接到result數組中,而有多個不一樣元素的組再遞歸地向下分,取的位次依次減小。

const {max} = Math;

function initBarrel() {
  let barrel = [];
  for (let i = 0; i < 10; i++) {
    barrel[i] = [];
  }
  return barrel;
}

//判斷當前桶中是否只有惟一值 有的桶中可能只有一種值,可是有多個重複項
function unique(barrel) {
  return new Set(barrel).size <= 1;
}

Array.prototype.radixSort = function () {
  let result = [];
  let figureNum = max(...this).toString().length;
  this.splice(0, this.length, ...this.map(num => num.toString().padStart(figureNum, '0')));
  radixGroup(this, 0, figureNum, result);
  this.splice(0, this.length, ...result.map(numStr => Number(numStr)));
};

function radixGroup(group, index, figureNum, result) {    //輸入的group是一組numStr,index是當前分桶依據第幾位數
  if (index < figureNum) {
    let barrel = initBarrel();
    group.forEach(numStr => {
      let idx = Number(numStr[index]);
      barrel[idx].push(numStr);
    });

    barrel.forEach(subBarrel => {
      if(unique(subBarrel)) {
        subBarrel.forEach(num => {
          result.push(num);
        })
      }
      else {
        radixGroup(subBarrel,index+1,figureNum,result);
      }
    })
  }
}
let arr = [1234342, 52165, 75, 1, 356, 575, 765433212, 57994, 3535];
arr.radixSort();
console.log(arr);

9.快速排序

將數組頭部的元素pivotNum做爲一個基準,經過兩個指針指向數組的頭部和尾部,通過一次partition之後將pivotNum放在一個位置pivot,pivot前面的數小於pivotNum,後面的數大於pivotNum。
爲了防止最壞狀況的發生,能夠在數組中隨機選出一個數來與數組頭部元素換位置,來下降具體實例與最壞狀況的關聯性。

const {floor, random} = Math;

function randomIndex(start, end) {
  return floor(random() * (end - start + 1)) + start;
}

function partition(arr, start, end) {
  let index = randomIndex(start, end);
  [arr[start], arr[index]] = [arr[index], arr[start]];

  let value = arr[start];

  while (start < end) {
    while (start < end && arr[end] > value) end--;
    arr[start] = arr[end];
    while (start < end && arr[start] < value) start++;
    arr[end] = arr[start];
  }

  arr[start] = value;
  return start;
}

function quickSort(arr, start, end) {
  if (start < end) {
    let pivot = partition(arr, start, end);
    quickSort(arr, start, pivot - 1);
    quickSort(arr, pivot + 1, end);
  }
}

Array.prototype.quickSort = function (asc = true) {
  quickSort(this, 0, this.length - 1, asc)
};

let arr = [43, 21, 10, 5, 9, 15, 32, 57, 35];
arr.quickSort();
console.log(arr);

10.堆排序
將數組看作徹底二叉樹,所以節點i的左右子節點的索引分別爲2i+1與2i+2。經過從根節點開始令小的值下沉,或者從最後的葉節點開始令大的值上浮的方法,將一個數組構形成一個大根堆。再將大根堆的頭元素與尾元素換位置,這樣就將當前最大值置換到了尾部。而後下次構建大根堆的時候,將剛置換過的尾部元素刨除在外不作爲節點。

const {floor, max} = Math;

function getBiggestNodeIndex(...nodes) {
  return nodes.indexOf(max(...nodes));
}

//將arr從0開始,長度爲length的子數組構建爲堆
function constructHeap(arr, length) {
  let adjusted = true;  //adjusted來標識本次堆是否做出了調整,若未調整說明堆已構建完畢
  while (adjusted) {
    adjusted = false;
    for (let i = 0; i < floor(length / 2); i++) {
      //當只有左節點時
      if (2 * i + 2 === length) {
        //當父節點比左節點小的時候
        if (arr[i] < arr[2 * i + 1]) {
          //互換
          [arr[i], arr[2 * i + 1]] = [arr[2 * i + 1], arr[i]];
          adjusted = true;
        }
      }
      //當同時有左節點和右節點時
      else {
        //判斷三個中最大的節點
        let biggestNodeIndex = getBiggestNodeIndex(arr[i], arr[2 * i + 1], arr[2 * i + 2]);
        //若父節點不是最大的,則和最大的交換
        //若是biggestNodeIndex爲0,說明本身最大,爲1,說明左節點大,爲2,說明右節點大
        switch (biggestNodeIndex) {
          case 0:
            break;
          case 1:
            [arr[i], arr[2 * i + 1]] = [arr[2 * i + 1], arr[i]];
            adjusted = true;
            break;
          case 2:
            [arr[i], arr[2 * i + 2]] = [arr[2 * i + 2], arr[i]];
            adjusted = true;
            break;
        }
      }
    }
  }
}

function heepSort(arr) {
  //只將arr從0開始,長度爲length的子數組構建成大根堆
  let length = arr.length;
  while (length > 1) {
    constructHeap(arr, length);
    [arr[0], arr[length-- - 1]] = [arr[length - 1], arr[0]];
  }
}

Array.prototype.heepSort = function () {
  heepSort(this);
};

let arr = [43, 21, 10, 5, 9, 15, 32, 57, 35];
arr.heepSort();
console.log(arr);
相關文章
相關標籤/搜索