有20个数组,每个数组里面有500个数,升序排列,求出这10000个数中的最大500个数。并求复杂度。【百度2012年的一道算法题】

题目描述

有20个数组,每个数组有500个升序排列的数,求出这10000个数中前500个最大的数,并求复杂度。

解题思路

用一个大小为20的最大堆,通过维护这个堆,每次选出一个最大的数,如此往复500次,所以时间复杂度500 * log(20)

代码

这道题比较有意思的是利用最大最小堆求解,以及求解最大堆解决的复杂度。代码分为两个部分,第一部分是我自己写的一个简略最大堆作为基础数据结构: MaxHeap.java

package wheels;

/**
 * 最大堆的简单实现
 * 类比最小堆,在此主要是锻炼自己迅速写各式各样的轮子,再写一遍
 * @author XZP
 * 一组测试数据: 12 45 -11 69 1 95 9 78 64 21
 */
public class MaxHeap {
	private int INF = -9999999; // 认为规定的最小值
	private int[] arr;
	public MaxHeap (int size) {
		arr = new int[size + 1]; // 下标从1开始
		for (int i = 0; i <= size; i++) { // 牺牲空间和一定时间来保证数组能存零值
			arr[i] = INF;
		}
	}
	/**
	 * 向最大堆中插入一个元素使其任然满足最大堆
	 * @param data 待插入数据
	 * @return 成功为true,失败将打印异常栈
	 */
	public boolean push(int data) {
		int index = this.index();
		try {
			this.arr[index] = data;
			siftup(index);  // 插入数组末尾之后需要向上调整使其满足最大堆
		} catch (IndexOutOfBoundsException e) {
			e.printStackTrace();
		}
		return true;
	}
	/**
	 * @return 数组能存储的最大size
	 */
	public int size() {
		return arr.length - 1;
	}
	/**
	 * 获取当前能插入值得数组下标
	 * @return 数组中已有值长度  + 1
	 */
	private int index() {
		int usedLength = 0;
		for (int i = 1; i <= this.size(); i++) {
			if (this.arr[i] != INF) {
				usedLength++;
			}
		}
		return usedLength + 1;
	}
	/**
	 * 从最小堆中取最小的那个元素,并调整使其一直满足最小堆
	 * @return 数组不为空则返回最小的元素,否则返回人为规定的INF值
	 */
	public int pop() {
		if (!isLegal(1)) { // 每次取第一个元素(下标值为1,而不是0)
			return INF;
		}
		int value = this.arr[1];
		int end = this.index() - 1; // 数组中最后一个有效元素
		if (!isLegal(end)) { // 这个元素不合法说明此时数组中只有索引为1这个值有效
			this.arr[1] = INF;
			return value;
		}
		this.arr[1] = this.arr[end]; // 将末尾有效值(层数最深最右边的叶子结点)置为根节点并向下调整
		this.arr[end] = INF;
		siftdown(1);
		return value;
	}
	/**
	 * 向上调整的方法
	 * @param i 待调整的孩子节点
	 */
	private void siftup(int i) {
		if (i <= 1) {
			return; // 1 左移一位是0了
		}
		int parent = i >> 1;
		boolean flag = true; // 是否需要调整
		while (parent >= 1 && flag) {
			if (this.arr[parent] < this.arr[i]) {
				swap(parent, i);
				siftup(parent);
				parent = parent >> 1;
			} else {
				flag = false;
			}
		}
	}
	/**
	 * 向下调整的方法
	 * @param i 待调整的节点下标
	 */
	private void siftdown (int i) {
		int parent = i;
		int left = this.left(i);
		int right = this.right(i);
		int max = this.getMax(left, right);
		boolean flag = true;
		while (parent <= this.size() / 2 && flag && isLegal(max)) {
			if (this.arr[max] > this.arr[i]) {
				swap(max, i);
				parent = max;
				siftdown(parent);
			} else {
				flag = false;
			}
		}
	}
	/**
	 * 获取根节点左孩子的下标值
	 * @param i
	 * @return
	 */
	private int left(int i) {
		return i << 1;  //左孩子的坐标为右移一位
	}
	/**
	 * 获取根节点右孩子的的下标值
	 * @param i
	 * @return
	 */
	private int right(int i) {
		return (i << 1) + 1; // 右孩子为右移一位 + 1
	}
	/**
	 * 获取值较大的下标值
	 * @param i 左孩子下标值
	 * @param j 右孩子下标值
	 * @return 如果有下标值有一个没有越界,返回较大的那个。如果都越界,则返回无效索引值0
	 */
	private int getMax(int i, int j) {
		if (isLegal(i) && isLegal(j)) { // 如果两个都没有越界,返回较小的下标值
			return this.arr[i] > this.arr[j] ? i : j;
		} else if (isLegal(i)) { // 返回左孩子下标
			return i;
		} else if (isLegal(j)) { // 返回右孩子下标
			return j;
		} else { // 返回备用索引0
			return 0;
		}
	}
	/**
	 * 交换两下标对应的值
	 * @param i
	 * @param j
	 */
	private void swap(int i, int j) {
		int temp = this.arr[i];
		this.arr[i] = this.arr[j];
		this.arr[j] = temp;
	}
	/**
	 * 检查一个下标是否魏越界或者是否存储了实际值
	 * @param i
	 * @return 在数组范围内返回true,越界返回false
	 */
	private boolean isLegal(int i) {
		return 1 <= i && i <= this.size() ? this.arr[i] != INF ? true : false : false; // 首先判断数组下标越界与否,在没有越界的基础上还需要判断是否是有效值
	}
}

