目录
一、堆(heap)
1.1 基本概念
堆:堆在逻辑上就是一颗完全二叉树,物理上是保存在数组中。
特点:
- 最大堆/大根堆:堆中根节点的值大于等于子树中的节点值。
- 最小堆/小根堆:堆中根节点的值小于等于子树中的节点值。
- 在最大堆中,只能保证当前根节点大于等于子树的所有节点,但是节点的大小关系和所处的层次无关。
- 堆的基本作用就是快速找到集合中的最值。
使用顺序表存储完全二叉树,节点的索引和节点的关系如下(根节点从0开始编号,此节点为k):
①左子树的索引为:2*k+1
②右子树的索引为:2*k+2
③父节点索引为:(k-1)/2
④判断k节点是否存在子树:(2*k+1)<数组长度
二、堆的基本操作
2.1 向最大堆中添加一个元素
向最大堆中添加一个新元素:
- 直接在数组的末尾添加新元素,在结构上保证在添加后该二叉树仍然为完全二叉树
- 添加完成后,需要调整元素的位置,使这颗二叉树满足最大堆的性质
调整元素的方法为:siftUp(int k):将数组最后一个元素向上调整,直到当前节点值小于父节点值或者此时k已经使根节点。
代码如下:
/**
* 添加元素
* @param val
*/
public void addNode(int val){
//先将元素添加到末尾
data.add(val);
//然后进行上浮操作
siftUp(data.size()-1);
}
/**
* 元素上浮
* @param k
*/
private void siftUp(int k) {
//当当前节点不是根节点并且当前节点的值大于父节点的值
while (k > 0 && data.get(k) > data.get(parent(k))){
//交换当前节点和它的父节点的值
swap(k,parent(k));
k=parent(k);
}
}
private void swap(int k, int p) {
int tmp=data.get(k);
data.set(k,data.get(p));
data.set(p,tmp);
}
2.2 取出堆中最大元素
取出最大堆中的最大值:就是取出根节点的值
- 直接取出根节点的值
- 删除堆顶元素
删除堆顶元素的方法:siftDown(int k):取出堆顶元素后,直接将数组末尾的元素顶到堆顶,然后删除数组末尾元素,开始进行元素下沉操作。
下沉的终止条件为:
- 到达叶子节点时( 2*k+1>size )
- 当前节点值大于其左右子树节点
代码如下:
/**
* 取出最大值
* @return
*/
public int extractMax(){
if (isEmpty()){
throw new NoSuchElementException("heap is empty!");
}
//最大值就为堆顶元素
int max=data.get(0);
//将堆顶元素修改成最后一个元素
data.set(0,data.get(data.size()-1));
//删除最后一个元素
data.remove(data.size()-1);
siftDown(0);
return max;
}
/**
* 元素下沉
* @param k
*/
private void siftDown(int k) {
//此时k存在子树
while(left(k)<data.size()){
//取得左子树
int i=left(k);
//判断右子树是否存在并且右子树是否大于左子树
if (i+1<data.size()&&data.get(i+1)>data.get(i)){
//如果条件满足,此时i就是右子树的索引
i=i+1;
}
//当前节点值大于左右子树的值
if (data.get(k)>=data.get(i)){
break;
}else{
swap(k,i);
k=i;
}
}
}
2.3 堆化(heapify)
堆化:就是将任意数组调整为堆的结构。
- 任意数组都可以看做一颗完全二叉树
- 从当前这个完全二叉树的最后一个非叶子节点开始进行元素下沉(siftDown)操作,逐步将这颗二叉树调整为堆结构
最后一个非叶子节点就是最后一个节点的父节点:parent(data.size()-1)。
代码如下:
/**
* 堆化
* @param arr
*/
public MaxHeap(int[] arr){
data=new ArrayList<>(arr.length);
//先将arr的所有元素复制到data数组中
for (int i:arr){
data.add(i);
}
//从最后一个非叶子节点开始进行siftDown
for (int i = parent(data.size()-1); i >=0 ; i--) {
siftDown(i);
}
}
三、源代码
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
/**
* 基于数组实现最大堆
*/
public class MaxHeap {
//使用list存储最大堆
List<Integer> data;
//堆的大小
int size;
public MaxHeap(){
this(10);
}
public MaxHeap(int size){
data=new ArrayList<>(size);
}
/**
* 查看最大值
* @return
*/
public int peekMax(){
if (isEmpty()){
throw new NoSuchElementException("peek is empty!cannot peek!");
}else{
return data.get(0);
}
}
/**
* 堆化
* @param arr
*/
public MaxHeap(int[] arr){
data=new ArrayList<>(arr.length);
//先将arr的所有元素复制到data数组中
for (int i:arr){
data.add(i);
}
//从最后一个非叶子节点开始进行siftDown
for (int i = parent(data.size()-1); i >=0 ; i--) {
siftDown(i);
}
}
/**
* 取出最大值
* @return
*/
public int extractMax(){
if (isEmpty()){
throw new NoSuchElementException("heap is empty!");
}
//最大值就为堆顶元素
int max=data.get(0);
//将堆顶元素修改成最后一个元素
data.set(0,data.get(data.size()-1));
//删除最后一个元素
data.remove(data.size()-1);
siftDown(0);
return max;
}
/**
* 元素下沉
* @param k
*/
private void siftDown(int k) {
//此时k存在子树
while(left(k)<data.size()){
//取得左子树
int i=left(k);
//判断右子树是否存在并且右子树是否大于左子树
if (i+1<data.size()&&data.get(i+1)>data.get(i)){
//如果条件满足,此时i就是右子树的索引
i=i+1;
}
//当前节点值大于左右子树的值
if (data.get(k)>=data.get(i)){
break;
}else{
swap(k,i);
k=i;
}
}
}
/**
* 添加元素
* @param val
*/
public void addNode(int val){
//先将元素添加到末尾
data.add(val);
//然后进行上浮操作
siftUp(data.size()-1);
}
/**
* 元素上浮
* @param k
*/
private void siftUp(int k) {
//当当前节点不是根节点并且当前节点的值大于父节点的值
while (k > 0 && data.get(k) > data.get(parent(k))){
//交换当前节点和它的父节点的值
swap(k,parent(k));
k=parent(k);
}
}
private void swap(int k, int p) {
int tmp=data.get(k);
data.set(k,data.get(p));
data.set(p,tmp);
}
/**
* 根据当前索引取得右子树
* @param k 当前索引
* @return 右子树节点
*/
public int right(int k){
return (k<<1)+2;
}
/**
* 根据当前索引取得左子树索引
* @param k 当前索引
* @return 左子树索引
*/
public int left(int k){
return (k<<1)+1;
}
/**
* 根据当前索引取得父节点的索引
* @param k 当前索引
* @return 父节点索引
*/
private int parent(int k){
return (k-1)>>1;
}
/**
* 判断最大堆是否为空
* @return
*/
public boolean isEmpty(){
return data.isEmpty();
}
@Override
public String toString() {
return data.toString();
}
}