Java数据结构-堆相关

1:堆

堆:堆是一棵完全二叉树

大根堆:完全二叉树,根节点比左右孩子都大

小根堆:完全二叉树,根节点比左右孩子都小

下图是一个小根堆:层序的规则采用顺序的方式来高效存储,使用数组来存储层序遍历的结果

  • 已知孩子节点下标i,父亲节点下标为 ( i − 1 ) / 2 (i-1)/2 (i1)/2
  • 已知父亲节点下标i,
    • 左孩子: 2 ∗ i + 1 2*i+1 2i+1
    • 右孩子: 2 ∗ i + 2 2*i+2 2i+2

在这里插入图片描述

2:大根堆/小根堆

如何将一个完全二叉树调整为大根堆/小根堆?

    //堆的成员变量,一个数组,一个记录节点数量的int型变量
    public int elem[];
    public int usedSize;
	//构造方法初始化数组为长度为10的数组
    public MyHeap() {
        this.elem = new int[10];
    }

大根堆创建: 时间复杂 O ( N ) O(N) O(N)

    public void createBigHeap(int [] array){
    	//将传入的数组的值依次放到堆数组中
        for(int i=0;i<array.length;i++){
            elem[i]=array[i];
            this.usedSize++;
        }
		//可以直到最后一个节点下标,即可知道最后一个有孩子的父节点的下标
    	//如果一颗完全二叉树要成为大根堆,那么它的每一棵子树都必须是大根堆
        for(int parent=(usedSize-1-1)/2;parent>=0;parent--){
            //每次调用向下调整,parent变化,而usedSize不变
            shiftDown(parent, usedSize);
        }
    }

	 /**
     *调整为大根堆
     * @param parent 每次调整的根节点的下标
     * @param len 调整的边界,如果超过则表示已经调整完毕
     */
    public void shiftDown(int parent,int len){
        
        int child=(parent*2+1);//左孩子下标

        //判断左孩子下标是否超过边界
        while (child<len){
			//如果左孩子下标+1还是再边界内则代表它还有右孩子
            //如果右孩子的值大,则child变为右孩子下标
            if(child+1<len && elem[child]<elem[child+1]){
                child++;
            }
			//如果孩子节点的值大于父亲节点则交换位置
            if(elem[child]>elem[parent]){
                int tmp=elem[parent];
                elem[parent]=elem[child];
                elem[child]=tmp;
                //父亲节点向下移动
                parent=child;
                child=parent*2+1;
            }else {
                break;
            }
        }
    }

大根堆创建

小根堆创建: 基本上和大根堆相似,时间复杂度 O ( N ) O(N) O(N)

	public void createSmallHeap(int [] array){

        for(int i=0;i<array.length;i++){
            elem[i]=array[i];
            this.usedSize++;
        }

        for(int parent=(usedSize-1-1)/2;parent>=0;parent--){
            shiftDownMine(parent, usedSize);
        }
    }

    /**
     * @param parent 每次调整的根节点的下标
     * @param len  调整的边界,如果超过则表示已经调整完毕
     */
    public void shiftDownMine(int parent,int len){
        int child=(parent*2+1);//左孩子下标

        while (child<len){
			//如果右孩子存在,且右孩子比左孩子小,则child为右孩子下标
            if(child+1<len && elem[child]>elem[child+1]){
                child++;
            }
			//如果孩子节点小于根节点则交换
            if(elem[child]<elem[parent]){
                int tmp=elem[parent];
                elem[parent]=elem[child];
                elem[child]=tmp;
                parent=child;
                child=parent*2+1;
            }else {
                break;
            }
        }
    }

3:插入/删除元素

大(小)根堆插入元素:

