二叉堆接口

public interface Heap<E> {

	int size();
	boolean isEmpty();
	void clear();
	void add(E element);//添加元素到堆
	E get();//取堆顶元素
	E remove();//删除堆顶元素
	E replace(E element);//删除堆顶元素的同时插入一个新元素
}

大顶二叉堆的实现

存储结构

对于采用二叉树结构的堆,使用数组存储结构,可以不需要有额外的保存左右孩子节点和父亲节点的指针域,节省空间,而且用数组存储实现简单。下标为n的节点的左右孩子节点(如果有的话)下标为2n+1和2n+2,下标为n的节点的父节点(如果有的话)下标为n/2-1。

大顶二叉堆性质

1.每个节点的值域按照比较方法来比较,比较结果将大于它的左右节点。而对于不是父子关系的节点,对谁大谁小没有要求。
2.大顶二叉堆的数组对应的二叉树是一棵完全二叉树,该完全二叉树中非叶子节点的下标值大于size/2

一些必要的辅助方法
protected int compare(E e1,E e2) {
	 return comparator!=null?comparator.compare(e1, e2):((Comparable)e1).compareTo(e2);
	}
	
		//数组扩容检查,如果需要扩容就扩容为原来的1.5倍
	private void ensureCapacity(int capacity) {
		int oldCapacity=elements.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]=elements[i];
		}
		elements=newElements;
	}
	//元素非空检查
		private void elementNotNullCheck(E element) {
		if(element==null) {
			throw new IllegalArgumentException("element must not be null.");
		}
	}
	//二叉堆非空检查
		private void emptyCheck() {
		if(size==0) {
			throw new IndexOutOfBoundsException("Heap is empty.");
		}
	}
元素添加方法

在对元素进行非空检查,并对存储空间进行溢出检查后,即可将元素先添加到二叉堆的数组末尾,然后采用 “上滤” 的方式恢复二叉堆。上滤的具体做法是,将当前节点node(此处为数组尾元素)与其父亲节点parent进行大小比较,如果当前节点node大于父亲节点parent,就交换它们的位置,然后再以交换后的parent的下标为node的下标,继续进行比较。直到遇到某次比较其父节点不小于当前节点,或者直到当前节点没有父节点时结束。
对于该上滤处理逻辑,可进行如下优化:由于当前节点node在每次与父节点比较过后其位置并未最终确定下来,所以可以在循环中先不将node放置在其父节点的位置,而是在找到合适位置后,退出循环,将node放置在该合适位置。这样减少了赋值次数。

@Override
	public void add(E element) {
		elementNotNullCheck(element);
		ensureCapacity(size+1);
		elements[size++]=element;//先将元素添加到数组末尾
		ShifUp(size-1);//再恢复二叉堆性质
	}
	private void ShifUp(int index) {
		E node=elements[index];
		while(index>0) {
			//计算并取出其父节点
			int parentIndex=(index-1)>>1;
			E parent = elements[parentIndex];
			//如果父节点不小于当前node, 退出循环执行将node放到index位置的语句
			if(compare(node, parent)<=0) break; 
			//当父节点小于node时,将父节点放到原node节点位置,并更改index,然后进行循环。
			elements[index]=parent;
			index=parentIndex;
		}
		//退出循环时已经找到了合适的下标位置
		elements[index]=node;
	}

元素移除方法

元素移除过程应先判断二叉堆是否为空,然后在进行移除操作。具体做法是:保存下标为0(即二叉树的root节点)的元素作为返回值,让数组最后一个元素(下标为size-1)覆盖下标为0的元素,并将数组最后一个元素置空,同时size–。此时元素已被删除,但二叉堆很可能不再符合大顶二叉堆性质,此时对下标为0的元素进行 “下滤” 操作,具体做法是:比较该节点的左右孩子节点,如果当前节点小于左右孩子节点中较大值,就将当前节点与其较大孩子节点交换位置,然后对交换位置后的节点进行同样的操作,即循环。循环结束条件是当节点不存在左右孩子节点时结束,亦即当下标大于size/2时(见大顶二叉堆性质2)。

@Override
	public E remove() {
		emptyCheck();//先进行空检查,非空才能进行下面的删除操作
		int lastSize=--size;
		E root = elements[0];
		elements[0]=elements[lastSize];
		elements[lastSize]=null;
		
		ShifDown(0);
		
		return root;
	}
//下滤过程
	 private void ShifDown(int index) {
		 
		 int half=size>>1;//完全二叉树中,非叶子节点的数量为floor(n/2)
		 E node =elements[index];
		 //index小于第一个叶子节点的下标 
		 while(index < half) {//保证当index有子节点时,才能进入该循环
			 
			 int ChildIndex=(index<<1)+1;//默认取左子节点,
			 E Child =elements[ChildIndex];
			 
			 if((ChildIndex+1) < size) {//如果有右子节点
				int  right=ChildIndex+1;//计算出右孩子节点下标
				if(compare(elements[right], Child)>0) {
				//如果右孩子节点较大,更换Child为右孩子节点
					Child=elements[right];
					ChildIndex=right;
				}
			 }
			 
			 //此时找到个子节点中的较大值
			 if(compare(Child, node)<=0) {//当较大子节点小于或等于node时,
				 break;
			 }
			 //将较大孩子上移,同时index更新
			 elements[index]=Child;
			 index=ChildIndex;
			 
		 }
		 //最后退出循环,将node,放到合适位置
		 elements[index]=node;
	 }

