最近看到了不少公司都在准备明年的实习校招,虽然离三月份还有一段时间,感受已经能够准备了。在网上看了一些排序算法和数组去重操做,感受都写的很好,心血来潮,也来写一写。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!"
若是已知插入排序是正确的状况下,就能够验证归并排序是否正确了。共勉!
欢迎来个人博客交流