剑指 Offer 45. 把数组排成最小的数

43 篇文章 1 订阅
4 篇文章 0 订阅

今天我通过一道简单题目,帮助大家了解一下选择排序,堆排序
题目描述:

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

题目示例:
在这里插入图片描述

选择排序

刚开始拿到这道题目,我首先想到的就是 选择排序,不是很了解选择 排序的可以先看一下:《排序算法—选择排序》
本题通过选择排序的代码如下:

    public int[] getLeastNumbers(int[] arr, int k) {
        int length = arr.length;
        if (length <= 0) return arr;
        return selectSort(arr, k);
    }

    public int[] selectSort(int[] nums, int k) {
        int[] re = new int[k];
        int length = nums.length;
        int count = 0;
        for (int i = 0; i < length - 1; i++) {
            int min = i;
            for (int j = i + 1; j < length; j++) {
                if (nums[min] > nums[j]) min = j;
            }
            if (count < k) {
                re[count++] = nums[min];
                if (min != i) {
                    int t = nums[i];
                    nums[i] = nums[min];
                    nums[min] = t;
                }
            } else return re;
        }
        if (count < k) {
            re[count] = nums[count];
        }
        return re;
    }

后来发现执行起来特别慢:
在这里插入图片描述

快速排序

然后我又开始尝试快速排序,快速排序在另一篇文章里从原理到实现讲的很详细,不会的可以看一下:《排序算法—快速排序》
然后我们直接看看本题快速排序的实现,先看看最简单的思路,直接快排所有元素,然后返回前k个元素

   public int[] getLeastNumbers(int[] arr, int k) {
        int length = arr.length;
        if (length <= 0) return arr;
        int[] re = new int[k];
        quickSort(arr, 0, length-1);
        for (int i = 0; i < k; i++) {
            re[i] = arr[i];
        }
        return re;
    }
        public void quickSort(int[] nums, int start, int end) {
        if (start >= end) return;
        int left = start;
        int right = end;
        int privot = nums[left];
        while (left < right) {
            while (left < right && nums[right] >= privot) {
                right--;
            }
            nums[left] = nums[right];
            while (left < right && nums[left] <= privot) {
                left++;
            }
            nums[right] = nums[left];
        }
        nums[left] = privot;
        quickSort(nums, start, left - 1);
        quickSort(nums, left + 1, end);
    }

在这里插入图片描述

速度明显提升,但是通过快排所有元素,太过于浪费,我们找前 K 大/前 K 小问题不需要对整个数组进行 O(NlogN) 的排序
直接通过快排切分排好第 K 小的数(下标为 K-1),那么它左边的数就是比它小的另外 K-1 个数啦~

再通俗点就是:我们需要划分的下标如:nums= {3, 2, 1, 3, 30, 34, 5, 9};k=2
那么我们第一次划分的结果为:{1,2,3,3,30,34,5,9},划分的边界值 为 (即left的值):2
由于left左边的一定小于left右边的,所以我们只需要继续划分左边的即可,于是最终代码如下:

 public int[] getLeastNumbers(int[] arr, int k) {
        int length = arr.length;
        if (length <= 0) return arr;
        int[] re = new int[k];
        quickSort(arr, 0, length - 1, k);
        for (int i = 0; i < k; i++) {
            re[i] = arr[i];
        }
        return re;
    }
     public void quickSort(int[] nums, int start, int end, int k) {
        if (start >= end || start >= k) return;
        int left = start;
        int right = end;
        int privot = nums[left];
        while (left < right) {
            while (left < right && nums[right] >= privot) {
                right--;
            }
            nums[left] = nums[right];
            while (left < right && nums[left] <= privot) {
                left++;
            }
            nums[right] = nums[left];
        }
        nums[left] = privot;
        if (left >= k) {//如果left大于k说明左边已经满足
            quickSort(nums, start, left - 1, k);
        }

        if (left < k) {
            quickSort(nums, left + 1, end, k);
        }

    }

在这里插入图片描述

可以看到效率又提升了一倍

堆排序

最后我们再看一下 堆排序,《排序算法—堆排序》

本题由于是实现最小的k个数, 所以我们通过小顶堆实现,代码如下:

class Solution {
   public int[] getLeastNumbers(int[] arr, int k) {
        int length = arr.length;
        if (length <= 0) return arr;
        int[] re = heapSort(arr, k);
        return re;
    }


    /**
     * 堆排序
     *
     * @param arr
     */
    public static int[] heapSort(int[] arr, int k) {
        int j = 0;//re下标
        int[] re = new int[k];
        if (arr == null || arr.length == 0 || k <= 0) {
            return re;
        }

        int len = arr.length;
        // 构建大顶堆,这里其实就是把待排序序列,变成一个大顶堆结构的数组
        buildMaxHeap(arr, len);

        // 交换堆顶和当前末尾的节点,重置大顶堆
        for (int i = len - 1; i > 0; i--) {
            swap(arr, 0, i);//交换堆顶元素和堆尾元素的值
            re[j++] = arr[i];
            if (j < k) {
                len--;
                HeadAdjust(arr, 0, len);
            } else break;

        }
        if (j < k) {
            re[j] = arr[0];
        }
        return re;
    }

    private static void buildMaxHeap(int[] arr, int len) {
        // 从最后一个非叶节点开始向前遍历,调整节点性质,使之成为大顶堆
        for (int i = len / 2 - 1; i >= 0; i--) {//从i=length/2-1
            HeadAdjust(arr, i, len);
        }
    }

    private static void HeadAdjust(int[] arr, int i, int len) {
        // 先根据堆性质,找出它左右节点的索引
        int left = 2 * i + 1;
        int right = 2 * i + 2;
        // 默认当前节点(父节点)是最大值。
        int largestIndex = i;
        if (left < len && arr[left] < arr[largestIndex]) {
            // 如果有左节点,并且左节点的值更小,更新最小值的索引
            largestIndex = left;
        }
        if (right < len && arr[right] < arr[largestIndex]) {
            // 如果有右节点,并且右节点的值更大,更新最大值的索引
            largestIndex = right;
        }

        if (largestIndex != i) {
            // 如果最大值不是当前非叶子节点的值,那么就把当前节点和最大值的子节点值互换
            swap(arr, i, largestIndex);
            // 因为互换之后,子节点的值变了,如果该子节点也有自己的子节点,仍需要再次调整。
            HeadAdjust(arr, largestIndex, len);
        }

    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

在这里插入图片描述

可以发现效率一般,但是比选择排序效率要高

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ZNineSun

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

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

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

打赏作者

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

抵扣说明:

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

余额充值