向大根堆中插入元素,先要判断是否要对数组进行扩容,再将插入的元素放入最后一个位置,进行向上调整

	/**
     * 插入元素到堆中
     * @param val
     */
    public void push(int val){
        //二倍扩容
        if(isFull()){
            this.elem= Arrays.copyOf(elem, 2*elem.length);
        }
        elem[usedSize]=val;
        usedSize++;
        //孩子下标最大值
        shiftUp(usedSize-1);
    }

	//向上调整
    public void shiftUp(int child){
		//找到最后一个元素的父节点下标
        int parent=(child-1)/2;
		//当父节点下标在边界内
        while (parent>=0){
            //如果孩子节点的值大于父亲节点则交换
            //交换后的父亲节点作为孩子节点继续向上调整
            if(elem[child]>elem[parent]){
                int tmp=elem[parent];
                elem[parent]=elem[child];
                elem[child]=tmp;
                child=parent;
                parent=(child-1)/2;
            }else {
                break;
            }
        }
    }

大根堆插入元素

大(小)根堆删除元素:

从大根堆中删除元素,一定删除的是堆顶元素,删除优先级最高的元素

将堆顶元素和最末尾元素互换位置并删除最后一个元素,将堆顶元素进行向下调整

 	/**
     * 删除堆顶元素
     */
    public void pop(){
        if(isEmpty()){
            throw  new RuntimeException("堆为空");
        }
        this.elem[0]=this.elem[usedSize-1];
        usedSize--;
        shiftDown(0, usedSize);
    }

    public void shiftDown(int parent,int len){
        int child=(parent*2+1);//左孩子
        while (child<len){
            if(child+1<len && elem[child]<elem[child+1]){
                child++;
            }
            if(elem[child]>elem[parent]){
                int tmp=elem[parent];
                elem[parent]=elem[child];
                elem[child]=tmp;
                parent=child;
                child=parent*2+1;
            }else {
                break;
            }
        }
    }

大根堆删除元素

4:堆排序

堆排序:

升序(从小到大):创建大根堆

降序(从大到小):创建小根堆

以大根堆为例,排序时,将最大元素也就是堆顶元素和最后一个元素交换位置,然后忽略最后一个元素也就是最大的元素,进行向下调整,以此类推,直到剩余堆顶一个元素即可

 //堆排序,从小到大,升序,需要一个大根堆
    public void  heapSort(){
        int end=usedSize-1;
        int tmp=0;
        while (end>0){
            tmp=elem[0];
            elem[0]=elem[end];
            elem[end]=tmp;
            shiftDown(0, end);
            end=end-1;
        }
    }

从小到大升序,大根堆实例图

top-k问题: 时间复杂度: N ∗ l o g 2 ( K ) N*log2(K) Nlog2(K)
最大的前k个,则建立为k的小根堆
最小的前k个,则建立为k的大根堆
在这里插入图片描述

5:Java的优先级队列

Java中的堆(优先级队列)

public class PriorityQueue<E> extends AbstractQueue<E>
    implements java.io.Serializable 

PriorityQueue 底层为小根堆,且插入的数据类型必须可以进行大小比较,不然第二次插入时会报错。

PriorityQueue 的初始化大小为11

//构造方法
public PriorityQueue(Comparator<? super E> comparator) {
        this(DEFAULT_INITIAL_CAPACITY, comparator);
    }
//常量为11
private static final int DEFAULT_INITIAL_CAPACITY = 11;

扩容机制:

小于64则2倍扩容+2,大于64返回1.5倍扩容

如果扩容后超过MAX_ARRAY_SIZE则按照MAX_ARRAY_SIZE扩容

 private void grow(int minCapacity) {
        int oldCapacity = queue.length;
        // Double size if small; else grow by 50%
        int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                         (oldCapacity + 2) :
                                         (oldCapacity >> 1));
        // overflow-conscious code
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        queue = Arrays.copyOf(queue, newCapacity);
    }

Java的优先级队列底层为最小堆,如何更改最小堆为最大堆?

