【左神】一周刷爆LeetCode,直击BTAJ等一线大厂必问算法面试题真题详解 【第三弹】

目录

10、对数器

11、桶排序

11.1 不完全二叉树

11.2 完全二叉树

11.3 大根堆

11.4 大根堆的排序:

11.6 优先队列

11.5 堆排序扩展题目

11.6  计数排序:

11.7  基数排序


10、对数器

对数器的概念和使用

1,有一个你想要测的方法a
2,实现复杂度不好但是容易实现的方法b
3,实现一个随机样本产生器
4,把方法a和方法b跑相同的随机样本,看看得到的结果是否一样。
5,如果有一个随机样本使得比对结果不一致,打印样本进行人工干预,改对方法a或者 方法b
6,当样本数量很多时比对测试依然正确,可以确定方法a已经正确。

使用对数器检查排序算法的准确

算法c是自己写的,算法b是系统提供的排序算法,使用随机数组,2种算法对比,看c是否有错。不依赖线上测试平台,自己就能测出来。

public static void comparator(int[] arr) {
        Arrays.sort(arr);
    }

11、桶排序

11.1 不完全二叉树

11.2 完全二叉树

左儿子:2 * i + 1
右儿子:2 * i + 1
父:(i - 1)/ 2

11.3 大根堆

父节点的数比子节点的数要大,示例:

利用新进堆的数与父节点比较,形成大根堆,把新的数插入到堆中,就是上移:

//取出最大元素之后执行此操作变成大根堆    
public static void heapInsert(int[] arr, int index) {
        while (arr[index] > arr[(index - 1) / 2]) {//当前节点数值大于父节点位置
            swap(arr, index, (index - 1) /2);
            index = (index - 1)/2 ;
        }
    }

某数a在index位置,将其往下移动,至堆结构符合大根堆要求,就是下移:

//大根堆什么也没有,此时不断进行插入排序,执行此操作
​
//某数a在index位置,将其往下移动
    public static void heapify(int[] arr, int index, int size) {//size为数组长度
        int left = index * 2 + 1;//左孩子位置
        while (left < size) {//判断孩子是否存在
            //只有当右孩子存在且大于左孩子时,才取右孩子作为最大值;
            //其余情况选左孩子,包括
            //  1.右孩子不存在
            //  2.右孩子存在但没左孩子大
            //largest记录最大值的位置
            int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
            //比较父节点和大孩子之间谁大,记录下大的值的位置
            largest = arr[largest] > arr[index] ? largest : index;
            //如果父节点比较大,堆结构排好,退出
            if (largest == index) {
                break;
            }
            //孩子比较大,交换父和孩子的位置
            swap(arr, largest, index);
            //记录某数a的新位置
            index = largest;
            //记录处于新位置的某数a的左孩子
            left = index * 2 + 1;
        }
    }

新增一个数,或删除最大值,调整的复杂度都是 O(logN)。

11.4 大根堆的排序:

做法2 O(NlogN):

所有数字先入大根堆,然后将最大数字于heapsize最后一个元素交换,heapsize减一,然后第一个数做heapify的下移操作,如此反复,就能将全部数字排序,调整的复杂度都是 O(NlogN)。

   public static void heapSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        //将所有数字搞成大根堆
        //做法1:
//      for (int i = 0; i < arr.length; i++) {// O(N)
//          heapInsert(arr, i);// O(logN)
//      }
        //做法2:
        for (int i = arr.length-1; i >= 0 ; i--) {
            heapify(arr, i, arr.length);
        }
        int size = arr.length;
        //0位置上的数与heapsize最后一个数交换
        swap(arr, 0, --size);
        while (size > 0) {// O(N)
            //0位置上的数重新调整位置
            heapify(arr, 0, size);// O(logN)
            //0位置上的数与heapsize最后一个数交换,heapsize减小
            swap(arr, 0, --size);// O(1)
        }
    }
​

做法1 O(N):

如果只是进行大根堆排序有O(N)的算法,即算法一:

第一步,全部数字变成大根堆,有优化做法,最小的树做heapify,然后次小…

时间复杂度分析:

假设一共N个数 最底层最差情况是heapInsert一次,共N/2个数需要进行heapInsert, 倒数第二层最差情况是heapInsert二次,共N/4个数需要进行heapInsert,如此类推:

复杂度使用错位相加法:

最终复杂度为 O(N)

11.6 优先队列

黑盒封装的优先队列实际上就是小根堆

public static void main(String[] args) {
    PriorityQueue<Integer> heap = new PriorityQueue<>();
    heap.add(8);
    heap.add(3);
    heap.add(6);
    heap.add(2);
    heap.add(4);
    while (!heap.isEmpty()){
        System.out.println(heap.poll());
    }
}
//输出:2 3 4 6 8

