堆(Heap) 浅析


一、堆的概念

在这里插入图片描述

  • 逻辑上是一棵完全二叉树
  • 物理上保存在数组中
  • 满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆
  • 满足任意结点的值都小于其子树中结点的值,则是小堆,或者小根堆,或者最小堆
  • 堆的基本作用是,快速找集合中的最值

二、堆的操作

向下调整(以小堆为例)

在这里插入图片描述
思路:伪代码分析

public static void 向下调整(int[] array, int size, int index) {
	1.判断 index 对应的下标是不是叶子结点
		如果是叶子结点,直接 return
		
	2.找到两个孩子中最小的
	
	3.最小的孩子的值和 index 对应位置的值比较
		<				接着进行下一步
		==		>		满足堆的性质,直接 return
		
	4.交换最小的孩子的值和 index 的值
	
	5.把最小的孩子视为 index,循环回去(从步骤 1,继续往下走)
}

引出的问题:

  1. 怎么判断 index 对应的位置是不是叶子结点 ?
    在这里插入图片描述
  2. 怎么找最小的孩子?
    在这里插入图片描述
  3. 小堆是有序的吗?有序的是小堆吗?
    小堆不一定是有序的,但有序的一定是小堆。

代码实现:

public class HeapTest {
    public static void shiftDown(int[] array, int size, int index) {
        while (true) {
            //1.判断 index 对应的下标是不是叶子结点
            int leftIndex = 2 * index + 1;
            if(leftIndex >= size) {
                return;
            }

            //2.找到两个孩子中最小的
            int minIndex = leftIndex;
            int rightIndex = leftIndex + 1;
            if(rightIndex < size && array[rightIndex] < array[leftIndex]) {
                minIndex = rightIndex;
            }

            //3.最小的孩子的值和 index 对应位置的值比较
            if(array[index] <= array[minIndex]) {
                return;
            }

            //4.交换最小的孩子的值和 index 的值
            int temp = array[index];
            array[index] = array[minIndex];
            array[minIndex] = temp;

            //5.把最小的孩子视为 index,循环回去(从步骤 1,继续往下走)
            index = minIndex;
        }
    }
}

向上调整(以大堆为例)

思路:伪代码分析

public static void adjustUp(int[] array,int size,int index) {
	1.判断 index 是不是树的根,如果是根,调整结束
	2.找到 index 的父结点
	3.比较父结点的值和 index 的值
	4.只要父结点的值 <= index,调整结束
	5.交换父结点和 index 的值
	6.把父结点看做 index,继续这个大循环
}

代码实现:

public class HeapTest {
    public static void adjustUp(int[] array,int size,int index) {
        while (true) {
            //1.判断 index 是不是树的根,如果是根,调整结束
            if(index == 0) {
                break;
            }
            
            //2.找到 index 的父结点
            int parentIndex = (index - 1) / 2;
            
            //3.比较父结点的值和 index 的值
            //4.只要父结点的值 <= index,调整结束
            if(array[parentIndex] <= array[index]) {
                break;
            }
            
            //5.交换父结点和 index 的值
            int temp = array[index];
            array[index] = array[parentIndex];
            array[parentIndex] = temp;
            
            //6.把父结点看做 index,继续这个大循环
            index = parentIndex;

        }
    }
}

建堆

思路:伪代码分析

public static void createHeap(int[] array, int size) {
	size-1	树最后一个结点的下标
	
	((size-1)-1)/2[(size-2)/2,0] 不断地进行向下调整
}

代码实现:

public static void createHeap(int[] array, int size) {
	//找到层序遍历的最后一个结点下标
	int lastIndex = size - 1;
	
	//找到最后一个结点的父节点的下标
	int lastParentIndex = lastIndex - 1 / 2;
	
	//从[(size-2)/2,0] 不断地进行向下调整
	for(int i = lastParentIndex; i >= 0; i++) {
		shiftDown(array,size,i);
	}
}

三、堆的应用

堆的应用有挺多:优先级队列、堆排序、TopK

堆排序TopK就先不浅析了,会写博客专门去分析理解,在这里就简要分析一下。

堆排序

分为两个步骤:

  1. 用当前需要排序的数据构建一个堆
  2. 不断的弹出当前堆的堆顶元素,因为小顶堆的堆顶元素一定是最小的,即可以用于排序。堆排序的本质就是,把数据构建成堆之后,弹出堆顶元素,然后互换堆顶元素和最后一个元素,不断对当前堆进行自顶向下的堆的调整,然后继续弹出。

TopK

TopK一般解决的是求解前K个最大或者最小的元素,或第K个最大或最小的元素。

拿到这类问题,我们的第一想法肯定是排序求解。但排序还会浪费一定的资源排序前K个元素,为了节省计算资源,我们需要思考的是怎么优化,聪明的你肯定想到了,是不是这最大的k个元素也不需要排序呢?

为此,我们需要构建包含K个元素的小顶堆这个小顶堆用于存储,当前最大的k个元素。接着我们需要从第 k+1个元素开始扫描,如果被扫描的元素大于堆顶,则替换堆顶的元素,并调整堆,以保证堆内的k个元素,总是当前最大的k个元素。扫描完所有n-k个元素,最终堆中的k个元素,就是优化后求的TopK。


优先级队列(Priority Queue)

入队列操作

过程(以大堆为例):

在这里插入图片描述


出队列操作

过程(以大堆为例):
在这里插入图片描述


代码实现

public class MyPriorityQueue {
    private Integer[] array;
    private int size;

    public MyPriorityQueue() {
        array = new Integer[100];
        size = 0;
    }

    public Integer element() {
        if(size == 0) {
            throw new RuntimeException("空的");
        }
        return array[0];
    }

    /**
     * 入队列
     */
    public void add(Integer e) {
        array[size] = e;
        size++;
        adjustUp(size-1);
    }

    public void adjustUp(int index) {
        while (true) {
            if(index == 0) {
                break;
            }
            int parentIndex = (index - 1) / 2;
            if(array[parentIndex] <= array[index]) {
                break;
            }
            int temp = array[index];
            array[index] = array[parentIndex];
            array[parentIndex] = temp;
            index = parentIndex;
        }
    }

    /**
     * 出队列
     */
    public Integer remove() {
        if(size == 0) {
            throw new RuntimeException("空的");
        }

        int e = array[0];
        array[0] = array[size - 1];
        size--;

        adjustDown(0);
        return e;

    }

    private void adjustDown(int index) {
        while (true) {
            int leftIndex = 2 * index + 1;
            if(leftIndex >= size) {
                return;
            }
            int minIndex = leftIndex;
            int rightIndex = leftIndex + 1;
            if(rightIndex < size && array[rightIndex] < array[leftIndex]) {
                minIndex = rightIndex;
            }

            if(array[index] <= array[minIndex]) {
                return;
            }

            int temp = array[index];
            array[index] = array[minIndex];
            array[minIndex] = temp;

            index = minIndex;
        }
    }
}

测试类:

public class PriorityQueueDemo {
    public static void main(String[] args) {
        MyPriorityQueue queue = new MyPriorityQueue();
        queue.add(3);
        queue.add(5);
        queue.add(2);
        queue.add(7);

        System.out.println(queue.remove());//2
        System.out.println(queue.remove());//3
        System.out.println(queue.remove());//5
        System.out.println(queue.remove());//7
    }
}

运行结果:
在这里插入图片描述


  • 12
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值