寻找最小的k个数

package com.refe.algorithm;

import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

/*输入n个整数,输出其中最小的k个。
 * 例如输入1,2,3,4,5,6,7和8这8个数字,
 * 则最小的4个数字为1,2,3和4。
 * 
 * 1、第一次遍历取出最小的元素,第二次遍历取出第二小的元素,依次直到第k次遍历取出第k小的元素。
 * 这种方法最简单,时间复杂度是O(k*n)。看上去效率很差,但当k很小的时候可能是最快的。
 * 2、对这n个元素进行排序,然后取出前k个数据即可,可以采用比较普遍的堆排序或者快速排序,时间复杂度是O(n*logn)。
 * 这种方法有着很大的弊端,题目并没有要求这最小的k个数是排好序的,更没有要求对其它数据进行排序,
 * 对这些数据进行排序某种程度上来讲完全是一种浪费。而且当k=1时,时间复杂度依然是O(n*logn)。
 * 3、可以把快速排序改进一下,应该和楼主的kth_elem一样,这样的好处是不用对所有数据都进行排序。
 * 平均时间复杂度应该是O(n*logk)。(在本文最后一节,你或将看到,复杂度可能应该为O(n))
 * 4、使用我开始讲到的平衡二叉树或红黑树,树只用来保存k个数据即可,这样遍历所有数据只需要一次。
 * 时间复杂度为O(n*logk)。后来我发现这个思路其实可以再改进,使用堆排序中的堆,堆中元素数量为k,
 * 这样堆中最大元素就是头节点,遍历所有数据时比较次数更少,当然时间复杂度并没有变化。
 * 5、使用计数排序的方法,创建一个数组,以元素值为该数组下标,数组的值为该元素在数组中出现的次数。
 * 这样遍历一次就可以得到这个数组,然后查询这个数组就可以得到答案了。时间复杂度为O(n)。
 * 如果元素值没有重复的,还可以使用位图方式。这种方式有一定局限性,元素必须是正整数,
 * 并且取值范围不能太大,否则就造成极大的空间浪费,同时时间复杂度也未必就是O(n)了。
 * 当然可以再次改进,使用一种比较合适的哈希算法来代替元素值直接作为数组下标。
 * 
 * 最好的方案是第一节中所提到的思路3:当然,更好的办法是维护k个元素的最大堆,原理与上述第2个方案一致,
 * 即用容量为k的最大堆存储最小的k个数,此时,k1<k2<...<kmax(kmax设为大顶堆中最大元素)。
 * 遍历一次数列,n,每次遍历一个元素x,与堆顶元素比较,x<kmax,更新堆(用时logk),否则不更新堆。
 * 这样下来,总费时O(n*logk)。
 * */
public class MinK {
	@Test
	public void test() {
		int[] array = { 6, 5, 7, 2, 8, 1, 3, 4 };
		if (4 <= array.length)
			// topk(array, 4);
			topkheap(array, 4);
	}

	private void topkheap(int[] array, int n) {
		List heap = new ArrayList();// 维护一个大顶堆
		for (int i : array) {
			heap.add(i);
			formatheap(heap);

			if (heap.size() == n)
				break;
		}

		for (int i = n; i < array.length; i++) {
			System.out.println(heap);
			if (array[i] < (int) heap.get(0)) {
				heap.set(0, array[i]);
				formatheap(heap);
			}
		}
		System.out.println(heap);
	}

	private void formatheap(List heap) {// 格式化堆,将大数推向堆顶
		for (int i = heap.size() - 1; i > 0; i = (i - 1) >> 1) {
			int max = 0;
			if (i % 2 != 0) // 左孩子
				max = i + 1 < (heap.size() - 1) ? getmax(heap, i, i + 1) : i;
			else
				// 右孩子
				max = getmax(heap, i, i - 1);

			if ((int) heap.get(max) > (int) heap.get((i - 1) >> 1))
				swap(heap, max, (i - 1) >> 1);
		}
	}

	private int getmax(List heap, int i, int j) {
		return (int) heap.get(i) > (int) heap.get(j) ? i : j;
	}

	private void swap(List heap, int i, int j) {
		int tmp = (int) heap.get(i);
		heap.set(i, heap.get(j));
		heap.set(j, tmp);
	}

	// 位图法,适用没有重复的元素,并且元素的值是正整数并且值不大
	private void topk(int[] array, int n) {
		long mask = 0;
		for (int i : array)
			mask = mask | (1 << (i));
		// System.out.println(mask);
		int index = 0, m = 0;
		while (m < n) {
			if ((mask & (1 << index)) != 0) {
				System.out.print(index);
				m++;
			}
			index++;
		}
	}
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值