1. 堆(Heap)
也是一种树状的数据结构。 任意节点的值总是大于等于子节点的值,称为最大堆、大根堆、大顶堆。 任意节点的值总是小于等于子节点的值,称为最小堆、小根堆、小顶堆。 堆中的元素必须具备可比较性。
1.1 接口设计
public interface Heap<E> {
int size();
boolean isEmpty();
void clear();
void add(E e);
E get();
E remove();
E replace(E e);
}
2. 二叉堆(Binary Heap)
二叉堆的逻辑结构就是一颗完全二叉树,也称完全二叉堆。
使用数组从上到下,从左到右来存储元素。
索引 i 规律(n为元素数量):
i = 0 ,根节点;i > 0 ,则父节点为floor((i - 1) / 2) ;2i + 1 <= n - 1 ,则左子节点为2i + 1 ;2i + 1 > n - 1 ,无左子节点;2i + 2 <= n -1 ,则右子节点为2i + 2 ;2i + 2 > n - 1 ,无右子节点。
2.1 二叉堆实现
2.2 全局变量和构造函数
private E[] es;
private int size;
private Comparator<E> comparator;
private static final int DEFAULT_CAPACITY = 10;
public BinaryHeap(Comparator<E> comparator){
this.comparator = comparator;
this.es = (E[]) new Object[DEFAULT_CAPACITY];
}
public BinaryHeap(){
this(null);
}
2.3 比较方法
private int compare(E e, E e1){
return comparator != null ? comparator.compare(e,e1)
: ((Comparable)e).compareTo(e1);
}
2.4 简单方法实现
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public void clear() {
for (E e : es) {
e = null;
}
size = 0;
}
2.5 get()
private void emptyCheck(){
if (size == 0){
throw new IndexOutOfBoundsException("Heap is empty");
}
}
@Override
public E get() {
emptyCheck();
return es[0];
}
2.6 add()
最大堆
- 例如添加元素80,放到数组的最后面,明显逻辑结构不满住大顶堆的性质。
- 解决方法:与父节点交换位置直到不能再往上交换,或者小于父节点。这种方法称为上滤。
private void siftUp(int index){
E e = es[index];
while (index > 0){
int parentIndex = (index - 1) >> 1;
E parent = es[parentIndex];
if (compare(e,parent) < 0){
return;
}
es[index] = parent;
index = parentIndex;
}
es[index] = e;
}
private void elementNotNullCheck(E e){
if (e == null){
throw new IllegalArgumentException("E must be not null");
}
}
private void ensureCapacity(int capacity) {
int oldCapacity = es.length;
if (oldCapacity >= capacity){
return;
}
int newCapacity = oldCapacity + (oldCapacity >> 1);
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newElements[i] = es[i];
}
es = newElements;
}
@Override
public void add(E e) {
elementNotNullCheck(e);
ensureCapacity(size + 1);
es[size++] = e;
siftUp(size - 1);
}
2.7 remove()
最大堆
直接让最后一个元素替换掉当前堆顶元素: 与子节点中最大的进行交换,直到大于子节点或者无法再进行交换,即可符合最大堆。 这个过程称为下滤。
private void siftDown(int index){
E e = es[index];
int half = size >> 1;
while (index < half){
int childIndex = (index << 1) + 1;
int rightIndex = childIndex + 1;
if (rightIndex < size && compare(es[childIndex],es[rightIndex]) < 0){
childIndex = rightIndex;
}
E child = es[childIndex];
if (compare(e,child) >= 0){
break;
}
es[index] = child;
index = childIndex;
}
es[index] = e;
}
@Override
public E remove() {
emptyCheck();
int lastIndex = --size;
E root = es[0];
es[0] = es[lastIndex];
es[lastIndex] = null;
siftDown(0);
return root;
}
2.8 replace()
public E replace(E e) {
elementNotNullCheck(e);
E root = es[0];
if (size == 0){
es[0] = e;
size = 1;
}else {
es[0] = e;
siftDown(0);
}
return root;
}
2.9 最大堆-批量建堆
给定一组数据,创建一个堆,如果for 循环add() 消耗内存比较高。
2.9.1 自上而下的上滤
从索引为1的元素开始,如果大于父节点就与父节点交换位置,直到全部交换完毕,符合最大堆
但是这个效率基本等同于添加的效率:底层的元素不仅数量多而且比较的次数也多。时间复杂度为O(nlogn)
2.9.2 自下而上的下滤
从最后一个索引位置的元素开始,排除叶子节点,如果小于子节点就与子节点交换位置。
明显的可以看出,下滤的效率高于上滤的效率:只有最上层的元素需要全部进行比较(符合条件的前提下)。时间复杂度为O(n) 。
2.9.3 实现
public BinaryHeap(){
this(null,null);
}
public BinaryHeap(E[] es, Comparator<E> comparator){
if (es == null || es.length == 0){
this.es = (E[]) new Object[DEFAULT_CAPACITY];
}else {
size = es.length;
int capacity = Math.max(es.length,DEFAULT_CAPACITY);
this.es = (E[]) new Object[capacity];
for (int i = 0; i < es.length; i++) {
this.es[i] = es[i];
}
heapify();
}
this.comparator = comparator;
}
public BinaryHeap(E[] es){
this(es,null);
}
private void heapify(){
for (int i = (size >> 1) - 1 ; i >= 0; i--) {
siftDown(i);
}
}
2.10 小顶堆
只需要在创建堆的时候指定相反的比较策略即可。
private static void test3(){
Integer[] integers = {72,68,43,38,14,10,3,50,47,21};
BinaryHeap<Integer> heap = new BinaryHeap<>(integers, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
BinaryTrees.println(heap);
}
3. Top K 问题
在n个整数中,找到最大(小)的 k 个数。
public static void getTopMinK(Integer[] nums, int k){
BinaryHeap<Integer> heap = new BinaryHeap<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
for (int i = 0; i < nums.length; i++) {
if (heap.size() < k){
heap.add(nums[i]);
}else {
if (nums[i] < heap.get()){
heap.replace(nums[i]);
}
}
}
}
public static void getTopMaxK(Integer[] nums, int k){
BinaryHeap<Integer> heap = new BinaryHeap<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
for (int i = 0; i < nums.length; i++) {
if (heap.size() < k){
heap.add(nums[i]);
}else {
if (nums[i] > heap.get()){
heap.replace(nums[i]);
}
}
}
}
|