JavaScript中的排序算法
本文参考博客一
本文参考博客二
@各个算法原理
@各个算法原理详细文字描述
一、评判算法的标准
评判算法的性能高低主要从算法的执行时间和所需要占用的存储空间两个方面衡量。
时间复杂度:
时间复杂度的计算并不是计算程序具体运行的时间,而是算法执行语句的次数。
空间复杂度:
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。
二、算法的分类
由于待排序的数量不同,在排序过程中数据占用的存储设备也会有所不同,根据在排序过程中占用的存储设备的不同,可将排序方法分为两大类,一类是内部排序,指的是待排序数据全部存放在内存中进行排序的过程;另一类是外部排序,待排序的数据量很大,以致内存不能够一次容纳全部的记录,在排序过程中需要访问外存。
排序算法分类 | ||
内部排序 | 插入类 | |
直接插入排序 | ||
折半插入排序 | ||
希尔排序 | ||
交换类 | ||
冒泡排序 | ||
快速排序 | ||
选择类 | ||
简单选择排序 | ||
树形选择排序 | ||
堆排序 | ||
归并类 | ||
2-路规归并排序 | ||
分配类 | ||
基数排序 | ||
外部排序 | ||
2-路平衡归并 | ||
多路平衡归并 | ||
置换选择排序 | ||
最佳平衡树 | ||
三、经典排序算法的性能指标
Tips什么是 排序的稳定性 ?
1.冒泡排序
算法原理:
数组中有 n 个数,比较每相邻两个数,如果前者大于后者,就把两个数交换位置;这样一来,第一轮就可以选出一个最大的数放在最后面;那么经过 n-1(数组长度 - 1) 轮,就完成了所有数的排序。
实现方案:
利用两层循环,内层循环用于把数组元素中最大的数挑选出来,外层循环用于轮询除最大数外剩下元素的需要进行冒泡的次数。
算法评价:
平均复杂度:o(n^2)
空间复杂度:o(1)
稳定性:稳定
代码详情:
var arrData = [13,8,12,10,54,9,65,27,49,76,33];
function BubbleSort (arr) {
for(let j=0;j<arr.length -1;j++){
//finish作为标志位,判断是否已经排序结束
var finish = true;
for(let i=0;i<arr.length - 1 -j;i++){
if(arr[i] > arr[i+1]){
/*
var data = arr[i];
arr[i] = arr[i+1];
arr[i+1] = data;
*/
//这三行的交换函数用ES6来写:
[arr[i], arr[i+1]] = [arr[i+1], arr[i]];
finish = false;
}
}
if(finish){
break;
}
console.log('第次'+j+'外层循环',arr);
// return arr;
}
}
BubbleSort(arrData)
鸡尾酒排序: 双向的冒泡排序,外层循环不变,内层循环有两个;就是在每次循环把最大的数据筛选出来同时,把最小的数据也往前移。
代码详情:
var arrData = [13,8,12,10,54,9,65,27,49,76,33];
function coaktailBubbleSort(arr) {
const length = arr.length;
let low = 0;
let high = length - 1;
while(low < high) {
let finish = false; //规定标志位
for (let j = low; j < high; j++) {
if(arr[j] > arr[j+1]) {
[arr[j], arr[j+1]] = [arr[j+1], arr[j]];
finish = true;
}
}
if(!finish) {
break;//如果一次交换也没有发生,那直接就可以跳出,结束排序
}
high--;
finish = false; //再次重置标志位
for (let j = high; j > low; j--) {
if (arr[j] < arr[j-1]) {
[arr[j-1], arr[j]] = [arr[j], arr[j-1]];
finish = true;
}
}
if(!finish) {
break;
}
low++;
}
console.log('排序完毕',arr);
return arr;
}
coaktailBubbleSort(arrData);
2.选择排序
算法原理:
从数组第一个元素开始,与其他所有元素比较,将最小的元素放在第一位;然后从第二个元素开始,与剩余其他所有元素比较,将最小的元素放在第二位;以此类推进行n-1次运算。
实现方案:
两个嵌套的循环,内层循环用于筛选出最小的数据,外层循环用于控制筛选次数。
算法评价:
平均复杂度:o(n^2)
空间复杂度:o(1)
稳定性:不稳定
代码实现:
var arrData = [13,8,12,10,54,9,65,27,49,76,33];
function selectSort(arr) {
for (var i = 0; i < arr.length; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (arr[i] > arr[j]) {
/**let temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
*/
/*上面三句可以替换为es6写法*/
[arr[i],arr[j]] = [arr[j],arr[i]]
}
}
}
console.log('排序完毕',arr);
return arr;
}
selectSort(arrData);
3.插入排序
算法原理:
(1)直接插入排序:将数组中的第一个元素作为有序序列,将第二个元素与它比较后插入,再把这两个当作有序序列,将第三个元素与前面的比较后插入以此类推到最后一个元素与所有元素比较。
(2)二分插入排序:将寻找每个数插入位置的方法改为折半比较即可
实现方案:
利用两层循环,外循环用于元素的后移,内循环用于元素的插入。
算法评价:
平均复杂度:o(n^2) 空间复杂度:o(1) 稳定性:稳定
直接插入排序:
最佳情况:输入数组按升序排列。T(n) = O(n)
最坏情况:输入数组按降序排列。T(n) = O(n2)
平均情况:T(n) = O(n2)
二分法插入排序:
最佳情况:T(n) = O(nlogn)
最差情况:T(n) = O(n2)
平均情况:T(n) = O(n2)
直接插入排序:
var arrData = [13,8,12,10,54,9,65,27,49,76,33];
function insertionSort(arr) {
for (let outer = 1; outer < arr.length; outer++) {
let temp = arr[outer];
let inner = outer;
while (inner > 0 && arr[inner - 1] > temp) {
arr[inner] = arr[inner - 1];
inner--
}
arr[inner] = temp
}
console.log('排序完毕',arr)
}
insertionSort(arrData);
二分法插入排序:
var arrData = [13,8,12,10,54,9,65,27,49,76,33];
function binaryInsertSort(arr){
for (var i = 1; i < arr.length; i++) {
var key = arr[i], left = 0, right = i - 1;
while (left <= right) {
var middle = parseInt((left + right) / 2);
if (key < arr[middle]) {
right = middle - 1;
} else {
left = middle + 1;
}
}
for (var j = i - 1; j >= left; j--) {
arr[j + 1] = arr[j];
}
arr[left] = key;
}
console.log('排序完成',arr)
return arr;
}
binaryInsertSort(arrData);
4.Shell希尔排序(分组插入排序)
算法原理:
希尔排序的实质是分组插入排序,该方法又称缩小增量排序。该方法的基本思想是:先将整个待排元素序列分割为若干个子序列(由相隔某个‘增量’的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待这个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况)效率是很高的,因此希尔排序在时间效率上有较大的提高。
实现方案:
把数组按下标的一定增量分组,然后对每组使用直接插入排序
算法评价:
平均复杂度:o(nlogn) 空间复杂度:o(1) 稳定性:不稳定
代码详情:
var arrData = [13,8,12,10,54,9,65,27,49,76,33];
function shallSort(array) {
var increment = array.length;
var i;
var temp; //暂存
var count = 0;
do {
//设置增量
increment = Math.floor(increment / 3) + 1;
for (i = increment ; i < array.length; i++) {
console.log(increment);
if (array[i] < array[i - increment]) {
temp = array[i];
for (var j = i - increment; j >= 0 && temp < array[j]; j -= increment) {
array[j + increment] = array[j];
}
array[j + increment] = temp;
}
}
}
while (increment > 1);
console.log('排序完成',array);
return array;
}
shallSort(arrData);
5.快速排序
该算法更多详细介绍:
@快速排序一
@快速排序二
算法原理:
通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
案例一:
var arrData = [13,8,12,10,54,9,65,27,49,76,33];
function quickSort(arr){
if (arr.length <= 1){return arr};
var pivotIndex = Math.floor(arr.length / 2);
var pivot = arr.splice(pivotIndex,1)[0];
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++){
if(arr[i] < pivot) {
left.push(arr[i]);
}else{
right.push(arr[i]);
}
}
var res = quickSort(left).concat([pivot],quickSort(right));
console.log('排序完毕',res)
return res;
}
quickSort(arrData)
案例二:有每次的结果
function quickSort(arr, i, j) {
if(i < j) {
let left = i;
let right = j;
let pivot = arr[left];
while(i < j) {
while(arr[j] >= pivot && i < j) { // 从后往前找比基准小的数
j--;
}
if(i < j) {
arr[i++] = arr[j];
}
while(arr[i] <= pivot && i < j) { // 从前往后找比基准大的数
i++;
}
if(i < j) {
arr[j--] = arr[i];
}
console.log('每次结果',arr)
}
arr[i] = pivot;
quickSort(arr, left, i-1);
quickSort(arr, i+1, right);
return arr;
}
}
// example
let arr = [25,84,21,46,13,27,68,35,20];
console.log('排序完成===>>>>',quickSort(arr, 0 , arr.length-1));
6.归并排序
算法原理:
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(n log n)的时间复杂度。代价是需要额外的内存空间。
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
核心思想:分治
实现方案:
主题流程:先将一个序列分成很多个不能再分割的子序列,将各个子序列分别排序后再将子序列合并。其实就是重复两个步骤:【1】分【2】合并。
实现上就可以分为两个函数,一个负责分段,一个负责合并(因为分割后的每个子序列都是有序的,合并就是两个有序数组合并的过程)
代码排序:
function merge(arr) {
if(arr.length<2){
return arr;
}
var mid = Math.floor(arr.length/2);
//floor 向下取值;ceil向上取值,around 正常的四舍五入
var left = arr.slice(0,mid);
var right = arr.slice(mid);
return sort(merge(left),merge(right));
}
function sort(left,right) {
var result=[];
var i=0;var j=0;
while(i<left.length&&j<right.length){
if(left[i]<right[j]){
result.push(left[i]);
i++
}
else{
result.push(right[j]);
j++
}
}
if(i==left.length){
result=result.concat(right.slice(j));
}
if(j==right.length){
result=result.concat(left.slice(i));
}
console.log('排序完毕',result)
return result;
}
var res = merge(arr);
console.log(res);
7.堆排序
暂无
8.排序两个有序数组
两个数组分别已经排序,将其合并成有序数组。
- 不太好的方案,先直接concat 在利用快排 冒泡或其他方案排序
- 创建结果集,分别添加两个数组元素,详情如下
/* 清空了原来的两个有序数组 */
function mergeTwoSortedArr (arr1, arr2) {
var retArr = [];
/* 遍历比较两个数组的首元素大小,小者 shift 出来 push 到结果数组中去 */
while (arr1.length > 0 && arr2.length > 0) {
if (arr1[0] < arr2[0]) {
retArr.push(arr1.shift());
} else if (arr1[0] > arr2[0]) {
retArr.push(arr2.shift());
} else {
retArr.push(arr1.shift());
retArr.push(arr2.shift());
}
}
/* 将数组剩余元素移出放置到结果数组中,可能是arr1 也可能是arr2 */
while (arr1.length > 0) {
retArr.push(arr1.shift());
}
while (arr2.length > 0) {
retArr.push(arr2.shift());
}
return retArr;
}
// 示例
var arr1 = [2, 3, 5];
var arr2 = [3, 4, 7, 9];
console.log(mergeTwoSortedArr(arr1, arr2)); // [2, 3, 3, 4, 5, 7, 9]
- 正序或者倒序,遍历小的数组,把元素分别按照大小插入到大数组里面