前言
学习C/C++或者Java的都知道,程序员面试基本都会问到算法问题,尤其是常见的排序算法与查找算法,而我们的前端也不例外,虽然前端开发者的大多数都只会注重界面的开发以及JavaScript的API,有很少会去查看JS底层代码的,对于那些正在学习后台,或者正忙于找工作的程序员来说,学习这些算法师非常有用的。
JS有自带的sort()方法,想了解sort()源码的,给大家推荐proto的博客,地址是
http://blog.csdn.net/lixuepeng_001/article/details/53742589。
在学习排序算法之前,我们先了解一些基本的概念:
稳定性:在其他的领域也叫鲁棒性(robust),在这里主要指一个数组中具有相同的关键字在经过排序后它们的相对顺序不变。如关键字a在关键字b之前(a与b相等),经过排序后关键字a在关键字b后就说明此排序算法不稳定。
时间复杂度:指算法完成并输出结果需要消耗的时间。通常用O表示。
这里先给出常用排序算法的时间复杂度与稳定性的比较,如下图所示:
常见排序算法的分类
一、冒泡排序
算法描述:依次比较相邻的两个数,如果第一个数比第二个数小,位置不变,如果第一个数比第二个数大,则交换两个数的顺序;然后对除了最后一个之外的数重复第一步,直到只剩一个数。
代码实现:
function swap(array,a,b){
var temp = array[a];
array[a] = array[b];
array[b] = temp;
}
function bubbleSort(array){
var length = array.length, i,j;
for (i = 0; i < length; i++){
for (j = 0; j < length-i-1; j++){
if(array[j] > array[j+1]){
swap(array, j, j+1);
}
}
}
return array;
}
冒泡排序如下图所示:(图片来自于互联网)
改进:鸡尾酒排序
冒泡排序的改进,也叫定向冒泡排序,从低到高索引然后再从高到低比较相邻数的大小,性能比冒泡好一点但没多大提升,代码如下:
function swap(array,a,b){
var temp = array[a];
array[a] = array[b];
array[b] = temp;
}
function cocktailSort(array){
var left = 0, right = array.length - 1;
while(left < right){
var i;
for(i = left; i < right; i++){
if(array[i] > array[i+1]){
swap(array, i, i+1);
}
}
right--;
for(i = right; i > left; i--){
if(array[i] < array[i-1]){
swap(array, i-1, i);
}
}
left++;
}
return array;
}
二、选择排序
算法描述:开始时在数组中找到最小的元素,然后将其作为数组的第一个元素,然后再从剩余未排序的元素中继续寻找最小的元素,依次放到数组中,以此类推,直到所有的元素均排序完毕
代码实现:
function swap(array,a,b){
var temp = array[a];
array[a] = array[b];
array[b] = temp;
}
function selectSort(array){
var i,j,length = array.length;
for(i = 0; i < length; i++){
var min = i;
for(j = i+1; j < length; j++){
if(array[min] > array[j]){
min = j;
}
}
swap(array,min,i);
}
return array;
}
选择排序如下图所示:(图片来自于互联网)
三、插入排序
算法描述:将数组第一个标记为已排序,剩下的数标记为未排序,然后从依次取出未排序的第一个数,与已排序的数进行比较然后插入进去,直到所有数都被标记为已排序为止。
代码实现:
function insertionSort(array){
var length = array.length, i;
for(i = 1; i < length; i++){ //默认未排序的数组从1开始
var get = array[i]; //获得数组的第一个数
var j = i - 1; //已排序的数组
while(j >= 0 && array[j] > get){ //从已排序数组最后一个数开始,依次与未排序的第一个数比较
array[j+1] = array[j]; //如果未排序的数小,将已排序的数后移一位,留空以判断是否插入
j--;
}
array[j+1] = get; //这里需注意断开while循环的条件,即array[j]<get;说明get需插入到array[i]的下一个数即array[i+1]的位置
}
return array;
}
插入排序如下图所示:(图片来自于互联网)
改进:二分插入排序
插入排序的一种改进算法,采用二分查找法的思想,二分查找法需在数字已排序的情况下才能使用,因为插入排序中已标记的数组已排好了顺序,所以从未排序的数组中选取一个数插入已排序中就可以使用二分查找以提高性能,代码如下:
function insertionSortDichotomy(array){
var i, j, length = array.length;
for(i = 1; i < length; i++){
var get = array[i], left = 0, right = i-1; //定义已标记的数组左右边界,用于二分查找,即折半比较
while(left <= right){
var mid = parseInt((left + right) / 2); //JS数据类型没有分整形与浮点型,所以这里需显式转化为整数
if(array[mid] > get){ //判断已排序的中间数与取出的数的大小
right = mid - 1; //如果中间数比取出的数大,则说明get所在的位置在mid左边,缩小数组范围,排除此时的mid,然后重新定义左右边界,再经过循环折半再比较
} else {
left = mid + 1; //如果中间数小,在mid右边,left就应该加1
}
} //直到找出mid应该插入的位置,此时的left=right即为应该插入的位置
for(j = i - 1; j >= left; j--){ //无论get在已标记数组的右边还是左边,只要有比get大的数,那么数组最后一个都必须向右移动
array[j + 1] = array[j]; //右移的数量为left所在位置的后面的所有数组成员
}
array[left] = get;
}
return array;
}
四、希尔排序
算法描述:插入排序的改进,也叫缩小增量排序,它是不稳定的一种排序算法。原理是将数组按一定的增量分组,然后分别对这些组排序,然后改变增量大小再进行排序,知道增量为1为止。
代码实现:
function shellSort(array){
var length = array.length,D = 0;
while(D <= length){
D = 3 * D + 1;
}
while(D >= 1){
var i, j;
for(i = D; i < length; i++){
j = i - D;
var get = array[i];
while(j >= 0 && array[j] > get){
array[j + D] = array[j];
j = j - D;
}
array[j + D] = get;
}
D = (D - 1) / 3;
}
return array;
}
希尔排序如下图所示:(图片来自于互联网)
五、归并排序
算法描述:归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
具体算法描述如下:
<1>.把长度为n的输入序列分成两个长度为n/2的子序列;
<2>.对这两个子序列分别采用归并排序;
<3>.将两个排序好的子序列合并成一个最终的排序序列。
代码实现:
function mergeSort(arr) { //采用自上而下的递归方法
var len = arr.length;
if(len < 2) {
return arr;
}
var middle = Math.floor(len / 2),
left = arr.slice(0, middle),
right = arr.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right)
{
var result = [];
console.time('归并排序耗时');
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());
console.timeEnd('归并排序耗时');
return result;
}
归并排序如下图所示:(图片来自于互联网)