— 以下全文皆以升序为例,代码并非最优化的方法
冒泡排序
冒泡排序属于程序猿必懂基础排序算法之一,相信科班出身的大家都懂,这里介绍该排序的2种优雅写法。
最坏时间复杂度:O(n)
最优时间复杂度:O(n)
平均时间复杂度:O(n)
稳定性:稳定
原理
最左端的2位数字比较大小(左-右分别从第0位置至第n-1位置)
如果第0位置的数 大于 第1位置的数,则交换位置的数;否则不变
第1位置的数(注意:经过第二步,第1位置的数为左端2个数中的最大数) 与 第2位置的数计较,规则如上
则最大数最后右移至第n位置,重复以上3步,最终完成排序
单看原理可能有些小伙伴不太明白,接下来结合实例演示一遍
栗子
有一组数集合:{4,2,5,1,3},升序排序
比较4和2的大小,4>2,则交换位置;数集合变为:{2,4,5,1,3}
比较4和5的大小,4<5,则没有变化;数集合变为:{2,4,5,1,3}
比较5和1的大小,5>1,则交换位置;数集合变为:{2,4,1,5,3}
比较5和3的大小,5>3,则交换位置;数集合变为:{2,4,1,3,5}
第一轮遍历完成,最大数5移动到第n-1位置;下面第二轮,数集合:{2,4,1,3,5}
比较2和4的大小,2<4,则没有变化;数集合变为:{2,4,1,3,5}
比较4和1的大小,4>1,则交换位置;数集合变为:{2,1,4,3,5}
比较4和3的大小,4>3,则交换位置;数集合变为:{2,1,3,4,5}
比较4和5的大小,4<5,则没有变化;数集合变为:{2,1,3,4,5} (这次比较没有意义的)
第二轮遍历完成,倒数第二大数4移动至第n-2位置;下面第三轮,数集合为:{2,1,3,4,5}
比较2和1的大小,2>1,则交换位置;数集合变为:{1,2,3,4,5}
比较2和3的大小,2<3,则没有变化;数集合变为:{1,2,3,4,5}
比较3和4的大小,3<4,则没有变化;数集合变为:{1,2,3,4,5} (这次比较没有意义的)
比较4和5的大小,4<5,则没有变化;数集合变为:{1,2,3,4,5} (这次比较没有意义的)
第三轮遍历完成,相信聪明的小伙伴们已经看出来,排序已经完成,但计算机不知道,它需要再遍历1轮。
由上面例子可以看出,实际上第二轮遍历最后一次是不需要的(经过第一轮,第n-1位置的数一定是最大的),并且如果某轮遍历没有任何交换位置发生,则证明排序已完成。
代码实例
function swap(i, j, arr){var temp = arr[i];arr[i] = arr[j];arr[j] = temp;}/*** 方法一:往一边冒泡*/function bubbleSort1(arr){for(var i = 0; i < arr.length - 1; i++){var bool = true;for(var j = 0; j < arr.length - 1 - i; j++){if(arr[j] > arr[j + 1]){swap(j, j + 1, arr);bool = false;}}if(bool){return arr;}}return arr;}var arr = [4, 2, 5, 1, 3];console.log(bubbleSort1(arr));/*** 方法二:同时往两边冒泡*/function bubbleSort2(arr){var left = 0;var rigth = arr.length - 1;while(left < rigth){for(var i = left; i < rigth; i++){if(arr[i] > arr[i + 1]){swap(i, i + 1, arr);}}rigth--;for(var i = rigth; i > left; i--){if(arr[i] < arr[i - 1]){swap(i, i-1, arr);}}left++;}return arr;}console.log(bubbleSort2(arr));
选择排序
选择排序是种简单直观的排序方法,同样属于程序猿必懂基础排序算法之一。
最坏时间复杂度:O(n)
最优时间复杂度:O(n)
平均时间复杂度:O(n)
稳定性:不稳定
原理
选取第1位数(最左边数)作为最小值min
遍历对比最小值min与每个数,当某数小于最小值min,则将值赋予最小值min
遍历完成,将最小值min对应的数取出,插入未排序序列的起始
重复以上3步(注意:第二轮应选未排序序列的第1位数作为 最小值min),将值插入已排序序列的尾部
单看原理可能有些小伙伴不太明白,接下来结合实例演示一遍
栗子
有一组数集合:{4,2,5,1,3},升序排序
取4为最小值min
4>2,将2赋予最小值min;2<5(不用管);2>1,将1赋予最小值min;1<3(不用管)
遍历完成,将最小值min=1取出来,插入未排序序列的起始(即交换1和4的位置)
第一轮遍历完成,数集合为:{1,2,5,4,3}
1为已排序序列,取2为最小值min
2<5,(不用管),2<4(不用管),2<3(不用管)
遍历完成,将最小值min=2取出来,插入已排序序列的尾部(2已在该位置)
第二轮遍历完成,数集合为:{1,2,5,4,3}
1,2为已排序序列,取5为最小值min
5>4,将4赋予最小值min;4>3,将3赋予最小值min;
遍历完成,将最小值min=3取出来,插入已排序序列的尾部(即交换5和3的位置)
第三轮遍历完成,数集合为:{1,2,3,4,5}
当第三轮结束排序已完成,计算机仍需要执行2次。当n较小时,选择排序会快过冒泡排序(交换所消耗CPU的时间比比较消耗CPU的时间多)。
代码实例
function swap(i, j, arr){var temp = arr[i];arr[i] = arr[j];arr[j] = temp;}/*** 选择排序*/function selectionSort(arr){if(!arr || arr.length <= 1) {return arr;}for(var i = 0; i < arr.length - 1; i++){var min = i;for(var j = i + 1; j < arr.length; j++){if(arr[min] > arr[j]){min = j;}}if(min != i){swap(i, min, arr);}}return arr;}var arr = [4, 2, 5, 1, 3];console.log(selectionSort(arr));
插入排序
插入排序是种简单直观的排序方法,面试常问算法之一。
最坏时间复杂度:O(n)
最优时间复杂度:O(n)
平均时间复杂度:O(n)
稳定性:稳定
原理
取第一个元素默认为已排序序列
取下个元素向已排序序列向后扫描,找到已排序序列中小于该元素的位置,向该位置后插入该元素
重复步骤2
单看原理可能有些小伙伴不太明白,接下来结合实例演示一遍
栗子
有一组数集合:{4,2,5,1,3},升序排序
取4作为已排序序列
2<4,将2取出插入第0位置
第一轮遍历完成,数集合为:{2,4,5,1,3}
取2,4作为已排序序列
5>4,将5取出插入第2位置(不变)
第二轮遍历完成,数集合为:{2,4,5,1,3}
取2,4,5作为已排序序列
1<5,继续向后比;1<4,继续向后比;1<2,将1取出插入第0位置
第三轮遍历完成,数集合为:{1,2,4,5,3}
后续1轮遍历不做演示,大家自行推导。该排序逻辑清晰简单,向已排序序列对应位置插入新元素就行。
代码实例
/*** 插入排序*/function insertSort(arr){for(var i = 1; i < arr.length; i++){var temp = arr[i];// 数组后移,让出对应位置插入新元素值for(var j = i - 1; j >= 0 && arr[j] > temp; j--){arr[j + 1] = arr[j];}arr[j + 1] = temp;}return arr;}var arr = [4, 2, 5, 1, 3];console.log(insertSort(arr));
快速排序
快速排序又称划分交换排序,简称快排,是冒泡排序的改进版。据说JavaScript的数组内置函数Sort,当n>10时,用的就是快速排序。
最坏时间复杂度:O(n)
最优时间复杂度:O(
)
平均时间复杂度:O(
)
稳定性:不稳定
原理
选第一个元素作为基准值
将数集大于基准值的数放在基准值右边,小于基准值的数放在基准值左边,形成2个数集
递归将2个数集重复以上2步
单看原理可能有些小伙伴不太明白,接下来结合实例演示一遍
栗子
选4作为基准值
遍历数集,将大于4放在右边数集,小于4放在左边数集,结果为 {2,1,3} + 基准值4 + {5}
数集{2,1,3}选2作为基准值
遍历数集,将大于2放在右边数集,小于2放在左边数集,结果为 {1} + 基准值2 + {3}
数集{1}只有一个数,作为有序数集
数集{3}只有一个数,作为有序数集
数集{5}只有一个数,作为有序数集
组合完成数集合:{1,2,3,4,5}
快速排序的实现思想很简单,但是代码实现并不简单,主要应用递归。
代码实例
/*** 快速排序(代码参照维基百科)*/function quickSort(arr) {var len = arr.length;if (len < 2){return arr;}var privotKey = arr[0], left = [], right = [];for (var i = 1; i < len; i++) {arr[i] >= privotKey && right.push(arr[i])arr[i] < privotKey && left.push(arr[i])}return quickSort(left).concat(privotKey, quickSort(right));}var arr = [4, 2, 5, 1, 3];console.log(quickSort(arr));
希尔排序
希尔排序又称递减增量排序,是插入排序的的改进版。
最坏时间复杂度:O(
)(最好步长)
最优时间复杂度:O(n)
平均时间复杂度:O(随步长变化)
稳定性:不稳定
原理(阅读前置条件——插入排序)
设置一个步长序列
取步长序列第一个元素将数集合分为改 (数集数量/元素值)个集合,对每个集合分别用插入排序进行排序
取步长序列下一个元素进行第二步
重复以上2步至步长为1结束
单看原理可能有些小伙伴不太明白,接下来结合实例演示一遍(其实我也看不懂自己写了啥)
栗子
有一组数集合:{4,2,5,1,3},升序排序
设置步长序列为 集合数量/2的商取整(即{n>>1})
根据步长2(5>>1),得出数集{4,5,3}和数集{2,1}
分别对2个数集使用插入排序,得数集{3,4,5}和数集{1,2}
第一轮完成,数集合为:{3,1,4,2,5}
根据步长1(2>>1),得出数集{3,1,4,2,5}
对该数集使用插入排序,得出数集{1,2,3,4,5}
步长的选择是该排序的重要部分,只要步长最终为1(即为插入排序),则可以保证一定排序。
代码实例
/*** shell排序(代码参照维基百科)*/function shellSort(arr) {var gap, i, j, temp;for (gap = arr.length >> 1; gap > 0; gap >>= 1){for (i = gap; i < arr.length; i++) {temp = arr[i];for (j = i - gap; j >= 0 && arr[j] > temp; j -= gap){arr[j + gap] = arr[j];}arr[j + gap] = temp;}}return arr;};
归并排序
归并排序是创建在归并操作上的排序算法,采用分治法,向下分层采用递归分治处理。
最坏时间复杂度:O(
)
最优时间复杂度:O(
)
平均时间复杂度:O(
)
稳定性:稳定
原理
将数集合分为2份
如果2个数集合有序(数集合数为1),则进入步骤3;如果数集无序,则继续重复步骤1
对比左右2个有序序列第一个元素中的最小值,将其取出放在新序列
重复步骤3,如果一方序列值为空,则返回新序列+左有序序列+右有序序列
重复步骤2-4
单看原理可能有些小伙伴不太明白,接下来结合实例演示一遍(其实我也看不懂自己写了啥)
栗子
有一组数集合:{4,2,5,1,3},升序排序
将数集合分为{4,2}和{5,1,3}
数集{4,2}分为{4}和{2}
{4}和{2}都为有序序列
取2为新序列,因为{2}取出2之后序列值为空;返回新序列+左有序序列+右有序序列(即{2}+{4}+{})
数集为{2,4}
数集{5,1,3}分为{5}和{1,3}
{5}为有序序列
{1,3}分为{1}和{3}
{1}和{3}都为有序序列
取1为新序列,因为{1}取出1之后序列值为空;返回{1}+{}+{3}
数集为{1,3}
取1为新序列,取3加入新序列,因为{1,3}取出1,3之后序列值为空;返回{1,3}+{5}+{}
数集为{1,3,5}
取1为新序列,取2加入新序列,取3加入新序列,取4加入新序列,因为{2,4}取出2,4之后序列值为空;返回{1,2,3,4}+{}+{5}
数集为{1,2,3,4,5}
代码实例
/*** 归并排序(代码参照维基百科)*/function mergeSort(arr) {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));};if (arr.length < 2){return arr;}var mid = arr.length / 2;return merge(mergeSort(arr.slice(0, parseInt(mid))), mergeSort(arr.slice(parseInt(mid))));};var arr = [4, 2, 5, 1, 3];console.log(mergeSort(arr));
