数据结构之堆(Heap)(一)
堆的概述
什么是堆
堆是一种特殊的基于树的数据结构。树是完全二叉树,实现是数组。
堆的性质
这里的堆,一般指二叉堆,符合下面两种性质的。
- 堆总是一颗完全二叉树。
- 堆总是有序的。即堆的父结点总是大于等于子结点,或者堆的父结点总是小于等于子结点。
所以堆分为两种:最大堆(或大根堆,即堆的父结点总是大于等于子结点) 和 最小堆(或小根堆,即堆的父结点总是小于等于子结点)。
堆的表示
如上图,示例了一个最大堆和一个最小堆。下面以最大堆来说明,堆如何表示的。
如下图,就是最大堆的数组存储的示意。
那么如何能知道结点的关系呢?哪个是父结点、子结点呢?
这就与完全二叉树的特点有关了。在数据结构之树(Tree)(一)_树的基础中也介绍了二叉树和完全二叉树的特点(注意树中介绍的特点是从1开始编号的,这里从0开始。),不了解可以先查看参考下。
所以有(Arr[]表示存储的数组,如上图):
//根结点即第一个元素
root = Arr[0];
//结点i的 父结点
parent = Arr[(i-1)/2];
//结点i的 左孩子结点
leftChild = Arr[(2*i)+1];
//结点i的 右孩子结点
rightChild = Arr[(2*i)+2];
注:结点i的 父结点(i-1)/2,因为int/int是取整,其实相当于Math.floor((i-1)/2)。
堆的操作
这里还是以最大堆为例说明
堆的主要操作:
- getMax():即获取最大堆的根结点,也就是数组第一个元素。时间复杂度O(1)。
- insert():插入一个新元素。插入过程和操作也很好理解,在二叉树的末尾(即最后一层最后一个结点右边位置)插入,插入完成后主要是调整结点使所有结点满足最大堆的属性。插入结点与父结点比较,如果比父结点大,则与父结点互换位置,依次类推,直到父结点比它大或者已交换至根结点。这个时间复杂度为O(logn)。
- delete():删除某个元素。这个过程是:删除某个结点元素,然后用最后一个叶子结点替换到删除位置,这样树仍是完整的 但不符合最大堆属性。所以删除替换后,对堆进行maxHeapify()操作,调整以删除结点为根结点的子树,使所有结点符合最大堆要求即可。这个时间复杂度为O(logn)。
过程不复杂也很容易理解,就不用示意图显示了。
下面是编写的一个最大堆的操作demo,大家可以参考了解下过程。
public class MaxHeap {
private int[] heapArr;
private int heapSize;
private int maxsize;
public MaxHeap(int maxsize) {
this.maxsize = maxsize;
this.heapSize = 0;
heapArr = new int[this.maxsize];
}
//父结点位置 数组下标
private int parent(int pos) {
return (pos - 1) / 2;
}
//左孩子结点位置 数组下标
private int leftChild(int pos) {
return (2 * pos) + 1;
}
//右孩子结点位置 数组下标
private int rightChild(int pos) {
return (2 * pos) + 2;
}
//交换位置
private void swap(int fpos, int spos) {
int tmp = heapArr[fpos];
heapArr[fpos] = heapArr[spos];
heapArr[spos] = tmp;
}
//插入:末尾插入,然后调整结点使符合最大堆属性
public void insert(int element) {
int current = heapSize;
heapArr[heapSize++] = element;
while (heapArr[current] > heapArr[parent(current)]) {
swap(current, parent(current));
current = parent(current);
}
}
//删除元素
public int delete(int pos) {
int tmp = heapArr[pos];
heapArr[pos] = heapArr[heapSize-1];
heapSize--;
maxHeapify(pos);
return tmp;
}
//删除堆的最大元素,即最大堆的根结点
// public int extractMax() {
// int root = heapArr[0];
// heapArr[0] = heapArr[heapSize--];
// maxHeapify(0);
// return root;
// }
// Returns true of given node is leaf
private boolean isLeaf(int pos) {
if (pos > (heapSize/2 - 1) && pos <= (heapSize-1)) {
return true;
}
return false;
}
//递归调整某结点为根结点的子树,是符合堆的属性。
private void maxHeapify(int pos) {
if (isLeaf(pos))
return;
if (heapArr[pos] < heapArr[leftChild(pos)] || heapArr[pos] < heapArr[rightChild(pos)]) {
if (heapArr[leftChild(pos)] > heapArr[rightChild(pos)]) {
swap(pos, leftChild(pos));
maxHeapify(leftChild(pos));
} else {
swap(pos, rightChild(pos));
maxHeapify(rightChild(pos));
}
}
}
private void printHeap() {
System.out.print("size=" + heapSize + ";Heap={");
for (int i = 0; i < heapSize; i++) {
System.out.print(heapArr[i]);
if (i < heapSize - 1) {
System.out.print(",");
}
}
System.out.print("}");
System.out.println();
}
public static void main(String[] arg) {
//创建堆并插入数据,形成了开始示意图中最大堆的样子。
MaxHeap maxHeap = new MaxHeap(15); maxHeap.printHeap();
maxHeap.insert(8);
maxHeap.insert(9); maxHeap.printHeap();
maxHeap.insert(6);
maxHeap.insert(5); maxHeap.printHeap();
maxHeap.insert(3);
maxHeap.insert(1); maxHeap.printHeap();
//删除堆顶
maxHeap.delete(0); maxHeap.printHeap();
//删除了编号为1的位置
maxHeap.delete(1); maxHeap.printHeap();
}
}
很容易理解。下面是打印的信息,与预期一致:
size=0;Heap={}
size=2;Heap={9,8}
size=4;Heap={9,8,6,5}
size=6;Heap={9,8,6,5,3,1}
size=5;Heap={8,5,6,1,3}
size=4;Heap={8,3,6,1}
善始者实繁,克终者盖寡。
---不足或不对的地方欢迎指正。