文章目录
堆
定义:堆是树型的一对多的数据结构,使用数组实现
- 对应完全二叉树
- 每个节点大于等于或者小于等于它的子节点
二叉树: 每个非叶子节点最多有两个分支节点
满二叉树: 每个非叶子节点都有两个子节点
完全二叉树: 最后一层的最后一个节点的父节点不满足满二叉树之外,其它非叶子节点都满足满二叉树
堆分为最小堆和最大堆
- 最大堆:是一个完全二叉树,父节点不小于子节点
- 最小堆:是一个完全二叉树,父节点不大于子节点
堆的特性:
- 堆的前一半是非叶子节点,后一般是叶子节点
关键操作
- 上浮
- 下沉
图解
- 当前节点父节点的索引为:当前节点的索引/2
最大堆的api设计
数组的索引0处,不存储元素被弃之,从1开始存储对的元素
索引1存根节点,2存根节点的左子节点,3存根节点的右子节点,依次类推
类名 | HeapMax<T extends Comparable> implements Iterable |
---|---|
构造方法 | Heap(): 创建容量为capacity的Heap对象,默认容量是16 Heap(int capacity): 创建容量为capacity的Heap对象,传入数组的大小 |
成员方法 | 1、priviate boolean compare(int i,int j): 判断堆中索引i处的元素是否小于j处的元素 2、private void exchange(int i, int j): 交换索引i处和索引j处的值 3、public int size(): 堆的长度 4、public void clear(): 清除堆 5、public void insert(T t): 往堆中插入一个元素 6、private void swim(int k): 使用上浮算法,使索k处的元素能在堆中处于一个正确的位置 7、public T maxEle(): 堆中最大的元素 8、public T delMax(): 删除堆中最大的元素,并返回这个最大的元素 9、private void sink(int k): 使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置 10、public void resize(int capacity): 扩容,缩容 11、contain(T t): 堆中是否存在t 12、iterator(): 重写Iterable的方法,用于foreach遍历 |
成员变量 | 1.private T[] items : 用来存储元素的数组 2.private int len: 记录堆中元素的个数 |
类名 | HIterator implements Iterator |
---|---|
构造方法 | HIterator(); 初始化n的长度 |
成员方法 | 1、public boolean hasNext(): 判断是否有下一个元素 2、public Object next(): 返回下一个元素 |
成员变量 | int n; 计数变量,用于判断是由小于堆的长度 |
最大堆代码实现
import java.util.Iterator;
public class HeapMax<T extends Comparable<T>> implements Iterable{
private T[] items;
private int len;
HeapMax(){
items = (T[]) new Comparable[16];
this.len = 0;
}
HeapMax(int capacity){
items = (T[]) new Comparable[capacity];
this.len = 0;
}
private boolean compare(Comparable e1,Comparable e2){
return e1.compareTo(e2)<0;
}
private void exchange(Comparable[] arr,int e1,int e2){
Comparable temp = arr[e1];
arr[e1] = arr[e2];
arr[e2] = temp;
}
public int size(){
return this.len;
}
public void clear(){
items = null;
this.len = 0;
}
public boolean isEmpty(){
return this.len == 0;
}
public void insert(T t){
//注意0为存储元素,废弃了
if(len==items.length-1){
resize((int) (items.length*1.5));
}
//数组索引0废弃掉了,方便索引计算
items[++len] = t;
//进行上浮操作,也就是排序(比较排序)
swim(len);
}
private void swim(int len){
//0废弃了,没有元素
//思考一下,为什么不是len>0
//最大堆必须保持,父节点大于等于子节点,len代表传进来的子节点,如果是左子节点,则len可以为1,如果len代表的是右子节点,则父节点就不存在了
//父节点索引=左子节点索引/2=右子节点索引/2-1
//边界是存在右子节点
while(len>1){
//子节点和父节点进行比较,满足条件进行位置交换
if(!compare(items[len],items[len/2])){
exchange(items,len,len/2);
}
//当父节点为1时就结束循环了
len/=2;
}
}
public T maxEle(){
if(this.len == 0){
return null;
}
return items[1];
}
public T delMax(){
if(len == items.length/4){
resize(items.length/2);
}
//该堆是最大堆,索引1存的最大元素
T max = items[1];
//堆删除元素其实就是维护一个完全二叉树
//将堆的最后一个元素的值赋给索引1后,就变成了一颗完全二叉树(无序),然后堆的长度-1
items[1] = items[len];
items[len] = null;
this.len--;
//最大堆必须满足父节点大于等于子节点,所以需要对索引1出的值进行下浮(排序)操作
sink(1);
return max;
}
private void sink(int k){
//下沉的操作比上浮的操作复杂
//最大堆必须保持,父节点大于等于子节点,首先我们需要找出子节点种较大的,然后进行下沉操作
//思考一下这个地方为什么不是k<=n和2*k+1<=n,而是2*k<=n
//k<=len时: 代表传入的是最有一个节点,不需要进行下沉操作了
//2*k+1<=len: 时代表传入的节点的两个子节点都存在,不能算作边界条件
//2*k<=len: 时代表传入的节点存在左子节点,不一定存在右子节点
//左子节点索引=父节点索引*2 右子节点索引=父节点索引*2+1
while(2*k<=len){
//定义一个变量max,获取到左右子节点种较大节点的索引
int max;
//判断是否存在右子节点
if(2*k+1<=len){
//找出较大节点的索引
if(!compare(items[2*k],items[2*k+1])){
max = 2*k+1;
}else{
max = 2*k;
}
}else {
//不存在右子节点,直接将左子节点的索引赋给max
max = 2*k;
}
//父节点与子节点进行下沉操作(排序)
if(compare(items[k],items[max])){
exchange(items,k,max);
k*=2;
}else{
//当父节点大于等于子节点时说明已经排好序,跳出循环
break;
}
}
}
//扩容,缩容
public void resize(int capacity){
Comparable[] temp = items;
items = (T[]) new Comparable[capacity];
//注意从索引1开始
for (int i = 1; i <= temp.length; i++) {
items[i] = (T) temp[i];
}
}
public boolean contain(T t){
//思考一下能够使用二叉查找的方法查找t吗
//最大堆只是说父节点大于等于子节点,并没有说左子树大于右子树,从二叉树的定义每个节点做多有两个节点上,堆是二叉树,但是不满足二叉树的左子树大于右子树的特点
//这里使用遍历数组的方式判断堆种是否包含t
for(int i = 1; i <= len; i++){
if(items[i] == t){
return true;
}
}
return false;
}
private class HIterator implements Iterator{
int n;
HIterator(){
n = 1;
}
@Override
public boolean hasNext() {
return n<=len;
}
@Override
public Object next() {
return items[n++];
}
}
@Override
public Iterator iterator() {
return new HIterator();
}
}
测试最大堆
public class HeapMaxTest {
public static void main(String[] args) {
HeapMax<Integer> heapMax = new HeapMax<>();
heapMax.insert(3);
heapMax.insert(2);
heapMax.insert(5);
heapMax.insert(1);
heapMax.insert(9);
heapMax.insert(6);
heapMax.insert(8);
heapMax.insert(7);
System.out.println(heapMax.maxEle());
System.out.println(heapMax.size());
System.out.println("-----------foreach遍历--------------");
//HeapMax需要实现Iterator方法,自定义私有类实现类Iterator接口重写里面的方法
for (Object o : heapMax) {
System.out.println(o);
}
System.out.println("----------------------------");
System.out.println(heapMax.delMax());
System.out.println(heapMax.size());
System.out.println(heapMax.contain(2));
heapMax.clear();
System.out.println(heapMax.size());
}
}
9
8
-----------foreach遍历--------------
9
7
8
5
2
3
6
1
----------------------------
9
7
true
0
最小代码实现
import java.util.Iterator;
public class HeapMin<T extends Comparable<T>> implements Iterable{
private T[] items;
private int len;
HeapMin(){
items = (T[]) new Comparable[16];
this.len = 0;
}
HeapMin(int capacity){
items = (T[]) new Comparable[capacity];
this.len = 0;
}
private boolean compare(Comparable e1,Comparable e2){
return e1.compareTo(e2)<0;
}
private void exchange(Comparable[] arr,int e1,int e2){
Comparable temp = arr[e1];
arr[e1] = arr[e2];
arr[e2] = temp;
}
public int size(){
return this.len;
}
public void clear(){
items = null;
this.len = 0;
}
public boolean isEmpty(){
return this.len == 0;
}
public void insert(T t){
//注意0为存储元素,废弃了
if(len==items.length-1){
resize((int) (items.length*1.5));
}
//数组索引0废弃掉了,方便索引计算
items[++len] = t;
//进行上浮操作,也就是排序(比较排序)
swim(len);
}
private void swim(int len){
//0废弃了,没有元素
//思考一下,为什么不是len>0
//最大堆必须保持,父节点大于等于子节点,len代表传进来的子节点,如果是左子节点,则len可以为1,如果len代表的是右子节点,则父节点就不存在了
//父节点索引=左子节点索引/2=右子节点索引/2-1
//边界是存在右子节点
while(len>1){
//子节点和父节点进行比较,满足条件进行位置交换
if(compare(items[len],items[len/2])){
exchange(items,len,len/2);
}
//当父节点为1时就结束循环了
len/=2;
}
}
public T minEle(){
if(len == 0){
return null;
}
return items[1];
}
public T delMin(){
if(len == items.length/4){
resize(items.length/2);
}
//该堆是最小堆,索引1存的最小元素
//将堆的最后一个元素的值赋给索引1后,就变成了一颗完全二叉树(无序),然后堆的长度-1
T min = items[1];
items[1] = items[len];
items[len] = null;
this.len--;
//最小堆必须满足父节点小于等于子节点,所以需要对索引1出的值进行下浮(排序)操作
sink(1);
return min;
}
private void sink(int k){
//下沉的操作比上浮的操作复杂
//最小堆必须保持,父节点小于等于子节点,首先我们需要找出子节点种较大的,然后进行下沉操作
//思考一下这个地方为什么不是k<=n和2*k+1<=n,而是2*k<=n
//k<=len时: 代表传入的是最有一个节点,不需要进行下沉操作了
//2*k+1<=len: 时代表传入的节点的两个子节点都存在,不能算作边界条件
//2*k<=len: 时代表传入的节点存在左子节点,不一定存在右子节点
//左子节点索引=父节点索引*2 右子节点索引=父节点索引*2+1
while(2*k<=len){
//定义一个变量min,获取到左右子节点种较小节点的索引
int max;
//判断是否存在右子节点
if(2*k+1<=len){
//找出较小节点的索引
if(compare(items[2*k],items[2*k+1])){
max = 2*k+1;
}else{
max = 2*k;
}
}else {
//不存在右子节点,直接将左子节点的索引赋给min
max = 2*k;
}
//父节点与子节点进行下沉操作(排序)
if(!compare(items[k],items[max])){
exchange(items,k,max);
k*=2;
}else{
//当父节点小于等于子节点时说明已经排好序,跳出循环
break;
}
}
}
//扩容,缩容
public void resize(int capacity){
Comparable[] temp = items;
items = (T[]) new Comparable[capacity];
//注意从索引1开始
for (int i = 1; i <= temp.length; i++) {
items[i] = (T) temp[i];
}
}
public boolean contain(T t){
//思考一下能够使用二叉查找的方法查找t吗
//最大堆只是说父节点大于等于子节点,并没有说左子树大于右子树,从二叉树的定义每个节点做多有两个节点上,堆是二叉树,但是不满足二叉树的左子树大于右子树的特点
//这里使用遍历数组的方式判断堆种是否包含t
for(int i = 1; i <= len; i++){
if(items[i] == t){
return true;
}
}
return false;
}
@Override
public Iterator iterator() {
return new HIterator();
}
private class HIterator implements Iterator{
int n;
HIterator(){
n = 1;
}
@Override
public boolean hasNext() {
return n<=len;
}
@Override
public Object next() {
return items[n++];
}
}
}
最小堆测试
public class HeapMinTest {
public static void main(String[] args) {
HeapMin<Integer> heapMin = new HeapMin<>();
heapMin.insert(3);
heapMin.insert(2);
heapMin.insert(5);
heapMin.insert(1);
heapMin.insert(9);
heapMin.insert(6);
heapMin.insert(8);
heapMin.insert(7);
System.out.println(heapMin.minEle());
System.out.println(heapMin.size());
System.out.println("-----------foreach遍历--------------");
//heapMin需要实现Iterator方法,自定义私有类实现类Iterator接口重写里面的方法
for (Object o : heapMin) {
System.out.println(o);
}
System.out.println("----------------------------");
System.out.println(heapMin.delMin());
System.out.println(heapMin.size());
System.out.println(heapMin.contain(2));
heapMin.clear();
System.out.println(heapMin.size());
}
}
1
8
-----------foreach遍历--------------
1
2
5
3
9
6
8
7
----------------------------
1
7
true
0
思考:
public T remvoe(int i); 堆中能有这个方法吗
public int get(T t): 堆中能使用二叉查找树查找t吗