堆就是用数组实现的二叉树,所有它没有使用父指针或者子指针。堆根据“堆属性”来排序,“堆属性”决定了树中节点的位置。
堆的常用方法:
- 构建优先队列
- 支持堆排序
- 快速找出一个集合中的最小值(或者最大值)
堆属性
堆分为两种:最大堆和最小堆,两者的差别在于节点的排序方式。
在最大堆中,父节点的值比每一个子节点的值都要大。在最小堆中,父节点的值比每一个子节点的值都要小。这就是所谓的“堆属性”,并且这个属性对堆中的每一个节点都成立。
例子:
来自数组的树
堆的下标属性
如果 i
是节点的索引,那么下面的公式就给出了它的父节点和子节点在数组中的位置
parent(i) = floor((i - 1)/2)
left(i) = 2i + 1
right(i) = 2i + 2
parent(i) =floor((i-1)/2)
left(i) =2i+1
right(i)=2i+2
我们将写公式放到前面的例子中验证一下。
Node | Array index ( | Parent index | Left child | Right child |
10 | 0 | -1 | 1 | 2 |
7 | 1 | 0 | 3 | 4 |
2 | 2 | 0 | 5 | 6 |
5 | 3 | 1 | 7 | 8 |
1 | 4 | 1 | 9 | 10 |
堆的基本操作:
堆一般用数组表示
1.插入(Insert):
以最大堆为栗子,插入其实就是把插入结点放在堆最后面,然后与父亲比较,如果父亲值小于它,那么它就和父亲结点交换位置,重复该过程,直到插入节点遇到一个值比它大的父亲或者它成为树根结点
以最大堆为栗子,在堆中插入值为20的结点(红色结点代表新进入)
20明显大于它的父亲结点值,所以和7交换位置,交换后新的父亲值还是比它小,继续交换
一步一步与它前面比它小的父亲结点交换位置,最后20成为根结点
最小堆同理,只不过要求父亲结点要比它小,如果父亲结点大于它,就得交换
2.删除(Pop):
删除就是删除最大堆中的最大值或者最小堆中的最小值,也就是树根
以删除最大堆树根为例子,删除其实就是整个堆中少了一个结点,不妨把位于最后面的一个结点当成新的树根,然后与它左右孩子比较,与值最大并且值大于它的孩子进行交换(好好读这句话),直到它的孩子都是小于它的,或者变成树叶
还是用最大堆为例,删除树根20
把最后的结点8提到树根
然后就按说明步骤,找到一个位置,它的孩子都小于它或者他变成树叶
堆代码实现:
//最大堆
public class MaxHeap {
private int[] data;//存放堆数据的数组
private int size; //当前堆的大小
private int capacity; //堆的最大容量
public MaxHeap(int size) {
data = new int[size];
this.size = 0;
this.capacity = size;
}
/**
* 获取某个结点的父结点索引
*
* @param index
* @return
*/
private int parent(int index) {
if (index == 0) {
throw new RuntimeException("根结点没有父结点");
}
return (index - 1) / 2;
}
/**
* 获取某个结点的左孩子索引
*
* @param index
* @return
*/
private int lchild(int index) {
return (2 * index) + 1;
}
/**
* 获取某个结点的右孩子索引
*
* @param index
* @return
*/
private int rchild(int index) {
return (2 * index) + 2;
}
//插入元素
public void insert(int d) {
if (size == capacity) {
System.out.println("堆已满");
return;
}
data[size] = d;
shiftUp(size);
size++;
}
//移除元素
public int removeMax() {
if (size == 0) {
System.out.println("堆已经是空的了!");
return -1;
}
int t = data[0];
//将最后一个元素放到第一个元素位置
data[1] = data[size];
//然后将第一个元素下移到适当位置
shiftDown(1);
size--;
return t;
}
//堆删除元素时的元素下移
private void shiftDown(int i) {
while ((2 * i + 1) <= size) {
int j = i * 2 + 1;
// 让j指向他的孩子结点中的大的那一个
if (j + 1 <= size && data[j] < data[j + 1]) {
j = j + 1;
}
if (data[i] > data[j]) {
break;
}
//元素下移
int t=data[i];
data[i]=data[j];
data[j]=t;
i=j;
}
}
private void shiftUp(int index) {
while (index > 0 && data[index] > data[(index - 1) / 2]) {
int temp = data[index];
data[index] = data[(index - 1) / 2];
data[(index - 1) / 2] = temp;
index = (index - 1) / 2;
}
}
}