PriorityQueue:底层的两个方法是针对插入元素进行比较的,两种比较方式分别是ComparableComparator

	//针对重写Comparable的比较规则
	private void siftUpComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>) x;
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (key.compareTo((E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = key;
    }

	//针对重写Comparator的比较规则
    private void siftUpUsingComparator(int k, E x) {
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (comparator.compare(x, (E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = x;
    }

Java中comparable和comparator接口详解

comparator:

小根堆实例

public static void main1(String[] args) {
        int[] array=new int[]{1,2,3,4,5,6};
        PriorityQueue<Integer> priorityQueue=new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                //o1-o2也是默认的小根堆
                return o1-o2;
            }
        });
        //找出最大的3个数 建立小根堆 放入数组前3个数字
        for (int i = 0; i < 3; i++) {
            priorityQueue.offer(array[i]);
        }
        //从3下标开始遍历整个数组
        for (int i = 3; i < array.length; i++) {
            //如果当前数字大于堆顶元素则将堆顶元素删除,并放入该元素
            if(array[i]>priorityQueue.peek()){
                priorityQueue.poll();
                priorityQueue.offer(array[i]);
            }
        }
        System.out.println(priorityQueue);//456
    }

大根堆实例

   public static void main(String[] args) {
        int[] array=new int[]{1,2,3,4,5,6};
        PriorityQueue<Integer> priorityQueue=new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                //o2-o1为大根堆
                return o2-o1;
            }
        });
        //找出最小的3个数 建立大根堆 放入数组前3个数字
        for (int i = 0; i < 3; i++) {
            priorityQueue.offer(array[i]);
        }
        //从3下标开始遍历整个数组
        for (int i = 3; i < array.length; i++) {
            //如果当前数字小于堆顶元素则将堆顶元素删除,并放入该元素
            if(array[i]<priorityQueue.peek()){
                priorityQueue.poll();
                priorityQueue.offer(array[i]);
            }
        }
        System.out.println(priorityQueue);//312
    }

comparable:

小根堆实例

	class Data implements Comparable<Object>{
     		int age;

    		public Data(int age) {
        	this.age = age;
    		}

    	@Override
    	public int compareTo(Object o) {
        	if(o instanceof Data){
            	//this-O为小根堆也是默认的
            	return this.age-((Data) o).age;
        		}
        	return 0;
    	}
	}


	public static void main(String[] args) {
        Data data1=new Data(1);
        Data data2=new Data(2);
        Data data3=new Data(3);
        Data data4=new Data(4);

        List<Data> list=new ArrayList<>();
        list.add(data1);
        list.add(data2);
        list.add(data3);
        list.add(data4);


        PriorityQueue<Data> priorityQueue=new PriorityQueue<>();

        priorityQueue.offer(data1);
        priorityQueue.offer(data2);
        //找出age最大的两个Data对象
        for(int i=2;i<list.size();i++){
            if(list.get(i).age>priorityQueue.peek().age){
                priorityQueue.poll();
                priorityQueue.offer(list.get(i));
            }
        }
        while (!priorityQueue.isEmpty()){
            System.out.print(priorityQueue.poll().age+" ");//3 4
        }
    }

大根堆实例

class Data implements Comparable<Object>{
     int age;

    public Data(int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Object o) {
        if(o instanceof Data){
            //O-this为大根堆
            return ((Data) o).age-this.age;
        }
        return 0;
    }
}

public static void main(String[] args) {
        Data data1=new Data(1);
        Data data2=new Data(2);
        Data data3=new Data(3);
        Data data4=new Data(4);

        List<Data> list=new ArrayList<>();
        list.add(data1);
        list.add(data2);
        list.add(data3);
        list.add(data4);


        PriorityQueue<Data> priorityQueue=new PriorityQueue<>();

        priorityQueue.offer(data1);
        priorityQueue.offer(data2);
        //找出age最小的两个Data对象
        for(int i=2;i<list.size();i++){
            if(list.get(i).age<priorityQueue.peek().age){
                priorityQueue.poll();
                priorityQueue.offer(list.get(i));
            }
        }
        while (!priorityQueue.isEmpty()){
            System.out.print(priorityQueue.poll().age+" ");//2 1
        }
    }

总结:

小根堆大根堆
ComparatorO1-O2O2-O1
Comparablethis-OO-this
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值