剑指offer:最小的k个数的两种Java解决(冒泡排序与大根堆)

题目

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

方法1,改进后的冒泡排序(时间复杂度为O(n*k))

问题分析

普通的冒泡排序使用了两层循环,第一层循环遍历了数组中所有的元素,第二层循环比较相邻的元素。如果第一个比第二个大,就交换他们两个。因此,我们并不需要得到所有数组元素的大小排序结果,我们只需要将外循环执行k次即可。

注意!!!冒泡排序每一次冒泡最大值(或最小值)都在数组最末端,每一次添加元素时,需要从数组末尾抽取,这时的元素才符合标准,而不能从数组开头抽取!!

代码展示

public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> al = new ArrayList<Integer>();
        if (k > input.length) {
			return al;
		}
		for (int i = 0; i < k; i++) {
			for (int j = 0; j < input.length - i - 1; j++) {
				if (input[j] < input[j + 1]) {
					int temp = input[j];
					input[j] = input[j + 1];
					input[j + 1] = temp;
				}
			}
			al.add(input[input.length - i - 1]);
		}
		return al;
    }

方法2,使用大根堆(时间复杂度为O(n*logn))

问题分析

当原数组中的数据顺序不可修改,并且n的值过于大的时候,各种排序算法要将n个数加载到内存中,即:如果是海量数据中查找出最小的k个数,那么这种办法是效率很低的。所以我们尝试使用另一种方法。
创建一个大小为k的数组,遍历n个整数,如果遍历到的数小于大小为k的数组的最大值,则将此数与其最大值替换。
由于每次都要拿n个整数和数组中的最大值比较,所以选择大根堆这一数据结构。

堆的概念请参考:https://www.cnblogs.com/tong-yuan/p/Heap.html
堆排序的定义与实现请参考:https://blog.csdn.net/qq_36186690/article/details/82505569

代码实现

public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> list = new ArrayList<>();
        if (input == null || input.length == 0 || k > input.length || k == 0)
            return list;
        int[] arr = new int[k + 1];//数组下标0的位置作为哨兵,不存储数据
        //初始化数组
        for (int i = 1; i < k + 1; i++)
            arr[i] = input[i - 1];
        buildMaxHeap(arr, k + 1);//构造大根堆
        for (int i = k; i < input.length; i++) {
            if (input[i] < arr[1]) {
                arr[1] = input[i];
                adjustDown(arr, 1, k + 1);//将改变了根节点的二叉树继续调整为大根堆
            }
        }
        for (int i = 1; i < arr.length; i++) {
            list.add(arr[i]);
        }
        return list;
    }

     //@Description: 构造大根堆
  
    public void buildMaxHeap(int[] arr, int length) {
        if (arr == null || arr.length == 0 || arr.length == 1)
            return;
        for (int i = (length - 1) / 2; i > 0; i--) {
            adjustDown(arr, i, arr.length);
        }
    }


//堆排序中对一个子二叉树进行堆排序
  
    public void adjustDown(int[] arr, int k, int length) {
        arr[0] = arr[k];//哨兵
        for (int i = 2 * k; i <= length; i *= 2) {
            if (i < length - 1 && arr[i] < arr[i + 1])
                i++;//取k较大的子结点的下标
            if (i > length - 1 || arr[0] >= arr[i])
                break;
            else {
                arr[k] = arr[i];
                k = i; //向下筛选
            }
        }
        arr[k] = arr[0];
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值