replace方法

在以上各个方法的基础上, replace方法如下:

@Override
	public E replace(E element) {
		elementNotNullCheck(element);
		if(size==0) {
			elements[size++]=element;
			return null;
		}
		//先让element覆盖下标为0的位置,再对该位置进行下滤以恢复性质
		E root = elements[0];
		elements[0]=element;
		ShifDown(0);
		return root;
	}
批量添加方法

对于二叉堆,应该有一个可以批量添加形成二叉堆的方法,传入一个数组,直接以该数组中元素构造二叉堆。
对于批量添加,增加两个构造函数,用于允许传入一个E类型数组。先将传入的数组中所有元素复制到elements中,再调用heapify方法对elements中元素进行调整,使其恢复性质。
heapify对每一个非叶子元素进行从小到上的下滤,该过程所有能够时结果变为符合二叉堆性质的二叉树的原因是:每对一个元素进行下滤之前,其左右子树都已经调整为一棵符合二叉堆性质的二叉树。
heapify也可以采用从上到下的下滤,该过程类似于将数组中元素依次添加到二叉堆中,性能较差。

public BinaryHeap(E[] elements,Comparator<E> comparator) {
		super(comparator);
		if(elements==null||elements.length==0) {
			this.elements=(E[])new Object[DEFAULT_CAPACITY];//创建默认容量的数组			
		}else {
			size=elements.length;
			int capacity=Math.max(DEFAULT_CAPACITY, elements.length);
			this.elements=(E[])new Object[capacity];
			//采用深拷贝,而不用this.elements=elements;的方式,是为了避免用户在外部修改heap
			for(int i=0;i<elements.length;i++) {
				this.elements[i]=elements[i];
			}
			heapify();
			
		}
	}
	public BinaryHeap(E[] elements) {
		super();
		if(elements==null||elements.length==0) {
			this.elements=(E[])new Object[DEFAULT_CAPACITY];//创建默认容量的数组			
		}else {
			size=elements.length;
			int capacity=Math.max(DEFAULT_CAPACITY, elements.length);
			this.elements=(E[])new Object[capacity];
			//采用深拷贝,而不用this.elements=elements;的方式,是为了避免用户在外部修改heap
			for(int i=0;i<elements.length;i++) {
				this.elements[i]=elements[i];
			}
			heapify();
			
		}
	}
//批量添加 自下而上的下滤  效率较高 (n)
	private void heapify() {
			//从最后一个非叶子节点的位置开始
			for(int i=(size>>1)-1;i>=0;i--) {//一定得是i>=0,不能是i>0
				ShifDown(i);
			}
		}
	//批量添加  自上而下的上滤 (nlogn)
	private  void heapify_1() {
		
		for(int i=1;i<size;i++) {
			ShifUp(i);
		}
	}

小顶二叉堆的实现

对于小顶二叉堆,在实现大顶二叉堆的基础上,直接继承大顶二叉堆,重新比较逻辑(在比较方法中更改e1和e2的位置,即元素较小的,认为它较大)。小顶二叉堆的实现所有代码如下:

public class minBinaryHeap<E> extends BinaryHeap<E> {
	
	public minBinaryHeap() {
		super();
	}
	 public minBinaryHeap(E[] elements,Comparator<E> comparator) {
		 super(elements,comparator);
	 }
	 public minBinaryHeap(E[] elements) {
		 super(elements);
	 }
	 public minBinaryHeap(Comparator<E> comparator) {
		 super(comparator);
	 }
//只要重写比较方法,就可以实现小顶二叉堆
	@Override
	protected int compare(E e1, E e2) {
		return comparator!=null?comparator.compare(e2, e1):((Comparable)e2).compareTo(e1);
	}
}

应用:topK问题

问题简单描述:对于大量无序数据,找出最大的前k个数。

处理分析和代码

使用小顶堆,遍历无序数据数组,将前k个元素用add方法添加到小顶二叉堆中,然后对于之后的元素所有元素,如果值大于当前小顶堆堆顶元素,使用replace方法替换堆顶元素。
代码如下:

private static void text4() {
		Integer[] data = {68,72,43,59,38,10,90,19,23,45,42,70,91,85};
		minBinaryHeap<Integer> heap=new minBinaryHeap<>();
		
		int k=4;
		for(int i=0;i<data.length;i++) {
			if(i<k) {
				heap.add(data[i]);
			}else {
				if(data[i]>heap.get()) {
					heap.replace(data[i]);
				}
			}
		}
		System.out.println(heap.toString());
	}
	public static void main(String[] args) {
	
		text4();
	}
运行结果
使用重写的toString方法打印输出:

java中elements类 java中element的方法_数据结构