1.定义
- 堆是一个完全二叉树
- 堆中的每一个节点值必须大于等于(大顶堆)或者小于等于(小顶堆)其子节点的值
2.堆的存储结构
- 之前我们知道完全二叉树可以对应连续存储在数组中只会浪费掉下标为0的位置 如下图 (而如果不能满足该条件的就不是完全二叉树)
3.使用数组来实现堆
- 基本方法
public class Heap {
private int[] date;//存储元素的数组
private int size; //数组长度
private int count;//已存储元素个数
/**
* 构造初始化
* @param init
*/
public Heap(int init) {
this.date = new int[init + 1];
this.size = init;
this.count = 0;
}
@Override
public String toString() {
return "Heap{" +
"date=" + Arrays.toString(date) +
", size=" + size +
", count=" + count +
'}';
}
}
- 添加方法(构建一个小顶堆)
- 只需要判断新添加的元素是否小于其父节点 如果小于 就替换即可
- 目前使用数组对二叉树进行实现 所以 父节点元素下标 = 当前节点下标 / 2
- 只需要判断新添加的元素是否小于其父节点 如果小于 就替换即可
/**
* 添加方法
* 1. 从下到上进行堆化 即把新添加的元素挨个向上对比(和其父节点对比)
* 2. 如果发现小于该节点 就替换两个节点位置 直到最后完成一个小顶堆
* @param data
*/
public void insert(int data) {
if (count > size) {
return;//此时堆已经满了 则不能继续存储 有需要可以自己实现一下扩容操作
}
this.data[++count] = data;
//进行堆化
heapDate2Top(this.data,count);
}
/**
* 比较新添加数据和父节点(现在堆直接对应到了数组里 元素下标除以2就是父节点的元素下标)数据大小 小于就替换
* @param data 数组
* @param endIndex 最后一个元素的索引
*/
public void heapDate2Top(int[] data, int endIndex) {
int temp = endIndex;
//当新插入元素索引的一半大于0且对应的元素小于当前值时 替换两个值 (堆里的元素是有序的 直接进行一个二分对比即可)
while (temp / 2 > 0 && this.data[temp / 2] > this.data[temp]) {
swap(data, temp / 2, temp);
temp /= 2;
}
}
//替换元素位置
public void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
-
删除堆顶元素
- f方法一 : 目前我们构造了一个小顶堆 所以 堆顶元素小于所有节点值 当我们删除该节点后 那么最小元素就出现在了左右2个子节点里 直接拿到最小的放到堆顶
- 但是上面这种方法有一个问题 可能在删除一个节点后 该二叉树就不是一个完全二叉树(也就不满足堆的定义了) 如下
- 方法二 : 当我们删除堆顶元素后 把数组最后一个节点元素放到当前位置 然后把该元素挨个和其子节点元素比较 如果大于子节点元素就替换(注意这里要和较小的哪个子节点替换) 直到最后 这就是从上向下的堆化 由于移除的是数组最后一个元素 所以不会破坏堆结构
- f方法一 : 目前我们构造了一个小顶堆 所以 堆顶元素小于所有节点值 当我们删除该节点后 那么最小元素就出现在了左右2个子节点里 直接拿到最小的放到堆顶
/**
* 删除堆顶元素
*/
public int delTop(){
if (count < 0){
return 0;//此时 堆里没有元素
}
//小顶堆 则下标为1的元素是最小值
int min = this.data[1];
//把最后一个元素放到堆顶 并且元素数量-1
this.data[1] = this.data[count--];
//把最后一个元素置空
this.data[size--] = 0;
//进行堆化
heapDate2high(this.data,1,count);
return min;
}
/**
* 比较替换后的父节点和子节点大小 如果大于 就替换 最后完成一个小顶堆
*
* @param data
* @param beginIndex 替换后的节点索引
* @param endIndex 结束位置索引
*/
public void heapDate2high(int[] data, int beginIndex, int endIndex) {
int left,right;
while (true) {//当前元素下标小于总元素个数时 进行替换操作
//判断节点和左子树大小且左子树值要小于右子树值时 大于就替换位置
if ((left = 2 * beginIndex) <= endIndex
&& data[beginIndex] > data[left]
&& ((right = 2 * beginIndex + 1) > endIndex ? true : data[left] < data[right])) {
swap(data, beginIndex, left);
beginIndex = left;
} else if ((right = 2 * beginIndex + 1) <= endIndex && data[beginIndex] > data[right]) {
//否则判断节点和右子树大小 大于就替换位置
swap(data, beginIndex, right);
beginIndex = right;
}else {
break;
}
}
}
- 测试
public static void main(String[] args) {
Heap heap = new Heap(8);
heap.insert(1);
heap.insert(6);
heap.insert(8);
heap.insert(0);
heap.insert(3);
heap.insert(5);
heap.insert(9);
heap.insert(10);
System.out.println("添加完成后的堆元素 : " + heap);
heap.delTop();
System.out.println("删除堆顶后的元素 : " + heap);
heap.delTop();
System.out.println("删除堆顶后的元素 : " + heap);
heap.delTop();
System.out.println("删除堆顶后的元素 : " + heap);
heap.delTop();
System.out.println("删除堆顶后的元素 : " + heap);
}
4.完整代码
package com.example.ioimage;
import java.util.Arrays;
/**
* 数组实现堆
*/
public class Heap {
public static void main(String[] args) {
Heap heap = new Heap(8);
heap.insert(1);
heap.insert(6);
heap.insert(8);
heap.insert(0);
heap.insert(3);
heap.insert(5);
heap.insert(9);
heap.insert(10);
System.out.println("添加完成后的堆元素 : " + heap);
heap.delTop();
System.out.println("删除堆顶后的元素 : " + heap);
heap.delTop();
System.out.println("删除堆顶后的元素 : " + heap);
heap.delTop();
System.out.println("删除堆顶后的元素 : " + heap);
heap.delTop();
System.out.println("删除堆顶后的元素 : " + heap);
}
private int[] data;//存储元素的数组
private int size; //数组长度
private int count;//已存储元素个数
/**
* 构造初始化
*
* @param init
*/
public Heap(int init) {
this.data = new int[init + 1];//0下标位置为空 所以长度 + 1
this.size = init;
this.count = 0;
}
/**
* 添加方法
* 1. 从下到上进行堆化 即把新添加的元素挨个向上对比(和其父节点对比)
* 2. 如果发现小于该节点 就替换两个节点位置 直到最后完成一个小顶堆
*
* @param data
*/
public void insert(int data) {
if (count > size) {
return;//此时堆已经满了 则不能继续存储
}
this.data[++count] = data;
//进行堆化
heapDate2Top(this.data, count);
}
/**
* 删除堆顶元素
*/
public int delTop() {
if (count < 0) {
return 0;//此时 堆里没有元素
}
//小顶堆 则下标为1的元素是最小值
int min = this.data[1];
//把最后一个元素放到堆顶 并且元素数量-1
this.data[1] = this.data[count--];
//把最后一个元素置空
this.data[size--] = 0;
//进行堆化
heapDate2high(this.data, 1, count);
return min;
}
/**
* 比较新添加数据和父节点(现在堆直接对应到了数组里 元素下标除以2就是父节点的元素下标)数据大小 小于就替换
*
* @param data 数组
* @param endIndex 最后一个元素的索引
*/
public void heapDate2Top(int[] data, int endIndex) {
int temp = endIndex;
//当新插入元素索引的一半大于0且对应的元素小于当前值时 替换两个值 (堆里的元素是有序的 直接进行一个二分对比即可)
while (temp / 2 > 0 && this.data[temp / 2] > this.data[temp]) {
swap(data, temp / 2, temp);
temp /= 2;
}
}
/**
* 比较替换后的父节点和子节点大小 如果大于 就替换 最后完成一个小顶堆
*
* @param data
* @param beginIndex 替换后的节点索引
* @param endIndex 结束位置索引
*/
public void heapDate2high(int[] data, int beginIndex, int endIndex) {
int left,right;
while (true) {//当前元素下标小于总元素个数时 进行替换操作
//判断节点和左子树大小且左子树值要小于右子树值时 大于就替换位置
if ((left = 2 * beginIndex) <= endIndex
&& data[beginIndex] > data[left]
&& ((right = 2 * beginIndex + 1) > endIndex ? true : data[left] < data[right])) {
swap(data, beginIndex, left);
beginIndex = left;
} else if ((right = 2 * beginIndex + 1) <= endIndex && data[beginIndex] > data[right]) {
//否则判断节点和右子树大小 大于就替换位置
swap(data, beginIndex, right);
beginIndex = right;
}else {
break;
}
}
}
//替换元素位置
public void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
@Override
public String toString() {
return "Heap{" +
"date : " + Arrays.toString(data) +
", size : " + size +
", count : " + count +
'}';
}
}