首页 > 学院 > 开发设计 > 正文

【练习】从一组数字中找出最大的几个,用堆完成

2019-11-06 07:40:39
字体:
来源:转载
供稿:网友

从一组数字中找出最大的几个,例如从n个数字中找出最大的k个,最容易想到的方法是首先对这n个数字进行从小到大的排序,然后选出前面的k个数字就行了,快速排序的平均时间复杂度是O(nlogn)。不过,在有些面试题里面会有这样一个条件: n非常大,以至于无法将这n个数一次性全部读入内存。

这种时候可以用大顶堆来完成这个题目,如果形象的用完全二叉树来想象堆的样子的话,大顶堆中满足“任意一个结点中的值,都大于其子结点中的值”。

在进行实现的时候,也不需要真的用一个树来存放数字,因为是完全二叉树,所以可以很方便的用数组来存放(可以理解为,从树的根结点开始进行广度优先遍历,把遍历的结果以此放在数组中)。只要在这个数组arr中,对于任意一个下标i,满足arr[i*2+1]>=arr[i]arr[i*2+2]>=arr[i],则这个数组满足堆的性质。

这个数据中的第一个元素(即arr[0])对应那棵完全二叉树的根结点中的值,也就是这个完全二叉树中最大的值(大顶堆的根结点中的值)。只要构建好了这样一个包含k个数的大顶堆,接下来就可以依次遍历那n个数,把每一个数同根结点的值进行比较: (1)如果这个数大于大顶堆的根结点,那么这个数就不会是“最小的k个数”之一,不理会这个数; (2)如果这个数等于大顶堆的根结点,虽然这个数是“最小的k个数”之一,但是同样不理会这个数,至于原因,举例来说,我们要找的是最小的3个数,现在大顶堆里面放的是“1,2,2”,现在又读到一个“2”,我们自然不可能把大顶堆里面的“1”换成“2”,用“2”替换“2”等于没换,所以不理会现在读到的这个“2”; (3)如果这个数小于大顶堆的根结点,那么自然大顶堆的根结点不在“最小的k个数”之中,先用这个新来的数替换大顶堆根结点的值,然后再对大顶堆进行调整,使之保持堆的结构。

用堆来完成“用n个数中找出最小的k个数”,时间复杂度为O(nlogk),时间复杂度比使用排序法稍微好一点,不过最大的优势应该是应对那种“n个数无法一次性全部读入内存”的情况。

至于怎么对大顶堆进行调整,使其保持堆的结构,可以下面的代码,就是replace()方法中的代码。

下面是大顶堆的代码:

public class MyMaxHeap { PRivate int size; private int[] arr; public MyMaxHeap(int size) { this.size=size; arr = new int[size]; for(int i=0; i<size; i++) { arr[i] = Integer.MAX_VALUE; } } public int peek() { return arr[0]; } public void replace(int val) { arr[0] = val; moveBigger2Top(); } public int[] getVals() { return arr; } public void moveBigger2Top() { int maxInd = size-1; int lastNonLeafInd = (maxInd-1)/2; int curNodeInd = 0; while( curNodeInd<= lastNonLeafInd ) { int leftSonInd = curNodeInd * 2+1; int rightSonInd = leftSonInd + 1; int targetNodeInd = leftSonInd; if( rightSonInd<=maxInd && arr[rightSonInd] > arr[leftSonInd] ) { targetNodeInd = rightSonInd; } if( arr[ targetNodeInd ] > arr[curNodeInd] ) { int t=arr[ targetNodeInd ]; arr[ targetNodeInd ] = arr[ curNodeInd ]; arr[ curNodeInd ] = t; } curNodeInd = targetNodeInd; } }}

下面是测试代码,与使用排序法得到的结果进行比较,观察结果是不是一样来判断代码写对了没有。

import java.util.Arrays;import java.util.Collections;public class Test { public static void main(String[] args) { // TODO Auto-generated method stub int length=2000; int heapSize=100; int[] arr1=new int[length]; int[] arr2=new int[length]; for(int i=0; i<length; i++) { arr1[i] = (int)(Math.random()*10000.0); arr2[i] = arr1[i]; } System.out.println("原始数组:"); for(int val: arr1) { System.out.print(val+" "); } MyMaxHeap heap=new MyMaxHeap(heapSize); for(int i=0; i<length; i++) { if(arr1[i] <heap.peek()) { heap.replace(arr1[i]); } } int[] minVals1=heap.getVals(); int sum1=0; System.out.println(); System.out.println("用最大堆选出来的最小"+heapSize+"个数:"); for(int val: minVals1) { sum1+=val; System.out.print(val+" "); } System.out.println(); System.out.println("其和为:"+sum1); Arrays.sort(arr2); int sum2=0; System.out.println(); System.out.println("用排序算法选出来的最小"+heapSize+"个数:"); for(int i=0;i<heapSize; i++) { sum2+=arr2[i]; System.out.print(arr2[i]+" "); } System.out.println(); System.out.println("其和为:"+sum2); System.out.println(); System.out.println("done."); }}
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表