概念
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
1.堆中某个节点的值总是不大于或不小于其父节点的值;
2.堆总是一棵完全二叉树。
性质一,就会使得堆产生两种形式“大根堆” “小根堆”,性质二呢,就使得堆是可以顺序存储的,即堆逻辑上表现为一棵二叉树,存储结构是顺序结构:
堆的创建
堆默认为小根堆,在我们使用堆时,会默认将我们提供的数组,调整为小根堆,采取的是向下调整,例如如下这一组数据,
对应的堆的逻辑结构是下面这颗二叉树
显然他不符合小根堆的定义,我们从最后一个元素开始调整,9下标和4下标的数进行比较,若4小标的大,则两者交换位置,若4下标小,我们开始调整3下标的子树,我们比较3下标节点的左右孩子,得到最小值,与3下标比较,若3下标的值大,3下标的值和左右节点的最小值交换位置,交换位置后,我们去比较7下标所在的子树,因为交换位置后,可能会产生子树不满足小根堆的条件。接下来我们来看代码:
public class MyHeap {
public int[] elem;
public int useSize;
public MyHeap() {
this.elem = new int[10];
}
public void init(int[] arr){
for (int i = 0; i < arr.length; i++) {
elem[i] = arr[i];
useSize++;
}
}
public void createHeap(){
for (int parent = (useSize - 1 - 1)/2; parent >= 0; parent--) {
shift(parent,useSize);
}
}
public void shift(int parent,int len){
int child = 2*parent + 1;
while (child < len){
if (child+1< useSize && elem[child] > elem[child + 1]){
child++;
}
if (elem[parent] > elem[child]) {
swap(parent,child);
parent = child;
child = 2*parent +1;
}else {
break;
}
}
}
public void swap(int parent,int child){
int tmp = elem[parent];
elem[parent] = elem[child];
elem[child] = tmp;
}
}
我们观察调整后的顺序
他的逻辑结构就是下图:
我们发现,调整后的堆满足小根堆的条件。
堆的插入
再上图中插入一个元素,那么应该向哪里插入呢?
首先我们得明确的一点是,堆是一个完全二叉树,那么堆只能在有效数据之后插入,即在4下标的有孩子处插入。
对于堆的插入,我们应当注意一下两点:
1.若此时数组空间已满,我们需要扩容,那么应当怎样扩容呢?
我们这里以数组长度的1.5进行扩容,而在优先级队列中的扩容策略是
将新元素插入数组的尾部、若数组已满、则进行扩容、小于64时2倍扩容、大于等于64、则1.5倍、若超过Integer-8则按、数组最大值来扩容。
2.插入时,旧的堆已经是一个小根堆,那么我们只需要,用新的元素与其父亲节点进行比较即可。
public void offer(int val){
if(isFull()){
elem = Arrays.copyOf(elem,2*elem.length);
}
elem[useSize++] = val;
}
public void shiftUp(int child){
int parent = (child - 1)/2;
while (child > 0){
if (elem[child] < elem[parent]) {
swap(child,parent);
child = parent;
parent = (child - 1)/2;
}else {
break;
}
}
}
public boolean isFull(){
return useSize == elem.length;
}
堆的删除
堆的删除思想就是,堆顶元素,和堆尾元素进行交换,交换完成后,堆的有效长度减一,除过堆尾元素外,其余元素,进行调整,调整尾小根堆。
我们来看代码:
public void Pop(){
if (isEmpty()){
return;
}
swap(useSize,0);
useSize--;
shift(0,useSize);
}
public boolean isEmpty(){
return useSize == 0;
}
堆的注意事项
堆的注意事项:
1.堆不能插入null对象、否则会抛出NullPointterException;
2.没有容量限制、可以自动扩容
3.删除时总是从根节点删除
4.默认为最小堆,
5.堆的最底层数据存储使用的是数组
6.插入和删除的时间发复杂度都是O(log2N)
7.PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出 ClassCastException异常。