JavaScript学习笔记--排序算法

207 阅读6分钟

基本排序

冒泡排序

得到升序序列: 先将数组的第一个元素和第二个元素比较,如果大于第二个则两两交换,然后第二个与第三个元素比较,一轮下来,换到最后的就是数组中最大的元素,那接下来的一轮则不用和最后一个比较。 综上,一共有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)