快速排序
思想:每次选出一个基准值,经过一趟比较,比基准大的都在右侧,比之小的,都在左侧;递归左右子区间
function quickSort(arr) {
if (arr.length <= 1) return arr;
var middleIndex = Math.floor(arr.length / 2);
var middleNum = arr.splice(middleIndex,1); // 中间位置的数字
var left = [], right = [];
for( var i = 0; i < arr.length; i++) {
if (arr[i] <= middleNum) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([middleNum],quickSort(right));
}
var arr = [3,5,4,1,2];
quickSort(arr);
指针方法:
// 递归+指针
function quickSort(arr) {
// console.log('arr:', arr)
// console.log('arr.length -1:', arr.length-1)
_quickSort(arr, 0, arr.length - 1); // 将整个num数组快速排序,left和right分别指向数组左右两端。
}
function _quickSort(arr, left, right) {
// console.log('left:', left);
// console.log('right:', right);
if (left >= right) return; // 若左右指针相遇,即递归的终点,return(注意不能写成left==right,这里left是有可能大于right的)。
var i = left, j = right, flag = left; // 定义可移动的左右指针 i,j,定义flag为基数下标。
while (i < j) { // 在i<j时不断循环,i一旦与j碰头,则跳出循环。
while (arr[j] >= arr[flag] && j > flag) j--; // j不断左移,找到在num[flag]右侧且比它大的数。
if (i >= j) {
break; // 由于j可能已被改变,需再次判断i与j是否碰头。
}
while (arr[i] <= arr[flag] && i < j) i++; // i不断右移,找到且比基数小的数,且i不能与j碰头。(由于两次交换已合并,此处不需要使得i在flag左侧)
// arr[flag] arr[j] arr[i]三者换位
// ES6写法[arr[flag],arr[j],arr[i]] = [arr[j],arr[i],arr[flag]];
let temp = arr[flag];
arr[flag] = arr[j];
arr[j] = arr[i];
arr[i] = temp;
flag = i; // 基数已经在原num[i]的位置,flag同时也要赋值成i。
}
_quickSort(arr, left, flag - 1); // 将flag左边数组作为待排序数组,递归调用。
_quickSort(arr, flag + 1, right); // 将flag右边数组作为待排序数组,递归调用。
}
var arr = [3,5,4,1,2];
quickSort(arr);
console.log(arr)
复杂度:
最好情况:每次基准选出来后,left和right区间内的数字个数相同;时间复杂度:o(nlogn);空间复杂度:o(n*n);
最坏情况:基准选出后,选出来的其中一个区间是空的;时间复杂度:o(n*n);空间复杂度:o(n),退化为冒泡排序的情况;
平均情况:时间复杂度:O(N*logN);
优化:
第一种:三数取中法:
基准选择时,选arr[left],arr[right],arr[mid]中值为中间大小的那个
function getMidIndex(arr, left, right) {
var mid = Math.floor(right - left) / 2;
if (arr[left] > arr[mid]) { // mid < left
if (arr[left] < arr[right]) { // mid < left < right
return left;
} else { // mid < left;right < left
if (mid < right) {
return right;
} else {
return mid;
}
}
} else { // left < mid
if (right < left) {
return left;
} else { // left < mid left < right
if (mid < right) {
return mid;
} else {
return right;
}
}
}
}
第二种:直接插入排序法
当序列元素小于13时,直接采用直接插入排序
因为快速排序对数组进行划分最后就像一颗二叉树一样,当序列小于13个元素时我们再使用快排的话就相当于增加了二叉树的最后几层的结点数目,增加了递归的次数。
function insertSort1(arr) {
var temp = 0;
var i = 0, j = 0;
for(i = 1; i < arr.length; i++) {
// 例 arr = [2,1,3,0,5];
// temp为本次循环待插入有序列表中的数
temp = arr[i]; // 要将下标为i的插入
// 遍历已经排序好的那部分数组(下表标为0到i-1的这部分),从中找到temp插入的正确位置
for(j = i - 1; j >= 0 && arr[j] > temp; j--){
arr[j+1] = arr[j]; // // 元素后移,为插入temp做准备
// 插入temp
arr[j] = temp;
}
}
}
var arr = [3,5,4,1,2];
var result = insertSort1(arr);
console.log(arr)
if (left-right+1 <= 13)
{
insertSort1(a, left, right);
return;
}
冒泡排序
思想:
冒泡算法:相邻的两个数字比较,两两交互位置;
每次比较都能确定一个较大的数字
// 冒泡排序(未优化版本)
function bubbleSort(arr) {
var temp = 0;
for(var i = 0; i < arr.length - 1; i++) { //确定排序趟数
for(var j = 0; j < arr.length - 1 - i; j++) { //确定比较次数
if(arr[j] > arr[j+1]) {
temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
console.log(arr)
}
var arr = [3,5,4,1,2];
bubbleSort(arr);
优化:
第一种:设置flag变量,若有数据位置变动,则flag值置为true,如果一趟比较下来,flag都没有变化,就结束继续排序;
第二种:记录最后一次交换的位置,内层循环的开始位置(最后一次交换的位置)
// 冒泡排序优化版本
function bubbleSortPrior(arr) {
var flag = false;
for(var i = 0; i < arr.length - 1; i++) { // 趟数
var lastModify = arr.length - 1;
var temp = 0;
var pos = 0; //用来记录最后一次交换的位置
for(var j = 0; j < lastModify; j++) { // 次数
if(arr[j] > arr[j+1]) {
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag = true;
pos = j;
}
}
lastModify = pos;
if (!flag) return;
}
console.log('arr')
}
var arr = [3,5,4,1,2];
bubbleSortPrior(arr);
排序算法复杂度总结(图片来源于https://www.cnblogs.com/wuxiangli/p/6399266.html)