今天聽到leader說面試的事,說問一個有兩年工作經驗的人,傳統的三種排序可以手寫嗎都手寫不出來。讓我心中也是一顫,其實想想,工作了這么久,對於原生js這塊兒真的有些淡忘了,在工作中平時都是用的框架來搞事情,直接拿來就可以用,想想當初剛入這行的時候,那時候就覺得js真的很神奇,可是隨着工作時間越來越久,一些東西都是直接拿來用,對於底層的原理也不那么深究了,之前還好,還看看,現在都已經麻木了。今天leader說的這番話,其實你如果說讓我手寫這三種排序我還是可以寫出來的,但是我覺得對於原生js這塊兒確實忘了一些,學無止境,不是新的東西就是好的東西,不能忘記最底層的實現,嗯~,寫博客寫博客,不閑聊了。
對於我們leader今天說的話,我也引入了另外一個知識點,多少和性能優化沾一點邊,冒泡排序我們都知道,可你知不知道它還可以進行優化呢,今天我們就來聊一聊傳統的三種基本排序算法(選擇排序、快排、冒泡)以及關於冒泡排序如何進行優化的三種優化方案
因為這三種基本的排序算法原理都很簡單,所以在思路這塊我就不做過多的解釋了,我們把重點放在最后的冒泡排序如何優化上面
一、選擇排序
選擇排序的思路:比如一個數組arr=[9,6,2,5,10,20,1]進行從小到大排序,我們讓每一個數字都和后面的數字進行比較,比自己小的就交換位置,比自己大的就進行下一個比較,將這個數放到相應的位置,下面是代碼的實現:
function selectSort(arr){ var temp; //定義一個變量用來操作數字交換 for(var i=0;i<arr.length-1;i++){ for(var j=i+1;j<arr.length;j++){ if(arr[i]>arr[j]){ //簡單的交換位置邏輯,相信你可以看的懂 temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } } return arr; }
二、快速排序
快速排序的思路:從一個數組中任意的挑選一個元素(通常來說我們會選擇中間的那個元素),將這個元素作為中軸元素,然后把剩下的元素以這個中軸元素的大小作為標准,比中軸元素小的放到左邊,比中軸元素大的放到右邊,然后把左邊和右邊的都作為一個數組,重復以上操作,知道子元素的元素個數小於等於1的時候,就證明全部的元素都已經從小到大排好序了(因為只有一個元素的數組一定是有序的,已經不需要再繼續排序了),在這個算法中我們主要是用到了了一個遞歸算法(遞歸算法不了解的建議可以先去看下這方面的知識),以下是代碼的實現:
function quickSort(arr){ if(arr.length<=1){ return arr; } //獲取下標 var midIndex = arr.length%2 == 0?arr.length/2:(arr.length+1)/2; //取中間值 var mid = arr[midIndex]; //定義左邊的數組 var left = []; //定義右邊的數組 var right = []; for(var i=0;i<arr.length;i++){ if(i != midIndex && arr[i]<=mid){ left.push(arr[i]); } if(i != midIndex && arr[i]>mid){ right.push(arr[i]); } } return quickSort(left).concat(mid).concat(quickSort(right)) }
三、冒泡排序
冒泡排序的思路:顧名思義,冒泡排序,就像冒泡一樣,從小往大冒,由於邏輯過於簡單,在這里我就直接貼出代碼了:
function bubbleSort(arr){ var temp; for(var i=0;i<arr.length;i++){ for(var j=0;j<arr.length-i;j++){ if(arr[j]>arr[j+1]){ temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } return arr; }
通過上面的代碼可以看出來,通過兩次簡單的for循環,並且不加任何判斷語句的形式的算法注定是低效的,以下是對冒泡排序算法的三種優化
(1)、當我們對一個數組arr=[9,1,2,3,4,5,6]進行排序的時候,我們正常冒泡排序的話,還是會每個數字都排一次,但事實上我們第一次排序進行完之后,數組就已經變成[1,2,3,4,5,6,9],已經達到我們想要的效果了,所以已經不需要再進行其他元素的排序了,所以我們要對這種傳統的冒泡排序算法做一個優化,思路大概是這樣的:我們定義一個flag,當某一次排序中沒有發生元素的交換的話,設置flag為false,當flag為false的時候直接結束后面的循環,這樣的話數組就不會再進行后面的無意義的排序了,代碼實現:
var arr = [9,1,2,3,4,5,6] function bubbleSort(arr){ var temp; var flag; //定義flag,用來判斷數組是否已經有序
for(var i=0;i<arr.length;i++){ flag = true //我們設置flag初始值為true
for(var j=0;j<arr.length-i;j++){ if(arr[j]>arr[j+1]){ temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; flag = false; //我們自己規定flag為false的時候說明數組需要繼續排序
} } if(flag){ //我們可以規定如果某次循環后flag依然為true的話,表明這次沒有進行重新的元素交換,也就是說沒有進行重新排序,那么此時數組中元素已經有序了,所以我們可以直接break跳出循環,return出數組
break; } } return arr; }
(2)、第二種優化是從另一個角度來考慮的,並且也是基於第一次優化的思想,我們每次排序后,數組的后面有一部分已經有序了,所以我們也不需要再和后面已經排好序的元素再進行比較了,我們可以這樣做,用一個變量來記錄下最后一次發生交換的位置,后面沒有發生交換的其實已經有序了,所以可以用這個值來作為一一次我們比較結束時的位置,將會減少很多次沒必要的排序過程,代碼實現如下:
var arr = [9,1,10,5,6,3,0] function bubbleSort(arr){ var temp; var flag; //定義flag,用來判斷數組是否已經有序
var lastindex = 0; //定義lastindex,用來判記錄每次排好序時的最后一次交換的位置
var k = arr.length-1; //用來和lastindex配合,作為每次循環的邊界值,實現不會進行沒必要排序的效果
for(var i=0;i<arr.length;i++){ flag = true //我們設置flag初始值為true
for(var j=0;j<k;j++){ if(arr[j]>arr[j+1]){ temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; flag = false; //我們自己規定flag為false的時候說明數組需要繼續排序
lastindex = j; //來記錄最后一次交換元素的下標
} } k = lastindex if(flag){ //我們可以規定如果某次循環后flag依然為true的話,表明這次沒有進行重新的元素交換,也就是說沒有進行重新排序,那么此時數組中元素已經有序了,所以我們可以直接break跳出循環,return出數組
break; } } return arr; }
在這里我只寫了兩種優化的方法,可我為什么說有三種呢,是我覺得肯定還會有第三種第四種等等很多,等待着我們后續的思考,做了前端也快三年了,性能優化這塊兒在各個方面都有很多,還有原生js也不能丟,我就是說一說自己的一些想法:),希望同行們(為夢想奮斗中的我們),在使用框架或者一些類庫來工作的同時,最底層最原理的東西也不能丟,我自己都不知道我現在在說什么,就是感覺我們不能丟了最原始的東西,好了,今天就到這里了,各位晚安,勿忘初心,方得始終~~~