什么是二叉堆?
二叉堆本质上是一种完全二叉树,它分为两个类型:
1.最大堆
2.最小堆
什么是最大堆呢?最大堆任何一个父节点的值,都大于等于它左右孩子节点的值。相反,最小堆就是任何一个父节点的值,都小于等于它左右孩子节点的值。
二叉堆的根节点叫做堆顶。
最大堆和最小堆的特点,决定了在最大堆的堆顶是整个堆中的最大元素;最小堆的堆顶是整个堆中的最小元素。
堆的自我调整
对于二叉堆,如下有几种操作:
插入节点
删除节点
构建二叉堆
这几种操作都是基于堆的自我调整。
下面让我们以最小堆为例,看一看二叉堆是如何进行自我调整的。
1.插入节点
二叉堆的节点插入,插入位置是完全二叉树的最后一个位置。比如我们插入一个新节点,值是 0。
这时候,我们让节点0的它的父节点5做比较,如果0小于5,则让新节点“上浮”,和父节点交换位置。
继续之前的上浮操作直到堆顶
2.删除节点
二叉堆的节点删除过程和插入过程正好相反,所删除的是处于堆顶的节点。比如我们删除最小堆的堆顶节点1。
这时候,为了维持完全二叉树的结构,我们把堆的最后一个节点10补到原本堆顶的位置。
接下来我们让移动到堆顶的节点10和它的左右孩子进行比较,如果左右孩子中最小的一个(显然是节点2)比节点10小,那么让节点10“下沉”。
继续让节点10和它的左右孩子做比较,左右孩子中最小的是节点7,由于10大于7,让节点10继续“下沉”。
堆的代码实现
二叉堆虽然是一颗完全二叉树,但它的存储方式并不是链式存储,而是顺序存储。换句话说,二叉堆的所有节点都存储在数组当中。
数组中,在没有左右指针的情况下,如何定位到一个父节点的左孩子和右孩子呢?
像图中那样,我们可以依靠数组下标来计算。
假设父节点的下标是parent,那么它的左孩子下标就是 2parent+1;它的右孩子下标就是 2parent+2 。
比如上面例子中,节点6包含9和10两个孩子,节点6在数组中的下标是3,节点9在数组中的下标是7,节点10在数组中的下标是8。
7 = 32+1
8 = 32+2
刚好符合规律。
用java实现的动态数组
Array
public class Array<E> {
private E[] data;
private int size;
public Array(int capacity) {
data = (E[]) (new Object[capacity]);
size = 0;
}
public Array() {
this(10);
}
public int getSize() {
return size;
}
public int getCapacity() {
return data.length;
}
public boolean isEmpty() {
return size == 0;
}
public void addLast(E e) {
add(size, e);
}
public void addFirst(E e) {
add(0, e);
}
public E get(int index) {
if (index < 0 || index >= size)
throw new IllegalArgumentException("add Failed,size is full");
return data[index];
}
public E getLast() {
return get(size-1);
}
public E getFirst() {
return get(0);
}
public void set(int index, E e) {
if (index < 0 || index >= size)
throw new IllegalArgumentException("add Failed,size is full");
data[index] = e;
}
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("add Failed,size is full");
}
if (size == data.length) {
resize(2 * data.length);
}
for (int i = size - 1; i >= index; i--) {
data[i + 1] = data[i];
}
data[index] = e;
size++;
}
public void resize(int newCap) {
E[] newData = (E[]) (new Object[newCap]);
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
data = newData;
}
public boolean contains(E e) {
for (int i = 0; i < size; i++) {
if (data[i].equals(e))
return true;
}
return false;
}
public int find(E e) {
for (int i = 0; i < size; i++) {
if (data[i].equals(e))
return i;
}
return -1;
}
public E remove(int index) {
if (index < 0 || index > size)
throw new IllegalArgumentException("add Failed,size is full");
E res = data[index];
for (int i = index + 1; i < size; i++) {
data[i - 1] = data[i];
}
data[size] = null;
size--;
if (size == data.length / 4 && data.length / 2 != 0)
resize(data.length / 2);
return res;
}
public E removeFirst() {
return remove(0);
}
public E removeLast() {
return remove(size - 1);
}
public void removeElement(E e) {
int index = find(e);
if (index != -1) {
remove(index);
}
}
public void swap(int i,int j){
if(i<0||i>=size||j<0||j>=size){
throw new IllegalArgumentException("index is illegal");
}
E temp=data[i];
data[i]=data[j];
data[j]=temp;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("Array:size=%d,capacity=%d\n", size, data.length));
res.append('[');
for (int i = 0; i < size; i++) {
res.append(data[i]);
if (i != size - 1) {
res.append(',');
}
}
res.append(']');
return res.toString();
}
}
MaxHeap
import java.util.Random;
public class MaxHeap<E extends Comparable<E>> {
private Array<E> data;
public MaxHeap(int capcity) {
data = new Array<>(capcity);
}
public MaxHeap() {
data = new Array<>();
}
//任意数组变成MaxHeap
public MaxHeap(E[] arr){
data=new Array<>(arr);
for (int i=parent(arr.length-1);i>=0;i--){
siftDown(i);
}
}
public int size() {
return data.getSize();
}
public boolean isEmpty() {
return data.isEmpty();
}
//返回该节点的父亲节点的索引
private int parent(int index) {
if (index == 0) {
throw new IllegalArgumentException("index=0 has not parent");
}
return (index - 1) / 2;
}
//返回左孩子所在的节点索引
private int leftChild(int index) {
return index * 2 + 1;
}
private int rightChild(int index) {
return index * 2 + 2;
}
//向堆中添加元素
public void add(E e) {
data.addLast(e);
siftUp(data.getSize() - 1);
}
private void siftUp(int k) {
while (k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0) {
data.swap(k, parent(k));
k = parent(k);
}
}
public E findMax(){
if(data.getSize()==0){
throw new IllegalArgumentException("can not findMax when heap is empty");
}
return data.get(0);
}
public E extractMax(){
E ret=findMax();
data.swap(0,data.getSize()-1);
data.removeLast();
siftDown(0);
return ret;
}
private void siftDown(int k){
while (leftChild(k)<data.getSize()){
int j=leftChild(k);
if(j+1<data.getSize()&&data.get(j+1).compareTo(data.get(j))>0){
j=rightChild(k);
}
//data[j]是leftChild和rightChild中的较大值
if(data.get(k).compareTo(data.get(j))>=0){
break;
}else{
data.swap(k,j);
k=j;
}
}
}
//用元素e来替换最大元素
public E replace(E e){
E ret=findMax();
data.set(0,e);
siftDown(0);
return ret;
}
private static double testMaxHeap(Integer[] testData,boolean isHeapify){
long startTime=System.nanoTime();
MaxHeap<Integer> maxHeap;
if(isHeapify){
maxHeap=new MaxHeap<>(testData);
}else{
maxHeap=new MaxHeap<>();
for(int num:testData){
maxHeap.add(num);
}
}
// int[] arr=new int[testData.length];
// for(int i=0;i<testData.length;i++){
// arr[i]=maxHeap.extractMax();
// }
// for (int i=1;i<testData.length;i++){
// if(arr[i-1]<arr[i]){
// throw new IllegalArgumentException("error");
// }
// }
System.out.println("MaxHeap is complete");
long endTime=System.nanoTime();
return (endTime-startTime)/1000000000.0;
}
public static void main(String[] args) {
int n=1000000;
Integer[] testData=new Integer[n];
Random random=new Random();
MaxHeap<Integer> maxheap=new MaxHeap<>();
for(int i=0;i<n;i++){
testData[i]=random.nextInt(Integer.MAX_VALUE);
}
double time1=testMaxHeap(testData,false);
System.out.println("maxHeap without heapify"+time1+"s");
double time2=testMaxHeap(testData,true);
System.out.println("maxHeap without heapify"+time2+"s");
}
}