算法爲王。前端
想學好前端,先練好內功,內功不行,就算招式練的再花哨,終究成不了高手;只有內功深厚者,前端之路纔會走得更遠。面試
本文中包含了 十大經典排序算法 的思想、代碼實現、一些例子、複雜度分析。 這應該是目前最全的 JavaScript 十大經典排序算法 的講解了吧。算法
排序算法的穩定性: 排序先後兩個相等的數相對位置不變,則算法穩定。數組
時間複雜度: 簡單的理解爲一個算法執行所耗費的時間,通常使用大O符號表示法,詳細解釋見時間複雜度markdown
空間複雜度: 運行完一個程序所需內存的大小。網絡
常見算法的複雜度(圖片來源於網絡)app
如下算法最頻繁的操做就是交換數組中兩個元素的位置(按照正序或者是逆序),簡單抽出一個函數以下:ide
* 按照正序比較並交換數組中的兩項
*
* @param {Array} ary
* @param {*} x
* @param {*} y
*/
function swap(ary, x, y) {
if (x === y) return
var temp = ary[x]
ary[x] = ary[y]
ary[y] = temp
}
複製代碼
冒泡排序是一種簡單的排序算法。它重複地走訪過要排序的數列,一次比較兩個元素,若是他們的順序錯誤就把他們交換過來。走訪數列的工做是重複地進行直到沒有再須要交換,也就是說該數列已經排序完成。這個算法的名字由來是由於越小的元素會經由交換慢慢「浮」到數列的頂端。函數
解題思路:從數組的第一位開始,依次向後比較相鄰元素的大小,若是前一個比後一個小,那麼交換兩者位置,直至數組末尾。 下一輪比較的起始位置加1,而後重複第一步。 重複1~2,直至排序結束。post
//bubbleSort.js
Array.prototype.bubbleSort = function(){//數組的實例沒有這個方法,可是其原型上有這個方法,咱們照樣能夠調用這個方法
for(let i=0;i<this.length-1-i;i++){// 排序一輪之後 就不須要那麼大的區間了 把最後一位去掉之後 的進行排序 每次都會區間變小
// j < length - i - 1 是由於內層的 length-i-1 到 length-1 的位置已經排好了,不須要再比較一次。
for(let j =0;j<this.length-1;j+=1){// length-1 是怕數組長度溢出
// i < length - 1 是由於外層只須要 length-1 次就排好了,第 length 次比較是多餘的。
// 打印相鄰元素
if(this[j]>this[j+1]){
const temp = this[j];
this[j]=this[j+1];
this[j+1]=temp
}
}
//this 就是數組
console.log(this[j],this[j+1])
}
}
const arr = [5,4,3,2,1]
arr.bubbleSort();
//時間複雜度O(n^2)
// 空間複雜度 O(1)
複製代碼
優化:當某次冒泡操做已經沒有數據交換時,說明已經達到徹底有序,不用再繼續執行後續的冒泡操做。
/ 冒泡排序(已優化)
//bubbleSort.js
Array.prototype.bubbleSort = function(){//數組的實例沒有這個方法,可是其原型上有這個方法,咱們照樣能夠調用這個方法
for(let i=0;i<this.length-1-i;i++){// 排序一輪之後 就不須要那麼大的區間了 把最後一位去掉之後 的進行排序 每次都會區間變小
// j < length - i - 1 是由於內層的 length-i-1 到 length-1 的位置已經排好了,不須要再比較一次。
for(let j =0;j<this.length-1;j+=1){// length-1 是怕數組長度溢出
// i < length - 1 是由於外層只須要 length-1 次就排好了,第 length 次比較是多餘的。
// 打印相鄰元素
let hasChange = false; // 提早退出冒泡循環的標誌位
if(this[j]>this[j+1]){
const temp = this[j];
this[j]=this[j+1];
this[j+1]=temp;
hasChange = true; // 表示有數據交換
}
}
if (!hasChange) break; // 若是 false 說明全部元素已經到位,沒有數據交換,提早退出
//this 就是數組
console.log(this[j],this[j+1])
}
}
const arr = [5,4,3,2,1]
arr.bubbleSort();
複製代碼
選擇排序是先在數據中找出最大或最小的元素,放到序列的起始;而後再從餘下的數據中繼續尋找最大或最小的元素,依次放到排序序列中,直到全部數據樣本排序完成。 複雜度分析:很顯然,選擇排序也是一個費時的排序算法,不管什麼數據,都須要O(n*n) 的時間複雜度,不適宜大量數據的排序。
解題思路:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。 再從剩餘未排序元素中繼續尋找最小(大)元素,而後放到已排序序列的末尾。重複第二步,直到全部元素均排序完畢。
//selectionSort.js
Array.prototype.selectionSort = function(){
for(let i=0;i<this.length-1;i++){
let indexMin = 0;//把第一位先設置爲最小值
for(let j =i;j<this.length;j+=1){
if(this[j]<this[indexMin]){
// 尋找最小的數
indexMin =j;// 將最小數的索引保存
}
}
if(indexMin !== i){
const temp = this[i];
this[i]=this[indexMin];
this[indexMin]=temp
//this 就是數組
}
}
}
const arr = [5,4,3,2,1]
arr.selectionSort();
//時間複雜度O(n^2)
// 空間複雜度 O(1)
複製代碼
插入排序是先將待排序序列的第一個元素看作一個有序序列,把第二個元素到最後一個元素當成是未排序序列;而後從頭至尾依次掃描未排序序列,將掃描到的每一個元素插入有序序列的適當位置,直到全部數據都完成排序;若是待插入的元素與有序序列中的某個元素相等,則將待插入元素插入到相等元素的後面。
解題思路:從第一個元素開始,該元素能夠認爲已經被排序 取出下一個元素,在已經排序的元素序列中從後向前掃描 若是該元素(已排序)大於新元素,將該元素移到下一位置 重複步驟 3,直到找到已排序的元素小於或者等於新元素的位置 將新元素插入到該位置後 重複步驟 2~5
//insertionSort.js
Array.prototype.insertionSort = function(){
for(let i =0;i<this.length;i++) {
const temp = this[1]
let j =1;
while(j>0){
if(this[j-1]>temp){
this[j]=this[j-1]
}else{
break;
}
j--;
}
this[j] = temp
}
}
const arr = [5,4,3,2,1]
arr.insertionSort();
//時間複雜度O(n^2)
// 空間複雜度 O(1)
複製代碼
歸併排序是利用歸併的思想實現的排序方法,該算法採用經典的分治(divide-and-conquer)策略(分治法將問題分(divide)成一些小的問題而後遞歸求解,而治(conquer)的階段則將分的階段獲得的各答案"修補"在一塊兒,即分而治之)。
解題思路:把長度爲n的輸入序列分紅兩個長度爲n/2的子序列; 對這兩個子序列分別採用歸併排序; 將兩個排序好的子序列合併成一個最終的排序序列。
//mergeSort
Array.prototype.mergeSort = function(){
const rec = (arr)=>{
if(arr.length ===1){return arr ;}
const mid = Math.floor(arr.length/2);
const left = arr.slice(0,mid);
const right= arr.slice(mid,arr.length);
const orderLeft = rec(left);
const orderRight = rec(right);
const res =[]
while(orderLeft.length||orderRight.length){
if(orderLeft.length&&orderRight.length) {
res.push(orderLeft[0]<orderRight[0]?orderLeft.shift():orderRight.shift())
}else if(orderLeft.length){
res.push(orderLeft.shift())
}else if(orderRight.length){
res.push(orderRight.shift())
}
}
return res
}
const res = rec(this)
res.forEach((n,i)=>{
this[i]=n
})
}
const arr = [5,4,3,2,1]
arr.mergeSort();
// 分 O(logn)
// 合 O(n)
//時間複雜度O(nlogn)
// 空間複雜度 O(1)
複製代碼
快速排序使用分治法策略來把一個數組分爲兩個子數組。首先從數組中挑出一個元素,並將這個元素稱爲「基準」,英文pivot。從新排序數組,全部比基準值小的元素擺放在基準前面,全部比基準值大的元素擺在基準後面(相同的數能夠到任何一邊)。在這個分區結束以後,該基準就處於數組的中間位置。這個稱爲分區(partition)操做。以後,在子序列中繼續重複這個方法,直到最後整個數據序列排序完成。
解題思路:從數列中挑出一個元素,稱爲 "哨兵"(pivot); 從新排序數列,全部元素比哨兵值小的擺放在哨兵前面,全部元素比哨兵值大的擺在哨兵的後面(相同的數能夠到任一邊)。在這個分區退出以後,該哨兵就處於數列的中間位置。這個稱爲分區(partition)操做; 遞歸地把小於哨兵值元素的子數列和大於哨兵值元素的子數列排序。
//quickSort
Array.prototype.quickSort = function(){
const rec =()=>{
if(arr.length ===1){return arr;}
const left =[]
const right = []
const mid = arr[0];
for(let i=1;i<arr.length;i+=1){
if(arr[i]<mid){
left.push(arr[i])
}else {
right.push(arr[i])
}
}
return [...rec(left),mid,rec(right)]
}
const res = rec(this)
res.forEach((n,i)=>{this[i]=n})
}
const arr = [5,4,3,2,1]
arr.quickSort();
// 遞歸 O(logn)
// 分區 O(n)
//時間複雜度O(nlogn)
// 空間複雜度 O(1)
複製代碼
刷題打卡第四天,選擇面試常常問的排序算法、一塊兒加油哇~
若是你以爲這篇內容對你挺有有幫助的話: 點贊支持下吧,讓更多的人也能看到這篇內容(收藏不點贊,都是耍流氓 -_-)關注公衆號給npy的前端祕籍,咱們一塊兒學習一塊兒進步。 以爲不錯的話,也能夠閱讀其餘文章(感謝朋友的鼓勵與支持🌹🌹🌹)