[03]算法排序

https://segmentfault.com/a/1190000011294349

前言

排序算法多是你學編程第一個學習的算法,還記得冒泡嗎?javascript

固然,排序和查找兩類算法是面試的熱門選項。若是你是一個會寫快排的程序猿,面試官在比較你和一個連快排都不會寫的人的時候,會優先選擇你的。那麼,前端須要會排序嗎?答案是毋庸置疑的,必須會。如今的前端對計算機基礎要求愈來愈高了,若是連排序這些算法都不會,那麼發展前景就有限了。本篇將會總結一下,在前端的一些排序算法。若是你喜歡個人文章,歡迎評論,歡迎Star~。歡迎關注個人github博客前端

正文

首先,咱們能夠先來看一下js自身的排序算法sort()java

Array.sort

相信每一個使用js的都用過這個函數,可是,這個函數自己有些優勢和缺點。咱們能夠經過一個例子來看一下它的功能:c++

const arr = [1, 20, 10, 30, 22, 11, 55, 24, 31, 88, 12, 100, 50]; console.log(arr.sort()); //[ 1, 10, 100, 11, 12, 20, 22, 24, 30, 31, 50, 55, 88 ] console.log(arr.sort((item1, item2) => item1 - item2)); //[ 1, 10, 11, 12, 20, 22, 24, 30, 31, 50, 55, 88, 100 ]

相信你也已經看出來它在處理上的一些差別了吧。首先,js中的sort會將排序的元素類型轉化成字符串進行排序。不過它是一個高階函數,能夠接受一個函數做爲參數。而咱們能夠經過傳入內部的函數,來調整數組的升序或者降序。git

sort函數的性能:相信對於排序算法性能來講,時間複雜度是相當重要的一個參考因素。那麼,sort函數的算法性能如何呢?經過v8引擎的源碼能夠看出,Array.sort是經過javascript來實現的,而使用的算法是快速排序,可是從源碼的角度來看,在實現上明顯比咱們所使用的快速排序複雜多了,主要是作了性能上的優化。因此,咱們能夠放心的使用sort()進行排序。github

冒泡排序

冒泡排序,它的名字由來於一副圖——魚吐泡泡,泡泡越往上越大。面試

回憶起這個算法,仍是最初大一的c++課上面。仍是本身上臺,在黑板上實現的呢!算法

思路:第一次循環,開始比較當前元素與下一個元素的大小,若是比下一個元素小或者相等,則不須要交換兩個元素的值;若比下一個元素大的話,則交換兩個元素的值。而後,遍歷整個數組,第一次遍歷完以後,相同操做遍歷第二遍。編程

圖例:segmentfault

代碼實現:

const arr = [1, 20, 10, 30, 22, 11, 55, 24, 31, 88, 12, 100, 50]; function bubbleSort(arr){ for(let i = 0; i < arr.length - 1; i++){ for(let j = 0; j < arr.length - i - 1; j++){ if(arr[j] > arr[j + 1]){ swap(arr, j, j+1); } } } return arr; } function swap(arr, i, j){ let temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } console.log(arr);

代碼地址

性能:

  • 時間複雜度:平均時間複雜度是O(n^2)
  • 空間複雜度:因爲輔助空間爲常數,因此空間複雜度是O(1);

改進:

咱們能夠對冒泡排序進行改進,使得它的時間複雜度在大多數順序的狀況下,減少到O(n);

  1. 加一個標誌位,若是沒有進行交換,將標誌位置爲false,表示排序完成。

代碼地址

const arr = [1, 20, 10, 30, 22, 11, 55, 24, 31, 88, 12, 100, 50]; function swap(arr, i, j){ const temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } for(let i = 0; i < arr.length - 1; i++){ let flag = false; for(let j = 0; j < arr.length - 1 - i; j++){ if(arr[j] > arr[j+1]){ swap(arr, j, j+1); flag = true; } } if(!flag){ break; } } console.log(arr); //[ 1, 10, 11, 12, 20, 22, 24, 30, 31, 50, 55, 88, 100 ]
  1. 記錄最後一次交換的位置, 由於最後一次交換的數,是在這一次排序當中最大的數,以後的數都比它大。在最佳狀態時,時間複雜度也會縮小到O(n);

