[Java DS] 二叉树与堆 (学习总结)

目录

堆:

1.1概念

1.2物理层面上的数组 是如何 和逻辑上的完全二叉树关联起来的

1.3 大小堆

1.4堆的核心操作——向下调整操作(justDown)

1.4.1可以向下调整操作的前提:

1.4.2向下调整的具体操作——只用完全二叉树的视角表示

​1.4.3向下调整方法签名:

1.5建堆操作:

1.6取出优先级队列每次取出集合中最小值

堆:

1.1概念

:一种有特殊用途的数据据结构——用来在一组变化频繁(发生增删查改的频率较高)的数据集中找到最值(最大值或者最小值)。

堆在物理层面上表现为一组连续的数组区间;堆在逻辑上表现为一棵完全二叉树

1.2物理层面上的数组 是如何 和逻辑上的完全二叉树关联起来的

long[] array = {1, 2, 3, 4, 5, 6, 7, 8 ,9 } size = 6 把数组中的前六个看成完全二叉数。

按照层序方式排列出数组中的元素

        完全二叉树中的下标遵循以下规则:双亲的下标记为 parent;左孩子的下标记为 left;右孩子的下标记为 right;忽略左右孩子的下标记为 child。如果我们已知某个 parent 的下标,则:

left = 2 * parent + 1; right = 2 * parent + 2;

        如果已知 child 的下标,无论左右,则:parent = (child - 1)/ 2。根的下标一定是0,完全二叉树中的最后一个(以层序为顺序)结点的下标:size - 1。

数组 -> 完全二叉树:按照层序排列

完全二叉树 -> 数组:处理下标关系

1.3 大小堆

满足任意结点的值都大于其子树中结点的值,叫做大堆。 反之,则是小堆。

1.4堆的核心操作——向下调整操作(justDown)

1.4.1可以向下调整操作的前提:

调整是针对某个位置(某个结点、某个下标)。除了这个结点以及它的两个孩子外,要求完全二叉树的其余部分已经满足堆的性质。

1.4.2向下调整的具体操作——只用完全二叉树的视角表示

long[] array = {2, 5, 1, 7, 13, 3, 5 ,9, 8}    size = 9

 例:要调整的是 2 元素这个位置,也就是[0] 所在位置 (调整成小堆)

明确要调整的结点,是不是叶子结点:是叶子,操作结束(出口一);不是叶子,进行下一步

比较要调整结点的左右两个孩子的值,找到其最值(小堆就是找最小值)

   根据 2 所在的下标,找到左孩子的下标和右孩子的下标 left = 2 * parent + 1;right = 2 * parent + 2

注意:2 所在的结点,一定不是叶子。由于我们这棵树是完全二叉树,不可能出现只有右孩子没有左孩子的结点。2 所在的结点可能有两种情况 只有左孩子 或者 左右孩子都有。

让左右孩子的最值 和 要调整的结点的值进行比较。 结点的值 <= 孩子的最值;结点的值 > 孩子的最值

交换结点的值 和 孩子的最值

虽然 1,5,2之间符合堆的性质了,但把 2 交换下来了,所以 2,3 ,5之间可能破坏堆的性质,所以再对 [2] 下标处再次进行判断操作,回到这一步进行再次的循环,如果没有破坏堆的性质,则结束操作(出口二)

1.4.3向下调整方法签名:

 1.名称:shiftDown / adjustDown / heapify

 2.方法的返回值类型:没有调整失败的情况,也不需要告诉调用者任何结论的内容,使用 void 

 3.参数列表:

        1.要调整的堆(满足可调整的前提)堆逻辑上是完全二叉树,但物理上是一个数组。所以在代码中直接表现为一个数组即可(带上size),如果不带size ,则视为把整个数组看成堆。long[] array,int size

        2.要调整的位置——表现为某个结点——物理上是数组——要调整位置的下标  int index

 4.public

 5.是不是属于某个对象? 不属于,所以可以使用static

public class HeapTest {
    /**
     * 小堆的向下调整,要求满足向下调整的前提
     * @param array 堆所在的数组
     * @param size  前 size 个元素视为堆中的元素
     * @param index 要调整位置的下标
     */

