上一篇中已經介紹了幾個簡單的排序算法,這一篇文章我將繼續向你們介紹排序算法相關的內容,本篇的會介紹希爾排序、快速排序、歸併排序以及分治算法的思想,但願經過本文章可以加深你們對排序算法的理解。算法
希爾排序又叫縮小增量排序,希爾排序的主要思想是使數組中任意相隔h的元素都是有序的,h就是希爾增量,實現的希爾排序的方法就是:對相隔h的元素進行分組,對每組進行使用插入排序,使子序列變成有序序列,增量逐漸遞減一直到1爲止,在例子中我將h增量設爲array.length/2,遞減的過程就是不斷除以2,是否是被個人解釋弄的有點暈,讓咱們先來看一個演示過程理解一下:shell
如圖所示,一共15個元素,增量就是15/2爲7,第一輪的分組即爲[2, 26, 48],[44, 26],[38, 2],[5, 46],[47, 4],[15, 19],[36, 50],而後進行插入排序,獲得新的序列[ 3, 27, 2, 5, 4, 15, 36, 26, 44, 38, 46, 47, 19, 50, 48 ],而後在進行分組,增量爲7/2爲3:數組
第二輪分組[3, 5, 36, 2, 19],[44, 47, 26, 46, 50], [38, 15, 27, 4, 48],而後進行插入排序,獲得序列[ 3, 4, 2, 5, 26, 15, 19, 27, 44, 36, 46, 47, 38, 50, 48 ],而後再進行分組,增量爲3/2爲1,再插入排序獲得的就是一個有序序列了,最好讓咱們來看具體的代碼實現:spa
function shellSort(arr) { var n = arr.length for (var gap = parseInt(n/2); gap > 0; gap=parseInt(gap/2)) { for (var i=gap; i<arr.length; i++) { var j = i - gap, temp = arr[i] while (arr[j] > temp) { arr[j+gap] = arr[j] j = j - gap } arr[j+gap] = temp } } console.log(arr) } shellSort([3,44,38,5,47,15,36,26,27,2,46,4,19,50,48])
其實希爾排序也不難,只要按照上面的分解圖示一步一步的理解去編寫,相信你們都能寫得出來,上面這種形式的增量設置就是二分的形式設置,而後插入排序,還有一種希爾排序的寫法就是自定義增量,而後子序列裏的元素兩兩比較,來看具體代碼:設計
function shellSort(arr) { var n = arr.length, gap = 1 while (gap < n / 3) { gap = gap * 3 + 1 } for (gap; gap > 0; gap=parseInt(gap/3)) { for (var i=gap; i<arr.length; i++) { for (var j = i - gap;j >= 0; j-=gap) { if (arr[j] > arr[j+gap]) { var temp = arr[j+gap] arr[j+gap] = arr[j] arr[j] = temp } } } } console.log(arr) } shellSort([3,44,38,5,47,15,36,26,27,2,46,4,19,50,48])
希爾排序更高效的緣由在於它權衡了子數組的規模和有序性,各個子數組都很短,排序以後的子數組都是部分有序的,所以在希爾排序的效率要比插入排序高。code
在介紹歸併排序和快速排序有必要先介紹一下分治法相關的內容,爲何呢?由於歸併排序和快速排序都是分治法的典型應用。
分治法的設計思路就是:將一個大問題分解成若干個規模嬌小的相同子問題,這些子問題互相獨立且與原問題形式相同,遞歸地解決這些子問題,而後將各個子問題的解合併獲得原問題的解
分治法所能解決的問題通常有以下幾個特色:blog
歸併排序就是分治法的典型應用之一,歸併排序的實現有兩種,一種是自頂向下的歸併排序,另外一種就是自底向上的歸併排序。排序
向來看第一種,自頂向下的歸併排序的實現思路是不斷二分,而後對二分後的最小序列分別進行排序後,再將排序後的結果向上合併獲得最終的有序數組,讓咱們先經過一個樹結構來理解歸併排序的過程:
遞歸
從圖中能夠看到將一個數組0-14的元素不斷二分,分到最後一層,而後互相比較,獲得新的有序序列,而後向上合併,在進行比較,不斷反覆,合併出最終的有序序列,這就是自頂向下的歸併排序的思路,經過這個思路你是否能本身寫出排序的方法呢?索引
好了,接下來就讓咱們看看具體的代碼實現:
function sort(arr) { var n = arr.length if (n < 2) return arr var mid = Math.ceil(n / 2) var left = arr.slice(0, mid) var right = arr.slice(mid) return merge(sort(left), sort(right)); } function merge(left, right){ var result = [] while (left.length && right.length) { if (left[0] < right[0]) { result.push(left.shift()) } else { result.push(right.shift()) } } while (left.length) { result.push(left.shift()) } while (right.length) { result.push(right.shift()) } return result; } var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48]; console.log(sort(arr))
代碼實現是否是很容易理解呢?相信你們通過仔細的思考事後都能看得懂,爲了方便更好的理解,來看一下動圖的排序過程:
自底向上的歸併排序思路是將長度爲n的數組當作n個長度爲1的數組,而後兩兩向上歸併排序,獲得新的數組,不斷向上歸併排序,直到獲得長度爲n的數組,這樣的排序比以前自頂向下的排序代碼要少,下面來看具體的代碼實現:
function merge(arr, start, mid, end){ var i = start, j= mid + 1, copy = [] for (var k = start; k <= end; k++) { copy[k] = arr[k] } for (var k = start; k <= end; k++) { if (i > mid) { // 左邊取完取右邊的元素 arr[k] = copy[j] j++ } else if (j > end) { // 右邊取完取左邊的元素 arr[k] = copy[i] i++ } else if (copy[j] < copy[i]) { // 右邊的元素小於左邊的元素,取右邊的元素 arr[k] = copy[j] j++ } else { // 左邊的元素小於右邊的元素,取左邊的元素 arr[k] = copy[i] i++ } } console.log(arr) } function sort(arr) { var n = arr.length for (var i = 1; i < n; i = i + i) { // i子數組的大小 for (var j = 0; j < n - i; j = j + i + i) { // j數組的索引 var start = j var end = Math.min(j + i + i - 1, n-1) var mid = parseInt((start + end) / 2) merge(arr, start, mid, end) } } } var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48]; sort(arr)
爲了方便理解,已經在代碼中加了必要的註釋,可能這段代碼比以前的要難理解一些,須要你們耐心多花一些時間去理解
快速排序也是一種分治的排序算法,因爲它實現簡單而且效率比通常的排序算法高,所以,它的應用範圍很是普遍,接下來讓咱們來看快速排序的排序過程:
將數組的第一個元素作爲基準,從數組末尾開始找比基準小的數,找到就停下來,記下索引j,而後從基準右邊開始找比基準大的數找到停下來,記下索引i,而後交換i和j上的元素,獲得數組:
而後繼續從44的位置開始找比基準3小的一直移動到2,此時索引i和索引j相等,將基準3和i、j對應的值交換位置獲得:
此時基準數3前面的元素都是比它小的數,後面元素都是比它大的數,而後從基準數先後拆成兩個數組,在遞歸執行前面一樣的操做。
看來上面的排序過程,你是否是有代碼的實現了呢?能夠先試着寫一下實現的代碼,這樣更容易理解,接下來就讓我來看一下具體代碼:
function sort(arr, left, right) { var temp = arr[left],i = left, j = right, t; if (left < right) { while (i != j) { while (arr[j] >= temp && i < j) { j-- } while (arr[i] <= temp && i < j) { i++ } if (i < j) { t = arr[i] arr[i] = arr[j] arr[j] = t } } arr[left] = arr[i] arr[i] = temp sort(arr, left, i - 1) sort(arr, i + 1, right) } return arr } var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48]; console.log(sort(arr, 0, arr.length - 1))
看了代碼以後是否是以爲並不難呢?這種快速排序的實現其實有一個問題不知道你們注意到沒有,當數組中有多個重複元素的時候,重複的元素只要排了一個就不須要重複排了,可是這中快速排序的實現並無考慮這種狀況,即使重複的元素仍是會執行排序,所以有人提出了更優的快速排序方法「三向切分的快速排序」,讓咱們先來看代碼實現有什麼不一樣:
function sort(arr, left, right) { var temp = arr[left],i = left, j = right,k = left + 1, t; if (left < right) { while (k <= j) { if (arr[k] < temp) { t = arr[k] arr[k] = arr[i] arr[i] = t i++; k++; } else if (arr[k] > temp) { t = arr[k] arr[k] = arr[j] arr[j] = t j--; } else { k++; } } sort(arr, left, i - 1) sort(arr, j + 1, right) } return arr } var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48,44,38]; console.log(sort(arr, 0, arr.length - 1))
整體思路和以前的同樣保證基準值前面的比它小後面的比它大,惟一不一樣的地方就是用了一個臨時下標k去做比較,把小的丟到基準值前面,大的值就和末尾的值交換,獲得新值再與基準值比較,當與基準值相等的時候,就只須要將臨時下標的值+1而不須要排序了
這篇文章詳細介紹了希爾排序、歸併排序、快速排序這三種排序的思想和實現方式,但願你們看完以後都能本身去多實踐,多思考,算法仍是須要本身多動手,不然光看做用不大。若是有錯誤或不嚴謹的地方,歡迎批評指正,若是喜歡,歡迎點贊收藏