【面試必備】前端常見的排序演算法
前言
演算法對於前端程式設計師來說可能並沒有後端程式設計師應用的多,但我們也得掌握一些基本演算法的思想,這無論是對我們找工作還是平時工作都有極大的幫助,現在越來越多的公司都會考察前端程式設計師的演算法能力了,所以我們有必要去學習一下前端常見演算法的基本思想。
如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公眾號首發,關注 前端南玖
第一時間獲取最新的文章~
文章收錄於github,歡迎star
氣泡排序
演算法描述
氣泡排序相信對於很多同學來說並不陌生,它應該是我們最經典的演算法了,無論學什麼語言,都能見到它的身影。基本思想:重複遍歷要排序的陣列,每次比較兩個元素的大小,如果順序錯誤就交換兩個元素的順序。遍歷陣列重複進行比較直到排序完成。
演算法實現
基本步驟
- 比較相鄰的兩個元素。如果第一個比第二個大,則交換位置;
- 對每一對相鄰元素重複第一個步驟,從開始第一對到結尾的最後一對,這樣在最後的元素應該會是最大的數;
- 針對所有的元素重複以上的步驟,除了最後一個;
- 重複步驟1~3,直到排序完成。
動畫演示
程式碼實現
/** * 外迴圈控制需要比較的元素,比如第一次排序後, 最後一個元素就不需要比較了,內迴圈則負責兩兩元素比較,將元素放到正確位置上 */ function bubbleSort(arr) { const len = arr.length for(let i=0; i<len; i++) { for(let j=0;j<len-1-i; j++) { // 注意邊界值 if(arr[j] > arr[j+1]){ [arr[j],arr[j+1]] = [arr[j+1],arr[j]] // 交換位置 } } } return arr } console.log(bubbleSort([3,44,15,36,26,27,2,46,4,19,50,48])) //[2,3,4,15,19,26,27,36,44,46,48,50]
時間複雜度:O(n^2)
快速排序
演算法描述
快速排序,是一種對氣泡排序改進的演算法,它是處理大資料最快的排序演算法之一了。基本思想:它是一種分而治之的演算法,找出一個參考值,通過遞迴的方式將資料依次分解為包含較小元素和較大元素的不同子序列。該演算法不斷重複這個步驟直至所有資料都是有序的。
演算法實現
基本步驟
-
選擇一個參考元素,將列表分割成兩個子序列;
-
對列表重新排序,將所有小於基準值的元素放在基準值前面,所有大於基準值的元素放在基準值的後面;
-
分別對較小元素的子序列和較大元素的子序列重複步驟1和2
動畫演示
程式碼實現
function quickSort(arr) { if(arr.length<=1) return arr const left = [],right = [],current = arr.splice(0,1) for(let i=0; i<arr.length; i++) { if(arr[i]<current) { // 小於參考值放左邊 left.push(arr[i]) }else{ // 否則放右邊 right.push(arr[i]) } } //遞迴上述步驟 return quickSort(left).concat(current,quickSort(right)) } console.log(quickSort([3,44,15,36,26,27,2,46,4,19,50,48])) //[2, 3, 4, 15, 19, 26, 27, 36, 44, 46, 48, 50]
時間複雜度:O(nlogn)
插入排序
演算法描述
插入排序是一種簡單直觀的排序演算法。基本思想:通過構建有序序列,對於未排序資料,在已排序序列中從後向前掃描,找到相應位置並插入。插入排序在實現上,通常採用in-place排序(即只需用到O(1)的額外空間的排序),因而在從後向前掃描過程中,需要反覆把已排序元素逐步向後挪位,為最新元素提供插入空間。
演算法實現
基本步驟
-
從第一個元素開始,該元素可以認為已經被排序;
-
取出下一個元素,在已經排序的元素序列中從後向前掃描;
-
如果該元素(已排序)大於新元素,將該元素移到下一位置;
-
重複步驟3,直到找到已排序的元素小於或者等於新元素的位置;
-
將新元素插入到該位置後;
-
重複步驟2~5。
動畫演示
程式碼實現
/**雙層迴圈,外迴圈控制未排序的元素,內迴圈控制已排序的元素,將未排序元素設為標杆,
與已排序的元素進行比較,小於則交換位置,大於則位置不動
*/
function insertSort(arr) {
let tem
for(let i=0; i<arr.length; i++) {
tem = arr[i]
for(let j=i; j>=0; j--){
if(arr[j-1] > tem){
arr[j] = arr[j-1]
}else {
arr[j] = tem
break
}
}
}
return arr
}
console.log(insertSort([3,44,15,36,26,27,2,46,4,19,50,48]))
//[2, 3, 4, 15, 19, 26, 27, 36, 44, 46, 48, 50]
時間複雜度O(n^2)
選擇排序
演算法描述
選擇排序是一種簡單直觀的排序演算法,基本思想:首先在待排序序列中選出最小或最大值,存放在排序序列起始位置,然後再從剩餘未排序元素中繼續尋找最小或最大元素,放到已排序序列末尾。以此類推,直到所有元素均排序完畢。
演算法實現
基本步驟
-
初始狀態:無序區為R[1..n],有序區為空;
-
第i趟排序(i=1,2,3...n-1)開始時,當前有序區和無序區分別為R[1..i-1]和R(i..n)。該趟排序從當前無序區中-選出關鍵字最小的記錄 R[k],將它與無序區的第1個記錄R交換,使R[1..i]和R[i+1..n)分別變為記錄個數增加1個的新有序區和記錄個數減少1個的新無序區;
-
n-1趟結束,陣列有序化了。
動畫演示
程式碼實現
/**
* 先假設第一個元素為最小的,然後通過迴圈找出最小元素,
* 然後同第一個元素交換,接著假設第二個元素,重複上述操作即可
*/
function selectSort(arr) {
let len = arr.length, minIndex, tem
for(let i=0; i<len-1; i++) {
minIndex = i //最小值下標
for(let j=i+1; j<len; j++) {
if(arr[j] < arr[minIndex]){
// 找出最小值
minIndex = j //更換最小值下標
}
}
// 交換位置
tem = arr[i]
arr[i] = arr[minIndex]
arr[minIndex] = tem
}
return arr
}
console.log(selectSort([3,44,15,36,26,27,2,46,4,19,50,48]))
//[2, 3, 4, 15, 19, 26, 27, 36, 44, 46, 48, 50]
時間複雜度O(n^2)
歸併排序
演算法描述
歸併排序是一種藉助“歸併”進行排序的方法,歸併的含義是將兩個或兩個以上的有序序列歸併成一個有序序列的過程。基本思想:將若干有序序列逐步歸併,最終歸併為一個有序序列。和選擇排序一樣,歸併排序的效能不受輸入資料的影響,但表現比選擇排序好的多,因為始終都是
O(n log n)
的時間複雜度。代價是需要額外的記憶體空間。
演算法實現
基本步驟
- 把長度為n的輸入序列分成兩個長度為n/2的子序列;
- 對這兩個子序列分別採用歸併排序;
- 將兩個排序好的子序列合併成一個最終的排序序列。
動畫演示
程式碼實現
// 將陣列一直等分,然後合併
function merge(left, right) {
let tem = []
while(left.length && right.length) {
if(left[0] < right[0]) {
tem.push(left.shift())
}else{
tem.push(right.shift())
}
}
return tem.concat(left,right)
}
function mergeSort(arr) {
const len = arr.length
if(len<2) return arr
let mid = Math.floor(len / 2), left = arr.slice(0,mid), right = arr.slice(mid)
return merge(mergeSort(left),mergeSort(right))
}
console.log(mergeSort([3,44,15,36,26,27,2,46,4,19,50,48]))
// [2, 3, 4, 15, 19, 26, 27, 36, 44, 46, 48, 50]
時間複雜度O(nlogn)
希爾排序
演算法描述
希爾排序是插入排序的一種。也稱縮小增量排序,是直接插入排序演算法的一種更高效的改進版本。希爾排序是非穩定排序演算法。基本思想:是把記錄按下標的一定增量分組,對每組使用直接插入排序演算法排序;隨著增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個檔案恰被分成一組,演算法便終止。
演算法實現
基本步驟
- 選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 按增量序列個數k,對序列進行k 趟排序;
- 每趟排序,根據對應的增量ti,將待排序列分割成若干長度為m 的子序列,分別對各子表進行直接插入排序。僅增量因子為1 時,整個序列作為一個表來處理,表長度即為整個序列的長度。
動畫演示
程式碼實現
function shellSort(arr) {
var len = arr.length;
for(var gap = Math.floor(len / 2); gap > 0; gap = Math.floor(gap / 2)) {
for(var i = gap; i < len;i++) {
var j = i;
var current = arr[i];
while (j - gap >= 0 && current < arr[j - gap]) {
arr[j] = arr[j - gap];
j = j - gap;
}
arr[j] = current;
}
}
return arr;
}
console.log(shellSort([50,70,60,80,61,84,83,88,87,99]))
//[50, 60, 61, 70, 80, 83, 84, 87, 88, 99]
時間複雜度:O(nlogn)
計數排序
演算法描述
計數排序是一種穩定的排序演算法。基本思想:使用一個額外的陣列C,其中第i個元素是待排序陣列A中值等於i的元素的個數。然後根據陣列C來將A中的元素排到正確的位置。它只能對整數進行排序。
演算法實現
基本步驟
- 找出待排序的陣列中最大和最小的元素;
- 統計陣列中每個值為i的元素出現的次數,存入陣列C的第i項;
- 對所有的計數累加(從C中的第一個元素開始,每一項和前一項相加);
- 反向填充目標陣列:將每個元素i放在新陣列的第C(i)項,每放一個元素就將C(i)減去1。
動畫演示
程式碼實現
function countingSort(arr) {
let len = arr.length, b = [], c = [], min = max = arr[0]
for(let i=0; i<len; i++) {
min = min <= arr[i] ? min : arr[i]
max = max >= arr[i] ? max : arr[i]
c[arr[i]] = c[arr[i]] ? c[arr[i]] + 1 : 1 // 計數
}
for(let i=min; i< max; i++) {
c[i+1] = (c[i+1] || 0) + (c[i] || 0)
}
for(let i=len-1; i>=0; i--) {
b[c[arr[i]] - 1] = arr[i]
c[arr[i]]--
}
return b
}
console.log(countingSort([2,3,8,7,1,2,2,2,7,3,9,8,2,1,4]))
//[ 1, 1, 2, 2, 2, 2, 2, 3, 3, 4, 7, 7, 8, 8, 9]
時間複雜度:O(n+k),k表示輸入的元素是n 個0到k之間的整數
基數排序
演算法描述
基數排序也是非比較的排序演算法,基本思想:按照低位先排序,然後收集;再按照高位排序,然後再收集;依次類推,直到最高位。有時候有些屬性是有優先順序順序的,先按低優先順序排序,再按高優先順序排序。最後的次序就是高優先順序高的在前,高優先順序相同的低優先順序高的在前。基數排序基於分別排序,分別收集,所以是穩定的。
演算法實現
基本步驟
- 取得陣列中的最大數,並取得位數;
- arr為原始陣列,從最低位開始取每個位組成radix陣列;
- 對radix進行計數排序(利用計數排序適用於小範圍數的特點);
動畫演示
程式碼實現
function radixSort(arr, maxDigit) {
let counter = [], mod = 10, dev = 1;
for (let i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
for(let j = 0; j < arr.length; j++) {
let bucket = parseInt((arr[j] % mod) / dev)
if(counter[bucket]==null) {
counter[bucket] = []
}
counter[bucket].push(arr[j])
}
let pos = 0
for(let j = 0; j < counter.length; j++) {
let value = null
if(counter[j]!=null) {
while ((value = counter[j].shift()) != null) {
arr[pos++] = value
}
}
}
}
return arr;
}
console.log(radixSort([3,44,15,36,26,27,2,46,4,19,50,48],2))
// [ 2, 3, 4, 15, 19, 26, 27, 36, 44, 46, 48, 50]
時間複雜度:O(n*k),k表示輸入的元素是n 個0到k之間的整數
總結
排序演算法 | 平均時間複雜度 | 最壞時間複雜度 | 空間複雜度 | 是否穩定 |
---|---|---|---|---|
氣泡排序 | O(n^2) | O(n^2) | O(1) | 是 |
快速排序 | O(nlogn) | O(n^2) | O(long) | 不是 |
插入排序 | O(n^2) | O(n^2) | O(1) | 是 |
選擇排序 | O(n^2) | O(n^2) | O(1) | 不是 |
歸併排序 | O(nlogn) | O(nlogn) | O(n) | 是 |
希爾排序 | O(nlogn) | O(n^1.5) | O(1) | 不是 |
計數排序 | O(n+k) | O(n+k) | O(n+k) | 是 |
基數排序 | O(n*k) | O(n*k) | O(k) | 是 |
推薦閱讀
- 前端常見的安全問題及防範措施
- 為什麼大廠前端監控都在用GIF做埋點?
- 前端人員不要只知道KFC,你應該瞭解 BFC、IFC、GFC 和 FFC
- Promise、Generator、Async有什麼區別?
- 2022年了你還不瞭解箭頭函式與普通函式的區別嗎?
- 從如何使用到如何實現一個Promise
- 超詳細講解頁面載入過程
我是南玖,我們下期見!!!