第二部分是题目的具体实现:FindTheMaxNumber.java

package sort.heapsort;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Random;

import wheels.MaxHeap;


/**
 * 题目描述见目录下的README.md文件
 * @author XZP
 *
 */
public class FindTheMaxNumber {
	public static void main(String[] args) {
		int row = 20;
		int column = 500;
		Random random = new Random();
		Integer[][] data = new Integer[row][column];
		// 数据生成
		for (int r = 0; r < row; r++) {
			for (int c = 0; c < column; c++) {
				data[r][c] = random.nextInt(1000); // 随机生成[0, 1000)的伪随机数
			}
			Arrays.sort(data[r]); // 对数组进行升序排列
		}
		// 用一个map来存值在数组中对应的下标
		HashMap<Integer, ArrInfo> value2ArrInfo = new HashMap<>();
		// 用一个数组存储结果
		ArrayList<Integer> result = new ArrayList<>();
		// 初始化一个最大堆
		MaxHeap maxHeap = new MaxHeap(20);
		// 先将20个数组各自的最大值入堆
		for (int i = 0; i < 20; i++) {
			maxHeap.push(data[i][499]);
			value2ArrInfo.put(data[i][499], new ArrInfo(i, 499));
		}
		// 循环500次,每次取一个最大的数
		int value;
		for (int i = 0; i < 500; i++) {
			value = maxHeap.pop();
			result.add(value);
			// 找到value对应的数组然后选取该数组的下一个最大数放入堆
			int r = value2ArrInfo.get(value).getArr();
			int index = value2ArrInfo.get(value).getIndex();
			maxHeap.push(data[r][index - 1]); // 将该值入堆,很巧妙的是不用判断index是否越界
			value2ArrInfo.put(data[r][index - 1], new ArrInfo(r, index - 1));
		}
		int size = result.size();
		System.out.println("结果计算完毕,总大小: " + size);
		for (int i = 0; i < size; i++) {
			System.out.println(result.get(i) + " ");
		}
	}
}
class ArrInfo {
	private int arr; // 存储数组编号
	private int index; // 存储在数组中的索引
	public ArrInfo(int arr, int index) {
		this.arr = arr;
		this.index = index;
	}
	public int getArr() {
		return arr;
	}
	public int getIndex() {
		return index;
	}
	
}

细节都注释上了,在此不多赘述。

你可能感兴趣的:(算法与数据结构)