数据结构面试题1.2.9-查找最小的K个元素-使用最大堆

Java最大堆相关知识:http://blog.csdn.net/sayangnala/article/details/48682381


package questions;

import java.util.Arrays;
import java.util.Random;

/**
 * @title 查找最小的K个元素-使用最大堆
 * @question 输入n个整数,输出其中最小的K个元素,例如,输入1,2,3,4,5,6,7,8那么最小的4个元素就是1,2,3,4
 * @analysis 
 *           堆其实是一颗完全二叉树,堆对于两类问题有着很好的解决方案:a.排序问题:由于堆是一颗完全二叉树,所以采用堆,堆n元数组进行排序,时间复杂度不会超过O
 *           (nlgn),而且只需要几个额外的空间。b.优先级队列。通过插入新元素和调整堆结构来维护堆的性质,每个操作所需要的时间都是O(lgn)<br>
 *           堆常见实现是采用一个大小为n的数组存储元素,并且0号单元舍弃不用。对堆中的元素按照层次从上到下,从左导游的顺序依次编号。
 *           那么对于编号为i的元素:<br>
 *           a:如果左孩子存在,那么左孩子的编号为2i<br>
 *           b:如果右孩子存在,那么右孩子的编号为2*i+1<br>
 *           c:如果有父结点,那么父结点的编号为i/2<br>
 *           d:结点为叶结点的条件是左孩子且右孩子都为空,为空结点的条件是i<1或者i>n<br>
 *           堆的设计对于处理Top K问题十分方便。首先设置一个大小为K的堆(如果求最大top K,那么用最小堆,如果求最小top
 *           K,那么用最大堆),然后扫描数组。并将数组的每个元素与堆的根比较,符合条件的就插入堆中,同时调整堆使之符合堆的特性,扫描完成后,
 *           堆中保留的元素就是最终的结果
 *           。说到调整堆,不得不提的是调整的算法,分为两类:向下调整(shiftdown)和向上调整(shiftup)。<br>
 *           有了堆的基本操作,top K问题就有了一个基础(当然也完全可以完全不用堆解决top K问题)。以最小top
 *           K问题为例(此时需要建立大小为K的最大堆),top
 *           K的求解过程是:扫描原数组,将数组的前K个元素扔到堆中,调整使之保持堆的特性。对于k之后的元素
 *           ,如果比堆顶的元素小,那么替换堆顶元素并调整堆,扫描数组完成后,堆中保存的元素就是最终的结果。
 * 
 * 
 * 
 * @link 堆的理解http://www.java3z.com/cwbwebhome/article/article1/1362.html?id=4745
 * @author Sam
 *
 */
public class Ex1o2o9 {
	public static void main(String[] args) {
		int[] a = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
				18, 19 };
		int k = 7;
		MinKElement mke = new MinKElement();
		mke.disArrange(a);
		System.out.println("after disarranging,the array a[]:");
		System.out.println(Arrays.toString(a));
		mke.findKMin(a, k);
	}

}

/**
 * 最小的K个元素,使用最大堆,当然使用快速排序也是OK的
 */
class MinKElement {
	/**
	 * rearrange the array, just for test.随机调换数组
	 * 
	 * @param a
	 */
	public void disArrange(int[] a) {
		for (int i = 0, len = a.length; i < len; i++) {
			Random random = new Random();
			int j = random.nextInt(len);
			swap(a, i, j);
		}
	}

	/**
	 * 数组a中下标为i,j数据交换
	 * 
	 * @param a
	 * @param i
	 * @param j
	 */
	private static void swap(int[] a, int i, int j) {
		int temp = a[i];
		a[i] = a[j];
		a[j] = temp;
	}

	public void findKMin(int[] a, int k) {
		// you can do this:int[] heap = new int[k].but that may be space-cost
		int[] heap = a;
		// create MaxHeap of K elements form the last RootIndex to 0.
		// 先建立K的最大堆,K个值,那么起始点为K/2-1
		int rootIndex = k / 2 - 1;
		while (rootIndex >= 0) {
			reheap(heap, rootIndex, k - 1);
			rootIndex--;
		}
		// 建立好K的最大堆之后,循环,将length-k之外的值不断的和最大值进行对比,小于最大值,就纳入堆中,调整堆的最大堆
		for (int i = k, len = heap.length; i < len; i++) {
			if (heap[i] < heap[0]) {
				heap[0] = heap[i];
				reheap(heap, 0, k - 1);
			}
		}
		System.out.println(toString(heap, k));
	}

	// reheap:from root to lastIndex.
	public void reheap(int[] heap, int rootIndex, int lastIndex) {
		int orphan = heap[rootIndex];
		boolean done = false;
		int leftIndex = rootIndex * 2 + 1;
		while (!done && leftIndex <= lastIndex) {
			int largerIndex = leftIndex;
			if (leftIndex + 1 <= lastIndex) {
				int rightIndex = leftIndex + 1;
				if (heap[rightIndex] > heap[leftIndex]) {
					largerIndex = rightIndex;
				}
			}
			// Attention! should not use-->heap[root]<heap[largerIndex]<--
			// I spend time to find the problem....
			if (orphan < heap[largerIndex]) {
				heap[rootIndex] = heap[largerIndex];
				rootIndex = largerIndex;
				leftIndex = rootIndex * 2 + 1;
			} else {
				done = true;
			}
		}
		heap[rootIndex] = orphan;
	}

	public static String toString(int[] a, int length) {
		if (a == null)
			return "null";
		int iMax = length - 1;
		if (iMax == -1)
			return "[]";

		StringBuilder b = new StringBuilder();
		b.append('[');
		for (int i = 0;; i++) {
			b.append(a[i]);
			if (i == iMax)
				return b.append(']').toString();
			b.append(", ");
		}
	}

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值