小根堆会遇到不够空间时扩容,扩容就会复制一次(2,4,8,16,32),长度为多少,复杂度就为多少,一共扩容 logN 次,总扩容复杂度为 O(N*logN),均摊下来每个元素,复杂度为O(logN)。

如果给其传入比较器可以变成大根堆

比较器的使用
1)比较器的实质就是重载比较运算符
2)比较器可以很好的应用在特殊标准的排序上
3)比较器可以很好的应用在根据特殊标准排序的结构上

public static class AComp implements Comparator<Integer>{
    //如果返回负数,认为第一个参数应该排在前面
    //如果返回正数,认为第二个参数应该排在前面
    //如果返回0,认为谁放前面都行
    @Override
    public int compare(Integer argo, Integer arg1) {
        return arg1 - arg0;
    }
}
​
public static void main(String[] args){
    PriorityQueue<Integer> heap = new PriorityQueue<>(new AComp());
    heap.add(6);
    heap.add(9);
    heap.add(3);
    heap.add(2);
    heap.add(10);
    while(!heap.isEmpty()) {
        System.out.println(heap.pol1());
    }
}

11.5 堆排序扩展题目

已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。

因为0位置上的正确数一定在0-6这七个数中,所以将这7个数在小根堆中排好序,最小值就可以弹出放到0位置上,然后再加入下一个数,进行重复操作。复杂度为O(Nlogk)。

  public void sortedArrDistanceLessK(int[] arr, int k) {
        PriorityQueue<Integer> heap = new PriorityQueue<>();
        int index = 0;
        //k个数形成小根堆
        for (; index < Math.min(arr.length, k); index++) {
            //index < Math.min(arr.length, k)进行的操作是避免传的参数不合适造成程序的无法正常运行
            heap.add(arr[index]);
        }
        int i = 0;
        for (; index < arr.length; i++, index++) {
            heap.add(arr[index]);//加一个数
            arr[i] = heap.poll();//弹出一个最小值
        }
        while (!heap.isEmpty()) {//依次弹出k个最小值
            arr[i++] = heap.poll();
        }
    }
​

系统提供的堆,只能给一个数,弹出一个数,不能做到上述的高效操作,要实现有高效操作的,必须自己写。

桶排序思想下的排序

1)计数排序
2)基数排序

分析:

1)桶排序思想下的排序都是不基于比较的排序
2)时间复杂度为0(N),额外空间负载度O(M)
3)应用范围有限,需要样本的数据状况满足桶的划分

11.6  计数排序:

11.7  基数排序

 先按个位数放进桶,然后从左往右,先进先出导出,再按十位数排序,重复,再按百位

代码的实现count不是记录桶 i 里面有多少个数,而是记录 ≤ i 里面有多少个数。

  // only for no-negative value
    public static void radixSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        radixSort(arr, 0, arr.length - 1, maxbits(arr));
    }
    //计算最大的十进制位是第几位
    public static int maxbits(int[] arr) {
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < arr.length; i++) {
            max = Math.max(max, arr[i]);//寻找数组中最大的数
        }
        int res = 0;
        while (max != 0) {
            res++;
            max /= 10;//自动整除,因为max是int
        }
        return res;
    }
​
    public static void radixSort(int[] arr, int begin, int end, int digit) {
        final int radix = 10;
        int i = 0, j = 0;
​
        int[] bucket = new int[end - begin + 1];
        //digit多少哥十进制位,也代表入桶出桶的次数
        for (int d = 1; d <= digit; d++) {
            int[] count = new int[radix];
            //用于记录当前位上等于0,...,等于9的各有多少个数
            for (i = begin; i <= end; i++) {
                j = getDigit(arr[i], d);//确认当位上的数是多少
                count[j]++;//等于该位上的数,统计加1
            }
            //用于记录当前位上小于等于0,...,小于等于9的各有多少个数
            //同时也记录了当前位上等于0,...,等于9的数组最后一个数出桶后的位置
            for (i = 1; i < radix; i++) {
                count[i] = count[i] + count[i - 1];
            }
            for (i = end; i >= begin; i--) {
                j = getDigit(arr[i], d);
                bucket[count[j] - 1] = arr[i];//出桶后的位置上放该数
                count[j]--;//该桶上的数减一
            }
            for (i = begin, j = 0; i <= end; i++, j++) {
                //把bucket的数组导入arr中,相当于保留了这次桶排序
                arr[i] = bucket[j];
            }
        }
    }
  • 48
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 37
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 37
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖虎不秃头

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值