第三课 堆排序,桶排序和排序总结

21 阅读5分钟

周内要工作,周末要出去玩,留给我学习的时间越来越少了(开始懒惰.jpg)。排序差不多学完了,前面学的差不多忘完了,很好。。。所以我打算总结完之后先练几遍各种排序,手写一遍,练到说到哪个排序立马就能知道思路是什么,脑子里大概浮现出一个代码,然后知道各个排序的优缺点,什么时候用,搞明白搞熟悉之后再学习后面的链表,小笨蛋就是要一步一个脚印慢慢走,工作忙起来下次更布吉岛啥时候了,想起我公众号断更俩年了哈哈哈哈哈,坚持六个月的习惯,只需要一分钟就可以放弃,人总是在和另一个想要舒适的自己作斗争,时而输,时而赢。

一 堆排序(时间复杂度为 O(N * LogN) )

1.什么是大根堆

将一个数组写成二叉树,如果每一个父比所有的子都大,那么这就是二叉树 例如[6,4,5,3,2,1]

graph TD
6 --> 4 -->3
4 -->2
6 --> 5 -->1

堆长度 heapSize = 6

子找父 (i-1)/2

父找子 2i+1

如果子位置>=heapSize,则后面不再有子了

2.大根堆排序思路

第一种,如果heapSize=0,给一个数,进行大根堆排序。思路就是弑父替位,更有歹毒的网友将它称为吕布算法。。。也就是说找到i位置的这个数的父,然后进行比较,如果大于父,则父子交换,i来到了父位,再进行比较,直到小于父或者没有父为止;

第二种 找出已有大根堆的最大值,删除它并且使得剩下的数仍然是大根堆。思路就是,大根堆最大值一定是0位置,将数组最后一个位置的数和0位置的数交换,然后heapSize--,最后一个数来到了0位置后,找到他的儿子们,在儿子中选出最大的儿子进行比较,如果小于儿子,则进行交换,来到了子的位置,然后继续找,直到没有儿子,或者大于儿子为止。

3.案例

    //不断向上和父位比较
    function heapInsert(arr,index){
        while(arr[index]>arr[(index-1)/2]){
            swap(arr,index,(index-1)/2); // 交换函数
            index=index-1)/2;
        }
    }
    // 不断向下和子位比较
    function heapify(arr,index,heapSize){
        // 左儿子
        let left=2*index+1;
        while(left<heapSize){
           // 在有右儿子的情况下,找出儿子中的最大值
           let maxIndex = left+1<heapSize && arr[left]>arr[left+1] ? left : left+1;
           maxIndex = arr[index]>arr[maxIndex]? index : maxIndex;
           if(maxIndex === index) break;
           swap(arr,index,maxIndex);
           index=maxIndex;
           left=2*index+1;
        }
    }
    
    // 取出大根堆的最大值,并使得剩下的仍然是大根堆
    function heapSort(arr){
        if(!arr || arr.length<=2) return;
        let heapSize = arr.length;
        // 排出第一个大根堆
        for(let i=0;i<arr.length;i++){
            heapInsert(arr,i);
        }
        swap(arr,0,--heapSize);
        while(heapSize>0){
            heapify(arr,0,heapSize);
            swap(arr,0,--heapSize);
        }
    }

二 桶排序

1.计数排序

思路:

只能在特定情况下使用,例如对员工的年龄进行排序,首先年龄有大小限制,基本在0-200之间,并且会有一些重复的数字,按0-200的顺序计算每一个年龄的个数来排序

案例:

   //计算某年龄数组[10,3,6,8,8,3,10,8]
   function countSort(arr){
       let arr1 = new Array(10+1).fill(0);
       let arr2 = [];
       for(let i=0;i<arr.length;i++){
           arr1[arr[i]]++;
       }
       // arr1 =[0,0,2,0,0,1,0,3,0,2]
       for(let i=0;i<arr1.length;i++){
           while(arr1[i]>0){
               arr2.push(i);
               arr1[i]--;
           }
       }
       // arr2 = [3,3,6,8,8,8,10,10]
   }

2.基数排序

思路:

设置0-9 10个桶,从某一个数组的个位数开始排,以此进桶出桶,先进先出,后进后出

例如 arr [02,13,04,52,06];

首先找出这个数组中最大的十进制位数(此数组是2位);

再计算个位数字的个数

count [0,0,2,1,1,0,1,0,0,0]

index [0,1,2,3,4,5,6,7,8,9]

然后将count数组转为前缀和

count [0,0,2,3,4,4,5,5,5,5]

遍历原数组,找出每一项的个位数,将其作为count的下标,然后找到对应的count[i],比如06对应的count是5,意思就是个位数小于等于6的项,数量为5个,而06作为最后一个项,肯定是后进桶,那么也要后出桶,它一定放在原数组下标为4的位置,然后count[i]--。以此类推

案例:

    radixSort1([02,13,04,52,06])
    
    function radixSort1(arr){
        if(!arr || arr.length<=2) return;
        radixSort(arr,0,arr.length-1,maxBits(arr))
    }
    
    // 找出数组中最大的十进制位数
    function maxBits(arr){
        let maxDigits = 0; 
        for (let i = 0; i < arr.length; i++) { 
            const num = Math.abs(arr[i]); 
            const numDigits = Math.floor(Math.log10(num)) + 1; 
            if (numDigits > maxDigits) { 
            maxDigits = numDigits; 
            } 
        } 
        return maxDigits;
    } 
    
    // 找到某个数的某一位
    function getDiget(item,d){
        const itemString = Math.abs(item).toString();
        const itemArray = itemString.split('').reverse();
        return itemArray[d-1] || 0;
    }
    
    function radixSort(arr,L,R,maxDigit){
        //设桶(count)的长度是10,原数组下标i,count数组下标j,新数组arr1
        let radix =10,i=0,j=0,arr1=new Array(R-L+1)
        
        for(let d=1;d<=maxDigit;d++){
            let count = new Array (radix).fill(0);
            // 计算count
            for(i=L;i<=R;i++){
                j=getDiget(arr[i],d) // 根据d来找到需要的某位数
                count[j]++;
            }
            // 转为前缀和
            for(j=1;j<radix;j++){
                count[j]=count[j]+count[j-1];
            }
            // 遍历原数组,出桶
            for(i=R;i>=L;i--){
                 j=getDiget(arr[i],d);
                arr1[count[j]-1] = arr[i];
                count[j]--;
            }
            // copy到原数组中
            for(i=0,j=0;i<=R;i++,j++){
                arr[i]=arr1[j];
            }
        }
        
    }

三 排序总结

1.什么是排序的稳定性

就是具有相同性质的两项,在排完序后,相对次序不变

例如: 一个对象中有年龄和班级两项,先按照年龄排序,再按照班级排序,那么最后就是每个班级中年龄都是有序的,纯数字是不存在稳定性的

2.哪些排序可以实现稳定性

冒泡,插入,归并,桶

3.哪些排序不能实现稳定性

快速,选择,堆

4.基于比较的排序的总结

基于比较的排序时间复杂度
选择O(N^2)
冒泡O(N^2)
插入O(N^2)
归并O(N * logN)
快排(最快最优)O(N * logN)
O(N * logN)
空间复杂度稳定性
------
O(1)
O(1)
O(1)
O(N)
O(logN)
O(1)