代碼地址

const arr = [1, 20, 10, 30, 22, 11, 55, 24, 31, 88, 12, 100, 50 ,112]; function swap(arr, i, j){ let temp = arr[i]; arr[i] = arr[j]; arr[j] = temp } function improveBubble(arr, len){ for(let i = len - 1; i >= 0; i--){ let pos = 0; for(let j = 0; j < i; j++){ if(arr[j] > arr[j+1]){ swap(arr, j, j+1); pos = j + 1; } } len = pos + 1; } return arr; } console.log(improveBubble(arr, arr.length)); //[ 1, 10, 11, 12, 20, 22, 24, 30, 31, 50, 55, 88, 100, 112 ]

選擇排序

選擇排序,即每次都選擇最小的,而後換位置

思路:

第一遍,從數組中選出最小的,與第一個元素進行交換;第二遍,從第二個元素開始,找出最小的,與第二個元素進行交換;依次循環,完成排序

圖例:

代碼實現:

const arr = [1, 20, 10, 30, 22, 11, 55, 24, 31, 88, 12, 100, 50]; function swap(arr, i, j){ var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } function selectionSort(arr){ for(let i = 0; i < arr.length - 1; i++){ let index = i; for(let j = i+1; j < arr.length; j++){ if(arr[index] > arr[j]){ index = j; } } swap(arr, i, index); } return arr; } console.log(selectionSort(arr)); //[ 1, 10, 11, 12, 20, 22, 24, 30, 31, 50, 55, 88, 100 ]

代碼地址

性能:

  • 時間複雜度:平均時間複雜度是O(n^2),這是一個不穩定的算法,由於每次交換以後,它都改變了後續數組的順序。
  • 空間複雜度:輔助空間是常數,空間複雜度爲O(1);

插入排序

插入排序,即將元素插入到已排序好的數組中

思路:

首先,循環原數組,而後,將當前位置的元素,插入到以前已排序好的數組中,依次操做。

圖例:

代碼實現:

const arr = [1, 20, 10, 30, 22, 11, 55, 24, 0, 31, 88, 12, 100, 50 ,112]; function insertSort(arr){ for(let i = 0; i < arr.length; i++){ let temp = arr[i]; for(let j = 0; j < i; j++){ if(temp < arr[j] && j === 0){ arr.splice(i, 1); arr.unshift(temp); break; }else if(temp > arr[j] && temp < arr[j+1] && j < i - 1){ arr.splice(i, 1); arr.splice(j+1, 0, temp); break; } } } return arr; } console.log(insertSort(arr)); //[ 0, 1, 10, 11, 12, 20, 22, 24, 30, 31, 50, 55, 88, 100, 112 ]

代碼地址

性能:

  • 時間複雜度:平均算法複雜度爲O(n^2)
  • 空間複雜度:輔助空間爲常數,空間複雜度是O(1)

咱們仨之間

其實,三個算法都是難兄難弟,由於算法的時間複雜度都是在O(n^2)。在最壞狀況下,它們都須要對整個數組進行從新調整。只是選擇排序比較不穩定。

快速排序

快速排序,從它的名字就應該知道它很快,時間複雜度很低,性能很好。它將排序算法的時間複雜度下降到O(nlogn)

思路:

首先,咱們須要找到一個基數,而後將比基數小的值放在基數的左邊,將比基數大的值放在基數的右邊,以後進行遞歸那兩組已經歸類好的數組。

圖例:

原圖片太大,放一張小圖,而且附上原圖片地址,有興趣的能夠看一下:

原圖片地址

代碼實現:

const arr = [30, 32, 6, 24, 37, 32, 45, 21, 38, 23, 47]; function quickSort(arr){ if(arr.length <= 1){ return arr; } let temp = arr[0]; const left = []; const right = []; for(var i = 1; i < arr.length; i++){ if(arr[i] > temp){ right.push(arr[i]); }else{ left.push(arr[i]); } } return quickSort(left).concat([temp], quickSort(right)); } console.log(quickSort(arr));

代碼地址

性能:

  • 時間複雜度:平均時間複雜度O(nlogn),只有在特殊狀況下會是O(n^2),不過這種狀況很是少
  • 空間複雜度:輔助空間是logn,因此空間複雜度爲O(logn)

