最近看到了不少公司都在準備明年的實習校招,雖然離三月份還有一段時間,感受已經能夠準備了。在網上看了一些排序算法和數組去重操做,感受都寫的很好,心血來潮,也來寫一寫。javascript
說到排序,以前在作百度前端學院的題目的時候,也碰到過,並把它整理到 github 上。這是一個可視化的排序展現,支持冒泡、插入和選擇排序,具體使用先 隨機添加 40 個,而後點排序,就能夠看到可視化的效果。php
推薦一下,HTML5 Canvas Demo: Sorting Algorithms,這裏還有個可視化的排序博客,各大排序算法的實現都栩栩如生。html
javascript 寫排序算法也比較奇葩,主要是參數的問題,好比 javascript 算法函數能夠扔給 Array 原型:Array.prototype.sort = function
,也能夠直接寫個函數帶參數:function sort(array){}
,在我看來,哪一種方法都同樣,須要注意的是兼容性的問題,若是能夠考慮對全部可遍歷對象都能排序(好比 arguments),才大法好。前端
好了,直接入主題了(下面的排序均是從小到大的順序)。java
插入排序是一種基本排序,它的基本思路是構建有序序列,對於未排序的數據,在已排序的基礎上,從右向左(或者二分查找)選擇位置插入,維基百科-插入排序。git
function insert_sort(input){ var i, j, temp; for(i = 1; i < input.length; i++){ temp = input[i]; for(j = i-1; j >= 0 && input[j] > temp; j--) input[j+1] = input[j]; input[j+1] = temp; } return input; }
若是以比較次數和移動次數來衡量算法的效率,最好狀況下,比較 n-1 次,移動 0 次,最壞狀況,比較 n*(n-1)/2 次,移動 n*(n-1)/2 次。github
思路基本同上,只是在查找插入位置的時候,不是依次查找,而是採用二分法:面試
function bin_insert_sort(input){ var i, j, low, high, mid, temp; for(i = 1; i < input.length; i++){ temp = input[i]; high = i - 1; low = 0; while(low <= high){ mid = parseInt((low + high) / 2); if(temp < input[mid]){ high = mid - 1; }else{ low = mid + 1; } } // low 位置就是要插入的位置 for(j = i-1; j >= low; j--) input[j+1] = input[j]; input[low] = temp; } return input; }
希爾排序實際上是增強版的插入排序,就是在原先插入排序的基礎上,加入了步長,原先插入排序的步長是 1,並且步長不一樣,效率也有差別,選擇一個合適的步長也很重要。並且,希爾排序的最後一步,也一定是步長爲 1 的插入排序,只不過此時整個排序已經基本穩定。維基百科-希爾排序。算法
function shell_sort(input){ var gap, i, j, temp; gap = input.length >> 1; while(gap > 0){ for (i = gap; i < input.length; i++) { temp = input[i]; for (j = i - gap; j >= 0 && input[j] > temp; j -= gap) input[j + gap] = input[j]; input[j + gap] = temp; } gap = gap >> 1; } return input; }
選擇排序的工做原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,而後,再從剩餘未排序元素中繼續尋找最小(大)元素,而後放到已排序序列的末尾。以此類推,直到全部元素均排序完畢。維基百科-冒泡排序。shell
function select_sort(input){ var i, j, min, temp; for(i = 0; i < input.length - 1; i++){ min = i; for(j = i + 1; j < input.length; j++){ if(input[min] > input[j]) min = j; } temp = input[min]; input[min] = input[i]; input[i] = temp; } return input; }
選擇排序在最好狀況下,也要比較 n*(n-1)/2,移動 n-1 次(這裏能夠加個判斷,移動 0 次),最差狀況下,比較 n*(n-1)/2 次,移動 n-1 次。全部最好,最壞狀況下,比較次數是同樣的。
冒泡排序的基本原理:對於帶排序列,它會屢次遍歷序列,每次都會比較相鄰的兩個元素,若順序相反,即交換它們,維基百科-冒泡排序。
function bubble_sort(input){ var i, j, temp, flag; for(i = 0; i < input.length - 1; i++){ flag = true; for(j = 0; j < input.length - i; j++){ if(input[j] > input[j + 1]){ temp = input[j]; input[j] = input[j + 1]; input[j + 1] = temp; flag = false; } } if(flag) // 提早結束 break; } return input; }
有 flag 時,最好狀況比較 n-1 次,移動 0 次,最壞狀況,比較 n*(n-1)/2 次,交換 n*(n-1)/2。
記得我一個同窗去百度面試,百度面試官上來就讓他手寫了一個快排,可見對快排的掌握很重要呀,並且快排理解起來也不容易。
維基百科-快排。快排的基本思路就是選擇一個元素,而後按照與這個元素的比較,將大於這個元素的都拿到右邊,小於這個元素的都拿到左邊,並找到這個元素的位置,這個元素的左右兩邊遞歸。
function quick_sort(input){ function sort(start, end){ if(start >= end){ return; } var mid = partition(start, end); sort(start, mid - 1); sort(mid + 1, end); } function partition(start, end){ var left = start, right = end, key = input[start], temp; while(left < right){ while(left < right && input[right] >= key){ right --; } input[left] = input[right]; while(left < right && input[left] <= key){ left ++; } input[right] = input[left]; } input[left] = key; return left; } // main here sort(0, input.length - 1); return input; }
partition 函數就是來找對應的 mid,sort 函數用來排序。
關於快排的優化,能夠從如下幾個方面來考慮:
partition 函數的哨兵(比較值)除了 start 之外,用其餘位置(好比中位數)是否可行;
當 start 和 end 間距很小的時候,改用其餘高效算法
還有就是優化遞歸。
其實呢,上面的這個算法,並不屬於 JavaScript 版本,而更像 C 版本的,重在讓人理解快排,下面是 JS 版的快排,來體驗下 JS 的迷人特性吧:
// javascript 版 function quick_sort(input) { var len = input.length; if (len <= 1) return input.slice(0); var left = []; var right = []; // 基準函數 var mid = [input[0]]; for (var i = 1; i < len; i++) if (input[i] < mid[0]) left.push(input[i]); else right.push(input[i]); return quick_sort(left).concat(mid.concat(quick_sort(right))); };
這個 JS 版快排也比較好懂,找到那個基準(這裏是第一個元素 input[0])以後,遍歷,把小於基準的放到左邊,大於基準的放到右邊,而後返回拼接數組。
在學習分治算法時,典型的一個例子就是歸併。維基百科-歸併排序。思路就是先分後和,依舊是遞歸。
function merge_sort(input){ function merge(left, right){ var temp = []; var i = 0, j = 0; while(i < left.length && j < right.length){ if(left[i] < right[j]){ temp.push(left[i]); i++; }else{ temp.push(right[j]); j++; } } if(i < left.length){ temp = temp.concat(left.slice(i)); } if(j < right.length){ temp = temp.concat(right.slice(j)); } return temp; } if(input.length <=1){ return input; } var mid = parseInt(input.length / 2); return merge(merge_sort(input.slice(0, mid)), merge_sort(input.slice(mid))) }
一樣,以上歸併仍然是相似 C 語言版本,JavaScript 版本以下:
// javascript 版 function merge_sort(input) { var merge = function(left, right) { var final = []; while (left.length && right.length) final.push(left[0] <= right[0] ? left.shift() : right.shift()); return final.concat(left.concat(right)); }; var len = input.length; if (len < 2) return input; var mid = len / 2; return merge(merge_sort(input.slice(0, parseInt(mid))), merge_sort(input.slice(parseInt(mid)))); };
數組的一系列操做大大優化排序的過程。
堆排序(Heapsort)是指利用堆這種數據結構所設計的一種排序算法。堆積是一個近似徹底二叉樹的結構,並同時知足堆積的性質:即子結點的鍵值或索引老是小於(或者大於)它的父節點。維基百科-堆排序。
其實,對於堆排序,只要牢記幾個操做就能夠,好比找到最後一個父節點,如何找到子節點(初始爲 0),如何創建一個最大堆。
function heap_sort(input){ var arr = input.slice(0); function swap(i, j) { var tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } // 上推操做 function max_heapify(start, end) { var dad = start; var son = dad * 2 + 1; if (son >= end) return; if (son + 1 < end && arr[son] < arr[son + 1]) son++; if (arr[dad] <= arr[son]) { swap(dad, son); max_heapify(son, end); } } var len = arr.length; // 創建一個最大堆 for (var i = Math.floor(len / 2) - 1; i >= 0; i--) max_heapify(i, len); for (var i = len - 1; i > 0; i--) { swap(0, i); max_heapify(0, i); } return arr; };
堆排序的過程大體以下:先生成一個最大堆,而後將根節點(最大元素)與最後一個元素交換,而後把剩下的 n-1 元素再次生成最大堆,交換,生成...
那麼問題來了,到底這些算法寫的對不對,否則寫個測試腳原本試試:
// 兩種排序算法 var test = function(sort1, sort2){ var arr1 = [], arr2 = []; // 隨機生成 100 個 1~100 隨機數 function random_arr(a1, a2){ var tmp; for(var i = 0; i < 100; i++){ tmp = parseInt(Math.random()*100) + 1; a1.push(tmp); a2.push(tmp); } } var flag = true; for(var i = 0; i < 100; i++){ random_arr(arr1, arr2); // 比較排序算法的結果 if(sort1(arr1).toString() != sort2(arr2).toString()){ flag = false; break; } arr1 = arr2 = []; } return flag ? "Ok!" : "Error!" } console.log(test(insert_sort, merge_sort)); //"Ok!"
若是已知插入排序是正確的狀況下,就能夠驗證歸併排序是否正確了。共勉!
歡迎來個人博客交流