二叉堆的实现

目录

1.二叉树的实现 

1.1二叉堆实现的功能

 1.2删除堆顶元素

1.3 堆化


1.二叉树的实现 

二叉堆是基于二叉树的堆结构,二叉堆就是一颗完全二叉树,节点编号从0开始。

问题:若一个节点k存在左右子树?如何仅用索引判断一个节点是否有子树

答:右子树的索引(k<<1)+1;

左子树的索引(k>>1)+2;

问:给一个节点k如何判断节点是否存在?

答:看其父节点的编号是否大于0

父节点的计算方法:(k-1)>>1

二叉堆的特点:

1.二叉堆是一颗完全二叉树

2.根节点节点值大于等于子树的节点值(最大堆/大根堆)

3.根节点节点值小于等于子树的节点值(最小堆/小根堆)

注意:在最大堆中,只能保证当前根节点大于等于子树所有节点,在任意子树中都满足。

节点大小与层次无关。

实现代码中用到了swap()用于元素的交换

parent()用于查找父节点

leftChild()用于查找节点的左孩子

1.1二叉堆实现的功能

完全二叉树、满二叉树建议使用顺序表存储,其他二叉树不建议(因为存储空节点会浪费大量的空间)

1.1向最大堆中添加一个新元素(在数组的末尾新增元素是O(n))

具体方法:直接在数组的末尾新增元素,保证添加元素之后,这颗二叉树仍然是完全二叉树。

添加新元素之后要调整元素的位置,使得这颗二叉树任然满足最大堆的性质。

方法:siftUp(int k):上浮操作,k为索引

  public void add(int val){
        //1.直接向数组的末尾添加元素
            data.add(val);//数组的add默认是尾插
            siftUp(data.size()-1);
        }

 public void siftUp(int k) {
            //k为当前索引
            while (k > 0 && data.get(k) > data.get(parent(k))) {
                //说明没有索引到根节点,且值大于父节点的值,所以需要向上调整
                swap(k, parent(k));
                k = parent(k);
            }
        }

 1.2删除堆顶元素

对于堆而言,最大的特点在于堆顶元素是这个集合的最值

而对于最大堆而言,堆顶元素就是最大值

而因为我们的二叉堆是基于数组实现的,尾插尾删都很简单,但是对头部的操作就比较麻烦

方法:将数组末尾的元素顶到堆顶,然后进行元素的下沉操作siftDown(int k)