歸併排序

歸併排序,即將數組分紅不一樣部分,而後注意排序以後,進行合併

思路:

首先,將相鄰的兩個數進行排序,造成n/2對,而後在每兩對進行合併,不斷重複,直至排序完。

圖例:

代碼實現:

//迭代版本 const arr = [3,44,38,5,47,15,36,26,27,2,46,4,19,50,48] function mergeSort(arr){ const len = arr.length; for(let seg = 1; seg < len; seg += seg){ let arrB = []; for(let start = 0; start < len; start += 2*seg){ let row = start, mid = Math.min(start+seg, len), heig = Math.min(start + 2*seg, len); let start1 = start, end1 = mid; let start2 = mid, end2 = heig; while(start1 < end1 && start2 < end2){ arr[start1] < arr[start2] ? arrB.push(arr[start1++]) : arrB.push(arr[start2++]); } while(start1 < end1){ arrB.push(arr[start1++]); } while(start2 < end2){ arrB.push(arr[start2++]); } } arr = arrB; } return arr; } console.log(mergeSort(arr));

代碼地址

//遞歸版 const arr = [3,44,38,5,47,15,36,26,27,2,46,4,19,50,48]; function mergeSort(arr, seg = 1){ const len = arr.length; if(seg > len){ return arr; } const arrB = []; for(var start = 0; start < len; start += 2*seg){ let low = start, mid = Math.min(start+seg, len), heig = Math.min(start+2*seg, len); let start1 = low, end1 = mid; let start2 = mid, end2 = heig; while(start1 < end1 && start2 < end2){ arr[start1] < arr[start2] ? arrB.push(arr[start1++]) : arrB.push(arr[start2++]); } while(start1 < end1){ arrB.push(arr[start1++]); } while(start2 < end2){ arrB.push(arr[start2++]); } } return mergeSort(arrB, seg * 2); } console.log(mergeSort(arr));

代碼地址

性能:

  • 時間複雜度:平均時間複雜度是O(nlogn)
  • 空間複雜度:輔助空間爲n,空間複雜度爲O(n)

基數排序

基數排序,就是將數的每一位進行一次排序,最終返回一個正常順序的數組。

思路:

首先,比較個位的數字大小,將數組的順序變成按個位依次遞增的,以後再比較十位,再比較百位的,直至最後一位。

圖例:

代碼實現:

const arr = [3221, 1, 10, 9680, 577, 9420, 7, 5622, 4793, 2030, 3138, 82, 2599, 743, 4127, 10000]; function radixSort(arr){ let maxNum = Math.max(...arr); let dis = 0; const len = arr.length; const count = new Array(10); const tmp = new Array(len); while(maxNum >=1){ maxNum /= 10; dis++; } for(let i = 1, radix = 1; i <= dis; i++){ for(let j = 0; j < 10; j++){ count[j] = 0; } for(let j = 0; j < len; j++){ let k = parseInt(arr[j] / radix) % 10; count[k]++; } for(let j = 1; j < 10; j++){ count[j] += count[j - 1]; } for(let j = len - 1; j >= 0 ; j--){ let k = parseInt(arr[j] / radix) % 10; tmp[count[k] - 1] = arr[j]; count[k]--; } for(let j = 0; j < len; j++){ arr[j] = tmp[j]; } radix *= 10; } return arr; } console.log(radixSort(arr));

代碼地址

性能:

  • 時間複雜度:平均時間複雜度O(k*n),最壞的狀況是O(n^2)

總結

咱們一共實現了6種排序算法,對於前端開發來講,熟悉前面4種是必須的。特別是快排,基本面試必考題。本篇的內容總結分爲六部分:

  • 冒泡排序
  • 選擇排序
  • 插入排序
  • 快速排序
  • 歸併排序
  • 基數排序

排序算法,是算法的基礎部分,須要明白它的原理,總結下來排序能夠分爲比較排序和統計排序兩種方式,本篇前5種均爲比較排序,基數排序屬於統計排序的一種。但願看完的你,可以去動手敲敲代碼,理解一下

相關文章
相關標籤/搜索