0
点赞
收藏
分享

微信扫一扫

堆之最小堆、最大堆(java实现)

有点d伤 2022-01-05 阅读 89

文章目录

定义:堆是树型的一对多的数据结构,使用数组实现

  • 对应完全二叉树
  • 每个节点大于等于或者小于等于它的子节点
二叉树: 每个非叶子节点最多有两个分支节点
满二叉树: 每个非叶子节点都有两个子节点
完全二叉树: 最后一层的最后一个节点的父节点不满足满二叉树之外,其它非叶子节点都满足满二叉树

堆分为最小堆和最大堆

  • 最大堆:是一个完全二叉树,父节点不小于子节点
  • 最小堆:是一个完全二叉树,父节点不大于子节点

堆的特性

  • 堆的前一半是非叶子节点,后一般是叶子节点

关键操作

  • 上浮
  • 下沉

图解

  • 当前节点父节点的索引为:当前节点的索引/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吗
举报

相关推荐

0 条评论