    public static void shiftDown(long[] array, int size, int index) {
        while (true) {

            //1.判断 index 所在位置是不是叶子
            // 没有左孩子一定就是叶子了
            int left = index * 2 + 1;
            if(left >= size){
                //越界  没有左孩子  是叶子  调整结束
                return;// 循环的出口一:走到的叶子的位置
            }

            // 2. 找到两个孩子中的最小值
            // 先判断有没有右孩子
            int right = left + 1;
            int min = left;
            if(right <size && array[right] < array[left]){
                // 有右孩子为前提的情况下,然后右孩子的值 < 左孩子的值
                // right < size 必须在 array[right] < array[left] 之前,不能交换顺序
                // 因为先得确定有右孩子,才有比较左右孩子的意义           
                min = right;     
            }

            // 3. 将最值和当前要调整的位置进行比较,判断是否满足堆的性质
            if(array[index] <= array[min]){
                // 当前要调整的结点的值 <= 最小的孩子值;说明这里也满足堆的性质了
                //所以,调整结束
                return;    // 循环的出口二:循环期间,已经满足堆的性质了
            }

            // 4. 交换两个值
            long temp = array[index];
            array[index] = array[min];
            array[min] = temp;

            // 5. 再对 min 位置重新进行同样的操作(对 min 位置进行向下调整操作)
            index = min;
        }
    }

    public static void main(String[] args) {
        long[] array = { 27, 15, 19, 18, 28, 34, 65, 49, 25, 37 };
        shiftDown(array, array.length, 0);
    }
}

1.5建堆操作:

帮助我们将一个完全无规则的数组(完全二叉树)构建成堆

  /**
     * 创建小堆:从一个无规则数组开始,经过调整,得到一个小堆
     * @param array 存储堆元素的数组
     * @param size  前size 元素视为堆中元素
     */
    public static void createHeap(long[] array,int size){
        // 从最后一个非叶子结点的双亲开始
        // 最后一个结点的下标一定是:size - 1
        // 他的双亲一定是: (size - 2) / 2
        // 从后往前遍历,直到根也被向下调整
        for(int  i = (size - 2) / 2 ;i >= 0;i--){
            shiftDown(array,size,i);
        }
    }

现实中,我们一般把这组需要不断找到最值变动频繁的数据集称为优先级队列——背后其实就是

1.6取出优先级队列每次取出集合中最小值

Collection(I) -> Queue(I) - > PriorityQueue(C)

offer(...) 向队列中放入元素        poll(...) 取出队列中的元素(取出来的是优先级队列中最小的元素)        peek(...) 查看最小元素,不删除

前提:优先级队列中的元素类型要求具备比较能力:元素类型本身是 Comparable;通过构造方法传入一个Compara。

public class Dome1 {
    public static void main(String[] args) {
        // 使用 Long 类型作为优先级队列的元素类型
        // 由于 Long 类型本身具备比较能力(Long 本身实现了 Comparable)
        PriorityQueue<Long> pq = new PriorityQueue<>();
        // 乱序的放入元素
        pq.offer(3L);
        pq.offer(5L);
        pq.offer(1L);
        pq.offer(4L);

        // 查看队首元素 —— 最小的元素
        Long peek = pq.peek();
        System.out.println(peek);

        System.out.println("==========");
        while(!pq.isEmpty()){
            Long poll =pq.poll();
            System.out.println(poll);
        }
    }

}

1.7取出优先级队列每次取出集合中的最大值

public class Dome2 {
    public static void mian(String[] args){
        // 如果我们想让优先级队列每次取出集合中的最大值,该如何操作?
        // 元素类型虽然支持比较能力,但其比较能力不是我们需要的方式,同样通过 Comparator 解决
         PriorityQueue<Long> pq = new PriorityQueue<>((o1, o2) -> -o1.compareTo(o2));
        
        // 乱序的放入元素
        pq.offer(3L);
        pq.offer(5L);
        pq.offer(1L);
        pq.offer(4L);

        // 查看队首元素 —— 最大的元素
        Long peek = pq.peek();
        System.out.println(peek);

        System.out.println("==========");
        while(!pq.isEmpty()){
            Long poll =pq.poll();
            System.out.println(poll);
        }
    }
}

对于pq 队列的写法: 

 PriorityQueue<Long> pq = new PriorityQueue<>((o1, o2) -> -o1.compareTo(o2));

也可以写成

PriorityQueue<Long> pq = new PriorityQueue<>(new Comparator<Long>() {
     @Override
     public int compare(Long o1, Long o2) {
          return -o1.compareTo(o2);        // 按照 Long 的原有比较逻辑去比较
      }
});
PriorityQueue<Long> pq = new PriorityQueue<>((Long o1, Long o2) -> 
       return -o1.compareTo(o2);
 });
PriorityQueue<Long> pq = new PriorityQueue<>((Long o1, Long o2) -> -o1.compareTo(o2));
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值