欲望以提升热忱,毅力以磨平高山。
什么是堆?
堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可被看做一棵树的数组对象。这里介绍的是最为常见的二叉堆。
二叉堆(Binary Heap)是一种特殊的堆,二叉堆是完全二元树并且总是满足下列性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
二叉堆有两种:最大堆和最小堆。
- 最大堆:父结点的键值总是大于或等于任何一个子节点的键值;
- 最小堆:父结点的键值总是小于或等于任何一个子节点的键值。
二叉堆的数组表示法
如果对于一个二叉堆从上到下、从左至右依次从1开始编号,并将每个节点的编号作为数组的下标,那么就能得到一个用数组表示的二叉堆。采用数组表示法有以下优点:
- 存储密度高
- 能够轻易获得某个节点的双亲和左右孩子结点
根据二叉树的性质:下标为 i(i>1)处节点的双亲节点下标为 i/2(整除),下标为 i 处节点的做孩子节点下标为 2 * i ;右孩子节点下标为 2 * i + 1。
为了充分利用数组空间,我们可以从 0 开始编号,那么下标为 i(i>0)处节点的双亲节点下标为 ( i - 1 ) / 2(整除),下标为 i 处节点的做孩子节点下标为 2 * i + 1 ;右孩子节点下标为 2 * i + 2。
大顶堆实现
基于以前实现的动态数组,这里直接复用SeqList实现大顶堆
数据结构:线性数据结构
大顶堆的向上调整siftUp和向下调整siftDown
siftUp
向大顶堆中添加元素时,由于我们是基于数组实现的,因此可直接在数组末尾添加该元素。但是添加完元素后的堆并不一定满足大顶堆的性质,因此还需要进行大顶堆的调整(siftUp)。
下面展示了添加元素的过程:
在数组尾部添加52
向上调整,52 大于 16
向上调整,52 大于 41
52小于62 因此不需要再调整,此时就满足大顶堆的性质了。
向大顶堆中添加元素:
//从index处向上调整
private void siftUp(int index) {
//index > 0 并且 index处的值大于双亲的值 则进行向上调整为大顶堆
while (index > 0 && data.getIndexOf(index).compareTo(data.getIndexOf(parent(index))) > 0) {
data.swap(index, parent(index));
index = parent(index);
}
}
//向大顶堆中添加元素
public void add(E e){
data.addLast(e);
siftUp(data.getSize() - 1);
}
由于需要交换数组中的值,因此在SeqList中添加swap方法:
//交换下标为 i 和 j 下标的值
public void swap(int i, int j) {
if (i < 0 || i >= size) {
throw new IllegalArgumentException(" index is illegal !");
}
if (j < 0 || j >= size) {
throw new IllegalArgumentException(" index is illegal !");
}
T temp = data[i];
data[i] = data[j];
data[j] = temp;
}
siftDown
需要取出大顶堆的最大元素时,我们可以将数组中最后一个元素放到堆顶,然后进行堆的向下调整。下面展示了取出大顶堆的最大元素的过程:
将数组最后一个元素放到堆顶。
如果待调整的元素小于它的孩子节点中的最大元素,那么就和最大元素交换。
16 小于 52 ,因此 16 和 52 发生了交换。
16 小于41 所以交换。
16 大于 15 因此不用再向下调整了,此时的堆满足大顶堆的性质。
取出最大元素
//从index处向下调整大顶堆
private void siftDown(int index) {
while (index * 2 + 1 < data.getSize()) {
int maxIndex = leftChild(index);
if (maxIndex + 1 < data.getSize() && data.getIndexOf(rightChild(index)).compareTo(data.getIndexOf(leftChild(index))) > 0) {
maxIndex = rightChild(index);
}//此时maxIndex为孩子元素中最大的元素
//若index处的元素小于maIndex的元素则交换 否则结束
if (data.getIndexOf(index).compareTo(data.getIndexOf(maxIndex)) < 0) {
data.swap(index, maxIndex);
index = maxIndex;
}else {
break;
}
}
}
//取出最大元素
public E extractMax() {
E ret = getMax();
//将最后一个元素放到堆顶 向下调整大顶堆
data.set(0, data.removeLast());
siftDown(0);
return ret;
}
//取出最大元素,并用e替换
public E replace(E e){
E ret = getMax();
data.set(0, e);
siftDown(0);
return ret;
}
将数组转化为大顶堆Heapify
将数组转化为大顶堆可以在遍历数组的时候调用上面的add方法,但是如果在原来数组上进行调整可以减少近半的次数,因为我们可以从第一个非叶子结点开始向下调整。
//构造方法:Heapify 将一个数组转化为大顶堆
public MaxBinaryHeap(E[] arr) {
data = new SeqList<E>(arr);
//从第一个非叶子结点开始,依次向下调整大顶堆
for (int i = parent(arr.length - 1); i >=0 ; i--) {
siftDown(i);
}
}
同时由于我们底层是用的自己实现的SeqList,因此还需在SeqList添加构造方法:
//将数组转化为动态数组
public SeqList(T[] arr) {
data = (T[]) new Object[arr.length];
for (int i = 0; i < arr.length; i++) {
data[i] = arr[i];
}
size = arr.length;
}
完整代码
到此,二叉大顶堆基本就实现了,需要注意的是,在实现大顶堆的相关操作时,对以前实现的SeqList增添了一个swap方法和一个构造方法。
MaxBinaryHeap
package cn.boom.heap;
public class MaxBinaryHeap<E extends Comparable<E>> {
private SeqList<E> data;
public MaxBinaryHeap(int capacity) {
this.data = new SeqList<E>(capacity);
}
public MaxBinaryHeap() {
this.data = new SeqList<E>();
}
//Heapify 将一个数组转化为大顶堆
public MaxBinaryHeap(E[] arr) {
data = new SeqList<E>(arr);
//从第一个非叶子结点开始,依次向下调整大顶堆
for (int i = parent(arr.length - 1); i >=0 ; i--) {
siftDown(i);
}
}
//获取元素个数
public int getSize() {
return data.getSize();
}
//非空判断
public boolean isEmpty() {
return data.isEmpty();
}
//获取参数下标对应的双亲下标
private int parent(int index) {
if (index < 0 || index >= data.getSize()) {
throw new IllegalArgumentException("index = " + index + " , index is illegal !");
}
if (index == 0) {
throw new IllegalArgumentException(" index = 0 , no parent !");
}
return ( index - 1 ) / 2;
}
//获取参数下标对应的左孩子下标
private int leftChild(int index) {
if (index < 0 || index >= data.getSize()) {
throw new IllegalArgumentException("index = " + index + " , index is illegal !");
}
if ( 2 * index + 1 >= data.getSize() ) {
throw new IllegalArgumentException(" index = " + index +" , no leftChild !");
}
return 2 * index + 1;
}
//获取参数下标对应的右孩子下标
private int rightChild(int index) {
if (index < 0 || index >= data.getSize()) {
throw new IllegalArgumentException("index = " + index + " , index is illegal !");
}
if ( 2 * index + 2 >= data.getSize() ) {
throw new IllegalArgumentException(" index = " + index +" , no rightChild !");
}
return 2 * index + 2;
}
//从index处向上调整
private void siftUp(int index) {
//index > 0 并且 index处的值大于双亲的值 则进行向上调整为大顶堆
while (index > 0 && data.getIndexOf(index).compareTo(data.getIndexOf(parent(index))) > 0) {
data.swap(index, parent(index));
index = parent(index);
}
}
//向大顶堆中添加元素
public void add(E e){
data.addLast(e);
siftUp(data.getSize() - 1);
}
//获取最大元素
public E getMax() {
if (data.getSize() == 0) {
throw new IllegalArgumentException("No data , No MaxValue ! ");
}
return data.getIndexOf(0);
}
//从index处向下调整大顶堆
private void siftDown(int index) {
while (index * 2 + 1 < data.getSize()) {
int maxIndex = leftChild(index);
if (maxIndex + 1 < data.getSize() && data.getIndexOf(rightChild(index)).compareTo(data.getIndexOf(leftChild(index))) > 0) {
maxIndex = rightChild(index);
}//此时maxIndex为孩子元素中最大的元素
//若index处的元素小于maIndex的元素则交换 否则结束
if (data.getIndexOf(index).compareTo(data.getIndexOf(maxIndex)) < 0) {
data.swap(index, maxIndex);
index = maxIndex;
}else {
break;
}
}
}
//取出最大元素
public E extractMax() {
E ret = getMax();
//将最后一个元素放到堆顶 向下调整大顶堆
data.set(0, data.removeLast());
siftDown(0);
return ret;
}
//取出最大元素,并用e替换
public E replace(E e){
E ret = getMax();
data.set(0, e);
siftDown(0);
return ret;
}
@Override
public String toString() {
return "MaxBinaryHeap{" +
"data=" + data +
'}';
}
}
修改后的SeqList
package cn.boom.heap;
public class SeqList<T> {
private T[] data;
private int size;
//无参构造
public SeqList(){
data = (T[]) new Object[10];
size = 0;
}
//带参构造 :参数 容量
public SeqList(int capacity) {
data = (T[]) new Object[capacity];
size = 0;
}
public SeqList(T[] arr) {
data = (T[]) new Object[arr.length];
for (int i = 0; i < arr.length; i++) {
data[i] = arr[i];
}
size = arr.length;
}
/**
* 获取真实长度(数据个数)
* @return size
*/
public int getSize(){
return this.size;
}
/**
* 获取index索引位置的元素
* @param index
* @return data[index]
* @throws IllegalArgumentException 参数不合法异常
*/
public T getIndexOf(int index) throws IllegalArgumentException{
//参数合法性校验
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Illegal Index !");
}
return this.data[index];
}
//修改值
public void set(int index, T elem) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Illegal Index !");
}
data[index] = elem;
}
/**
* 按值查找 返回elem第一次出现的下标 未找到返回-1
* @param elem
* @return index
*/
public int locationElem(T elem){
for (int i = 0; i < this.size; i++) {
if (elem.equals(this.data[i])) {
return i;
}
}
return -1;
}
//是否存在元素
public boolean contains(T elem){
return locationElem(elem) != -1;
}
//表是否为空
public boolean isEmpty(){
return (size == 0);
}
//表是否为空
public boolean isFull(){
return (size == data.length);
}
//获取容量
public int getCapacity(){
return data.length;
}
//在 index处插入一个元素
public void add(int index, T elem) {
//参数合法性校验
if (index < 0 || index > size) {
throw new IllegalArgumentException("Illegal Index ! index is " + index);
}
if (isFull()) {
updateCapacity(data.length * 2);
}
//在 index 后的数据后移
for (int i = size; i > index; i--) {
this.data[i] = this.data[i - 1];
}
this.data[index] = elem;
size++;
}
//在数组首部添加元素
public void addFirst(T elem) {
add(0, elem);
}
//在数组尾部添加元素
public void addLast( T elem){
add(this.size,elem);
}
//更新数组容量
public void updateCapacity(int newCapacity) {
T[] newArray = (T[]) new Object[newCapacity];
for (int i = 0; i < size; i++) { // copy原数组中的数据
newArray[i] = data[i];
}
this.data = newArray;
}
//删除下标为index的元素并返回
public T remove(int index){
if (index < 0 || index > size) {
throw new IllegalArgumentException("Illegal Index ! index is " + index);
}
T elem = data[index];
for (int i = index; i < size - 1; i++) {
data[i] = data[i + 1];
}
size--;
if (size < data.length / 2) { //缩容
updateCapacity(data.length / 2);
}
return elem;
}
//删除第一个元素
public T removeFirst() {
return remove(0);
}
//删除最后一个元素
public T removeLast() {
return remove(size - 1);
}
//交换下标为 i 和 j 下标的值
public void swap(int i, int j) {
if (i < 0 || i >= size) {
throw new IllegalArgumentException(" index is illegal !");
}
if (j < 0 || j >= size) {
throw new IllegalArgumentException(" index is illegal !");
}
T temp = data[i];
data[i] = data[j];
data[j] = temp;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("SeqList{");
res.append(" data=[");
for (int i = 0; i < this.size; i++) {
res.append(this.data[i].toString());
if (i != size - 1) {
res.append(',');
}
}
res.append("], size=" + size + ", capacity=" + getCapacity() + " }");
return res.toString();
}
}
基于二叉堆实现优先队列
普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。
由于二叉大顶堆的堆顶是堆中最大的元素,基于这个特点,我们很方便的就能基于二叉堆来实现优先队列这种数据结构。
package cn.boom.queue;
/**
* 优先队列
* @param <E>
*/
public class priorityQueue<E extends Comparable<E>> implements Queue<E> {
private MaxBinaryHeap<E> maxBinaryHeap;
public priorityQueue() {
this.maxBinaryHeap = new MaxBinaryHeap<E>();
}
@Override
public int getSize() {
return maxBinaryHeap.getSize();
}
@Override
public boolean isEmpty() {
return maxBinaryHeap.isEmpty();
}
@Override
public void enQueue(E e) {
maxBinaryHeap.add(e);
}
@Override
public E deQueue() {
return maxBinaryHeap.extractMax();
}
@Override
public E getFront() {
return maxBinaryHeap.getMax();
}
}