引子:javascript實際使用的排序算法在標准中沒有定義,可能是冒泡或快排。不用數組原生的 sort() 方法來實現冒泡和快排。
Part 1:冒泡排序(Bubble Sort)
- 原理:臨近的兩數兩兩進行比較,按從小到大或從大到小順序排列,進行多趟,每一趟過去后(外循環),最大或最小的數字被交換到最后一位(內循環)。
- 代碼:共進行6趟,每一趟比較5次
1 var a=[6,2,4,1,5,9],t; 2 for(var i=0;i<a.length;i++){ 3 for(var j=0;j<a.length-1;j++){ 4 if(a[j]>a[j+1]){ 5 t=a[j]; 6 a[j]=a[j+1]; 7 a[j+1]=t; 8 } 9 } 10 }
-----分割線----更正於2016.2.25
更正冒泡代碼:
- 代碼:共進行5趟(最后一趟即要開始比較的最后一個數不用比較了,它已經被其他趟擠到該在的位置了),每一趟比較5-i次,因為那i次在上一趟中已經排好序了在該在的位置了,肯定是前小后大沒什么可比較的了,即后面繼續比較無意義。
1 var a=[6,2,4,1,5,9],t; 2 for(var i=0;i<a.length-1;i++){ 3 for(var j=0;j<a.length-1-i;j++){ 4 if(a[j]>a[j+1]){ 5 t=a[j];i 6 a[j]=a[j+1]; 7 a[j+1]=t; 8 } 9 } 10 }
Part 2:快速排序(Quick Sort)
- 原理:在一個個數為n的序列中找一個數作為基准數(任意哪個皆可,一般找第一個),即找到該基准數所在位置k,k作為分界點,k左邊數均小於基准數,k右邊數均大於基准數。
- 具體做法:設置i,j兩個指針分別指向最左端和最右端,每次比較都從j指針開始向左移動尋找比基准數小的數后停止移動,然后指針i向右移動尋找比基准數大的數后停止移動,交換此時i,j所指向的內容,這算一趟中的一次交換完成,直到i,j指針相遇位置即找到k,將基准數和k位置的數字交換,這算完成一趟排序。(解釋一下為什么每次一定要從j指針移動開始:舉個栗子說明,某序列為[3,1,2,5,4],基准數為3,i指向3,j指向4,如你所願假設先從i移動找比3大的,i指針移動到5停止,j指針找比3小的,移動到5時相遇了,所以5的位置即為k,交換3和5,序列變為[5,1,2,3,4],這顯然越排越亂。之所以一定要從右邊開始就是保證了從右邊過濾的是比基准數小的,然后再從左邊移動時即使相遇了也能保證這個數比基准數小交換后不會影響序列)
- 分析原理:用分治的思想進行多趟尋找,每一趟都找出基准數所在位置,其中每一趟最壞情況都是兩個指針指向了相鄰元素,進行交換,這樣比較次數和交換次數和冒泡一樣了。所以快排在最壞情況下時間復雜度和冒泡一樣O(n2)。但快排的平均時間復雜度為O(nlogn),快排之所以比冒泡有優勢,在於每次交換都是跳躍式的,不一定每個位置都交換,而且每一趟序列的個數有可能是大於1那樣的遞減因為k位置不確定划分后才能確定左右兩邊序列個數,但冒泡每一趟比較序列的個數是規律的每次少一個(n,n-1,n-2...),快排不是像冒泡相鄰交換,跳躍式的交換使交換距離變大,因此總的比較和交換次數變少,速度自然就提高。
- 舉例:某序列[6,1,2,7,9,3, 4,5,10,8],說明以下圖片均引用自網絡(侵刪)
以6作為基准數,尋找分界點k。
1.現在i,j指針均已就位
2.j指針先從右往左找小於6的數再停下,然后i指針再從左往右找大於6的數再停下,最后交換
3.第一趟的第一次交換完畢,序列變為[6,1,2,5,9,3,4,7,10,8]
4.j指針繼續向左尋找小於6的元素,i指針繼續向右尋找大於6的元素,最后交換
5.第一趟的第二次交換完畢,序列變為[6,1,2,5,4,3,9,7,10,8]
6.j指針繼續向左尋找小於6的元素,i指針繼續向右尋找大於6的元素,到3時相遇停止移動,即找到位置k
7.交換3和6,第一趟排序完畢。序列變為[3,1,2,5,4,6,9,7,10,8]
8.剩下的就每一趟都是按此方式重排,可用分治的思想解決,先解決6左邊的序列,完畢后再解決6右邊序列,直到不可拆分出新的子序列為止
- 代碼:為了測試代碼差點把瀏覽器搞崩,一不小心少了某個條件就進入死遞歸了。。。
1 function quicksort(a,left,right){ 2 if(left>right){ //一定要有這個判斷,因為有遞歸left和i-1,若沒有這個判斷條件,該函數會進入無限死錯位遞歸 3 return; 4 } 5 6 var i=left, 7 j=right, 8 jizhun=a[left]; //基准總是取序列開頭的元素 9 10 while(i!=j){ //該while的功能為每一趟進行的多次比較和交換最終找到位置k。當i==j時意味着找到k位置了 11 while(a[j]>=jizhun&&i<j){j--} //只要大於等於基准數,j指針向左移動直到小於基准數才不進入該while。i<j的限制條件也是很重要,不然一直在i!=j這個循環里,j也會越界 12 while(a[i]<=jizhun&&i<j){i++} //只要小於等於基准數,i指針向右移動直到大於基准數才不進入該while。等於條件也是必要的,舉例[4,7,6,4,3]驗證一下是兩個4交換 13 if(i<j){ //如果i==j跳出外層while 14 t=a[i]; 15 a[i]=a[j]; 16 a[j]=t 17 } 18 } 19 20 a[left]=a[i];//交換基准數和k位置上的數 21 a[i]=jizhun; 22 23 quicksort(a,left,i-1); 24 quicksort(a,i+1,right); 25 } 26 27 var array=[4,7,2,8,3,9,12]; 28 quicksort(array,0,array.length-1);//排完序后再看array是[2, 3, 4, 7, 8, 9, 12]
性能測試:在jsperf整了個鏈接測試一下冒泡,快排,sort()對100個數字排序的性能,http://jsperf.com/sortdemo 果然還是sort()原生方法快。
在chrome控制台統計代碼執行的時間,也同樣驗證了sort()原生方法快:
參考:http://developer.51cto.com/art/201403/430986.htm