算法开篇必提之比较排序七剑客(上)

174 阅读3分钟

这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

最近复习了下几种排序,就以它作为在掘金发布的首篇文章吧,如果有写的不对的地方,欢迎大家斧正。

image.png

如上图所示,基于比较的排序可分为七种,今天我们先来看看比较简单的冒泡排序、插入排序和选择排序叭😄😀(排序顺序默认是从小到大哈)

1. 冒泡排序

谁的排序算法不是从冒泡开始的呢?所谓“冒泡”就是通过相邻元素两两比较,依次把每一趟中最大的元素“冒泡”到该趟的末尾,经过第一趟0-(N-1)的冒泡,把最大的元素放到了N-1位置上,第二趟就是0-(N-2),把数组第二大的元素放到了N-2位置上,以此类推。

function bubbleSort(arr){
   for(let end=arr.length-1;end>0;end--){//n-1趟的冒泡
       for(let j=0;j<end;j++){
           if(arr[j+1]<arr[j]){//如果后一个元素比前一个元素小,两者就交换位置
               [arr[j],arr[j+1]]=[arr[j+1],arr[j]];
           }
       }
   }
}

2. 选择排序

每次选择都把该次遍历得到的最小值放到本次的起始位置。第一次的起始位置是0,经过遍历1-N-1位置的值之后得到最小值索引minIndex,将该位置的值与0位置交换,这样整个数组的最小值就来到了0位置;第二次的起始位置是1,经过遍历2-N-1位置的值后同样把这次的最小值放到了1位置,这样整个数组的次小值就来到了1位置;以此类推,当经过n-1趟的选择之后,我们的数组也就从小到大的排好啦。

function selectionSort(arr){
    for(let i=0;i<arr.length-1;i++){//i就是每次的起始位置
        let minIndex=i;
        for(let j=i+1;j<arr.length;j++){
        //然后遍历起始位置之后的所有位置,得到最小值所在位置
            minIndex=arr[j]<arr[minIndex]?j:minIndex;
        }
        [arr[i],arr[minIndex]]=[arr[minIndex],arr[i]];//交换起始元素和最小值
    }
}

3. 插入排序

逐步构造有序序列,0位置默认有序,从1位置开始构造。

function insertionSort(arr){
    for(let i=1;i<arr.length;i++){
        for(let j=i-1;j>=0&&arr[j]>arr[j+1];j--){
        //在一次构造过程中,如果前一个数比后一个数大,就进行交换,直至到达边界或者满足前一个数小于等于后一个数为止
             [arr[j],arr[j+1]]=[arr[j+1],arr[j]];
        }
    }
}

说到排序,我们还得知道一个概念叫“稳定性”,这个稳定性是指在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,这样我们就称这个排序是稳定的。不难判断,我们上面的三种实现都是稳定的。

对数器

最后我们再介绍一种叫做“对数器”的方法,它可以帮助我们验证解法是否正确,定位错误case,验证贪心策略等。首先我们需要写一个随机数组产生器:


function generateRandomArray(maxSize,maxValue){
    let arr=[];
    for(let i=0;i<Math.floor((maxSize+1)*Math.random());i++){
        arr[i]=Math.floor((maxValue+1)*Math.random())-Math.floor(maxValue*Math.random())
    }
    return arr;
}

2.再写一个绝对正确的方法即使复杂度更高 以排序为例,使用内置sort方法,要记得传入比较器噢,不然默认是按字典序排序的。

function absoluteBingo(arr){
     arr.sort((a, b) => a - b);
}

3.通过大样本测试,比对该绝对正确方法与我们的解法的结果,如果不一致,则可以打印出错误case。

//复制一个数组
function copy(arr){
    let res=[];
    for(let i=0;i<arr.length;i++){
        res[i]=arr[i];
    }
    return res;
}
//判断分别调用了绝对正确的方法和我们实现的方法所得到的两个数组是否相等
function isEqual(arr1,arr2){
    if(arr1.length!==arr2.length){return false;}
    for(let i=0;i<arr1.length;i++){
        if(arr1[i]!==arr2[i])return false;
    }
    return true;
}

let testTime=500000;//测试次数
let maxSize=100;//随机数组最大长度
let maxValue=100;//随机数组最大值
for(let i=0;i<testTime;i++){
    let arr1=generateRandomArray(maxSize,maxValue);
//console.log(arr1,'arr1');
    let arr2=copy(arr1);
    bubbleSort(arr1);//这里以冒泡排序为例
    absoluteBingo(arr2);
    if(!isEqual(arr1,arr2)){
        console.log('Not Bingo!');
        console.log(arr1,'arr1');
        console.log(arr2,'arr2');
        break;
    }
}
console.log('bingo');

下集见

至此三种简单的排序我们就学习完啦,还学习了对数器的使用,下一集就到比较高级的归并排序、快速排序、希尔排序登场了,不见不散哟😋😙