用extractMax():取出当前最大值元素(也就是删除之后返回之前的最大值),实际就是取出堆顶最大值data.get(0)

 public int extractMax(){
         if(isEmpty()){
             throw new  NoSuchElementException("heap is empty now");
         }
        int max =data.get(0);
        //让最后一个元素顶替根节点
//         data.set(0,data.get(data.size()-1));
         int lastVal = data.get(data.size() - 1);
         data.set(0,lastVal);
         //然后删除数组的最后一位
         data.remove(data.size()-1);
         siftDown(0);
        return max;
     }
     //然后开始元素的下浮操作
    public void siftDown(int k){
       //还存在子树
        while(leftChild(k)<data.size()){
            int j =leftChild(k);
            //看是否存在右数
            if(j+1< data.size()&&data.get(j+1)> data.get(j)){
                //说明右子树存在,且右子树大于左子树
                j =j+1;
            }
            //此时j就对应左右子树的最大值
            //和当前节点比较
            if(data.get(k)>=data.get(j)) {
                //下沉结束
                break;
            }else{
                swap(k,j);
                k=j;
            }
            }
        }

 这里附上测试人员测试用的代码,因为我们举得个例可能太特殊,所以需要举很多随机数,防止巧合的发生。

 int[] data = new int[100000];
        ThreadLocalRandom random = ThreadLocalRandom.current();
        for (int i = 0; i < data.length; i++) {
            data[i] = random.nextInt(0, Integer.MAX_VALUE);
        }
        //把数组放入堆中
        MaxHeap heap = new MaxHeap();
        for (int i : data) {
            heap.add(i);
        }
        for (int i = 0; i < data.length; i++) {
            data[i] = heap.extractMax();
        }
        System.out.println(isSorted(data));
    }

        public static boolean isSorted(int []data){
            for(int i=0;i<data.length-1;i++){
                if(data[i]<data[i+1]){
                    System.out.println("error");
                    return false;
                }
        }
        return true;

1.3 堆化

就是将任意的数组调整为堆的结构,这个方法输出的结果是倒序

其实很简单,在上面测试的代码也用到了,一个构造方法就可以实现

  //将任意数组堆化,在构造方法里面
        public MaxHeap(int []arr){
        //数组的初始化
            data = new ArrayList<>(arr.length);
            for (int i:arr) {
                data.add(i);
            }
            //因为最后一个叶子节点的父亲就是最后一个非叶子节点
            for (int i = parent(data.size()-1); i>=0; i--) {
                siftDown(i);
            }
        }

具体的代码如下:

package heap;

import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;

//基于整型最大堆实现,此时根结点从零开始编号
public class MaxHeap {
    //用数组存储最大堆
    List<Integer> data;
    public MaxHeap(){
        //构造方法的this调用
        this(10);
    }
        public MaxHeap(int size){
        data =new ArrayList<>(size);

        }
        //将任意数组堆化,在构造方法里面
        public MaxHeap(int []arr){
        //数组的初始化
            data = new ArrayList<>(arr.length);
            for (int i:arr) {
                data.add(i);
            }
            //因为最后一个叶子节点的父亲就是最后一个非叶子节点
            for (int i = parent(data.size()-1); i>=0; i--) {
                siftDown(i);
            }
        }

     public void add(int val){
        //1.直接向数组的末尾添加元素
            data.add(val);//数组的add默认是尾插
            siftUp(data.size()-1);
        }
     public int extractMax(){
         if(isEmpty()){
             throw new  NoSuchElementException("heap is empty now");
         }
        int max =data.get(0);
        //让最后一个元素顶替根节点
//         data.set(0,data.get(data.size()-1));
         int lastVal = data.get(data.size() - 1);
         data.set(0,lastVal);
         //然后删除数组的最后一位
         data.remove(data.size()-1);
         siftDown(0);
        return max;
     }
     //然后开始元素的下浮操作
    public void siftDown(int k){
       //还存在子树
        while(leftChild(k)<data.size()){
            int j =leftChild(k);
            //看是否存在右数
            if(j+1< data.size()&&data.get(j+1)> data.get(j)){
                //说明右子树存在,且右子树大于左子树
                j =j+1;
            }
            //此时j就对应左右子树的最大值
            //和当前节点比较
            if(data.get(k)>=data.get(j)) {
                //下沉结束
                break;
            }else{
                swap(k,j);
                k=j;
            }
            }
        }
        public int peekMax(){
        if(isEmpty()){
            throw new NoSuchElementException("can not find any value");
        }
        return data.get(0);//返回堆顶元素
        }
        //上浮操作
        public void siftUp(int k) {
            //k为当前索引
            while (k > 0 && data.get(k) > data.get(parent(k))) {
                //说明没有索引到根节点,且值大于父节点的值,所以需要向上调整
                swap(k, parent(k));
                k = parent(k);
            }
        }
        public void swap(int i ,int j){
            int temp =data.get(i);
            //修改数组的值
            data.set(i,data.get(j));
            data.set(j,temp);
            }
        public boolean isEmpty(){
        return data.size()==0;
        }
        //根据索引得到父节点的索引
    private int parent(int k){
        return (k-1)>>1;
    }
    private int leftChild(int k){
        return (k<<1)+1;
    }
    private int rightChild(int k){
        return (k<<1)+2;
    }

    @Override
    public String toString() {
        return data.toString();
    }
}

测试代码

public class HeapTest {
    public static void main(String[] args) {
//这里用于实现
        int[] data = {15, 17, 19, 13, 22, 16, 28, 30, 41, 62};
        MaxHeap heap = new MaxHeap(data);
        System.out.println(heap);

      /*  //这是测试的代码------------------------------------------------------------------------------------------------------
        int[] data = new int[100000];
        ThreadLocalRandom random = ThreadLocalRandom.current();
        for (int i = 0; i < data.length; i++) {
            data[i] = random.nextInt(0, Integer.MAX_VALUE);
        }
        //把数组放入堆中
        MaxHeap heap = new MaxHeap();
        for (int i : data) {
            heap.add(i);
        }
        for (int i = 0; i < data.length; i++) {
            data[i] = heap.extractMax();
        }
        System.out.println(isSorted(data));
    }

        public static boolean isSorted(int []data){
            for(int i=0;i<data.length-1;i++){
                if(data[i]<data[i+1]){
                    System.out.println("error");
                    return false;
                }
        }
        return true;
        }*/
        //--------------------------------------------------------------------------------------------------------------------
//这里是添加元素以及堆化和删除堆顶元素的代码
//       MaxHeap heap =new MaxHeap();
//       int [] data ={62,41,30,28,16,13,22,19,17,15};
//       for (int i:data) {
//            heap.add(i);
//        }
      /*  heap.add(52);
        System.out.println(heap);*/
//        //得到的是一个完全降序的数组
//        for (int i = 0; i < data.length; i++) {
//            data[i]=heap.extractMax();
//        }
//        //如果不写数组的toString方法,打印的是地址
//        System.out.println(Arrays.toString(data));
    }
}

 

测试代码的执行 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值