基本排序
冒泡排序
得到升序序列: 先将数组的第一个元素和第二个元素比较,如果大于第二个则两两交换,然后第二个与第三个元素比较,一轮下来,换到最后的就是数组中最大的元素,那接下来的一轮则不用和最后一个比较。 综上,一共有length-1轮,每轮比较的次数从length-1次递减到1次
let a=[2,4,6,0,1];
function babel(arr) {
let len=arr.length;
for(let i=0;i<len-1;i++){
for(let j=0;j<len-1-i;j++){
if(arr[j]>arr[j+1]){
[arr[j],arr[j+1]]=[arr[j+1],arr[j]]; //ES6的解构赋值
}
}
}
return arr;
}
console.log(babel(a));
时间复杂度:O(N^2) 【临时变量所占空间不随处理数据n的大小改变而改变】
空间复杂度:O(1)
选择排序
先将数组的第一个元素依次与其他元素进行比较,记下每轮下来最小元素的下标,然后与第一个元素互换,则两两比较,比较length-1次,一轮下来最小元素就在第一位。综上,一共有length-1轮,比较的次数从length-1递减到1次,每轮交换的次数0/1
function quickSort(arr) {
let len=arr.length;
for(let i=0;i<len-1;i++){ //控制比较几轮
let k=i;
for(let j=i+1;j<len;j++){
if(arr[k]>arr[j]){
k=j; //找出最小元素的下标
}
}
if(k!==i){
[arr[i],arr[k]]=[arr[k],arr[i]];
}
}
return arr;
}
console.log(quickSort(a));
时间复杂度:O(N^2)
空间复杂度:O(1)
插入排序
类似扑克牌,将拿到的牌插入到正确的位置
首先将待排序的第一个元素作为有序序列,从第二个元素到最后一个依次与前面的有序序列进行比较,插入合适的位置。
function insertSort(arr) {
let len=arr.length;
for(let i=1;i<len;i++){ //默认arr[0]有序
for(let j=i;j>0;j--){ //与前面的有序序列比较
if(arr[j]<arr[j-1]){
[arr[j-1],arr[j]]=[arr[j],arr[j-1]];
}else{
break;
//因为前面的已经是有序的序列,所以当前要插入的值arr[j]>=arr[j-1]
//则无需再向前比较,继续下层循环即可
}
}
}
}
时间复杂度:O(N^2)
空间复杂度:O(1)
总结:这些排序每一轮下来都是其中一端有序
高级排序
快速排序
单标杆
第一轮排序:1)默认第一个元素有序,设为标杆,创建一个存放比标杆小的数组,一个存放比标杆大的数组。2)遍历数组,将比标杆小的存到数组,比标杆大的存到另一个数组。3)接下来的就将小数组和大数组重复1、2步骤,最后将标杆与排好序的数组相拼接
function quickSort(arr) {
if(arr.length<=1){ //递归的出口
return arr
}
let flag=arr[0]; //标杆,有序序列
let len=arr.length;
let minArr=[],maxArr=[];
for(let i=1;i<len;i++){
if (arr[i]<flag){
minArr.push(arr[i]);//比标杆小的存到一个数组
}else{
maxArr.push(arr[i]);//比标杆大的存到另一个数组
}
}
return quickSort(minArr).concat(flag,quickSort(maxArr))
}
双标杆
第一轮排序:1)先以首元素为基准,让标杆low和high分别指向第一个元素和最后一个元素。2)向前移动high标杆,直到找到比基准小的元素,然后将该元素置换到low指向的位置。3)向后移动low标杆,直到找到比基准大的元素,然后将该元素置换到high指向的位置。4)然后继续执行2和3,直到low=high,最后low就是基准的位置,将之置换过去,那现在基准左边的就是比基准小的,右边就是比基准大的,以基准的位置为分界点,左右元素再按相同方法选出基准的位置,使用递归得到正确的排序。
function quickonceSort(arr,low,high){
let flag=arr[low];
while(low<high){
while(low<high && arr[high]>=flag){
high-- //找到比flag小的或low=high才能退出循环
}
arr[low]=arr[high];
while(low<high && arr[low]<=flag){
low++
}
arr[high]=arr[low]
}
arr[low]=flag;
return low
}
function quickSort(arr,low,high){
if(low>=high) return;
let index=quickonceSort(arr,low,high);
quickSort(arr,low,index-1);
quickSort(arr,index+1,high);
}
let arr[2,4,1,0,3];
quickSort(arr);
console.log(arr); //[0,1,2,3,4]
时间复杂度:O(nlogn)
空间复杂度:O(logn) (包含了递归,每次递归都需要保存一定数据)
希尔排序
希尔排序是改良的插入排序,但是不是与相邻的比较,而是与相隔较远的比较。
function shellSort(arr,gap) { //arr数组,gap步长
for(let i=0;i<gap.length;i++){ //比插入排序多了一层循环
let n=gap[i]
for(let j=i+n;j<arr.length;j++){ //开始的下标也是根据遍历到的步长来定
for(let k=j;k>0;k-=n){ //此时k不是-1,而是-n
if(arr[k]<arr[k-n]){
[arr[k],arr[k-n]]=[arr[k-n],arr[k]]
}else{
break;
}
}
}
}
}
let aa=[3,6,2,0,1,7];
let gap=[3,2,1]; //传入的步长肯定是从多到少
shellSort(aa,gap);
console.log(aa);
时间复杂度:O(nlogn)
空间复杂度:O(1)
总结:这些排序在最后的结果前的每一轮数组都是存在一个分界点,左边的比它小,右边比它大
归并排序
将一个数组分成前后两部分,前后两部分分别排序,再将排好序的两部分合并在一起,这个数组就有序了
采用的是分治思想,把大问题分成一个个小问题,小问题解决了,大问题就解决了
function mergeSort(arr) {
if(arr.length<2){
return arr;
}
let middle=Math.floor(arr.length/2);
let left=arr.slice(0,middle);
let right=arr.slice(middle);
return merge(mergeSort(left),mergeSort(right))
}
function merge(left,right) {
let res=[];
while(left.length&&right.length){
if(left[0]<=right[0]){
res.push(left.shift())
}else{
res.push(right.shift())
}
}
if(left.length){
res.push(...left)
}
if(right.length){
res.push(...right)
}
return res;
}
const arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48];
console.time('归并排序耗时');
console.log('arr :', mergeSort(arr));
空间复杂度:O(n)
在合并两个有序数组为一个数组时,需要开辟额外的空间,实际上,尽管每次合并操作都需要申请额外的内存空间,但在合并完成之后,临时开辟的内存空间就被释放掉了。在任意时刻,CPU 只会有一个函数在执行,也就只会有一个临时的内存空间在使用。临时内存空间最大也不会超过 n 个数据的大小,所以空间复杂度是 O(n)。
空间复杂度:O(nlogn)