二叉堆

Posted luozhiyun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二叉堆相关的知识,希望对你有一定的参考价值。

二叉堆是一个数组,它可以被看成一个近似的完全二叉树,树上的每一个节点对应数组中的一个元素。除了最底层外,该树是完全充满的,而且是从左向右填充。二叉堆可以有两种形式:最大堆和最小堆,这里我主要讲解最大堆。最大堆的定义是:堆中某个节点的值总是不大于其父节点的值。

                  62
               /                   41     30
             /      /             28   16 22  13
           /    /
         19  17 15
0   1   2   3   4   5   6   7   8   9 
62  41  30  28  16  22  13  19  17  15

当我们用二叉堆表示上面的数组的时候,我们可以知道
父节点:parent(i) = i /2;
左节点:left child (i) = 2 i + 1
右节点:right child(i) = 2
i + 2

首先我们实现一下堆的交换方法swap和父子节点的方法

public void swap(int i,int j){
    E t = data[i];
    data[i] = data[j];
    data[j] = t;
}
  // 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引
private int parent(int index){
    if(index == 0)
        throw new IllegalArgumentException("index-0 doesn't have parent.");
    return (index - 1) / 2;
}

// 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引
private int leftChild(int index){
    return index * 2 + 1;
}

// 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引
private int rightChild(int index){
    return index * 2 + 2;
}

二叉堆的核心是”添加节点”和”删除节点”,理解这两个算法,二叉堆也就基本掌握了。下面对它们进行介绍。

向堆中插入一个元素

从最后一个节点的地方插入元素,然后和父节点比较并进行交换位置。如果堆的有序状态因为某个节点变得比它的父节点更大而被打破,那么我们就需要通过交换它和它的父节点来修复堆。
比如向堆中插入52:

            62
         /             41     30
        /      /      
       28   16 22  13
      /    /      19  17 15 52
    在最后添加节点52,发现52大于16,于是和16交换位置
----->
            62
         /             41     30
        /      /      
       28   52 22  13
      /    /      19  17 15 16
     52再和自己的父节点比较,发现52大于41,再和41交换位置
----->
            62
         /             52     30
        /      /      
       28   41 22  13
      /    /      19  17 15 16   
     当52发现小于自己的父节点的时候,停止交换,插入完成
----->    
            62
         /             52     30
        /      /      
       28   41 22  13
      /    /      19  17 15 16  

我们用代码来表示上面的过程

    // 向堆中添加元素
    public void add(E e){
        data.addLast(e);
        siftUp(data.getSize() - 1);
    }

    private void siftUp(int k){
        while(k > 0 && data[parent(k)].compareTo(data[k)] < 0 ){
            swap(k, parent(k));
            k = parent(k);
        }
    }

删除堆中最大元素

我们从数组顶端删去最大的元素并将数组的最后一个元素放到顶端,减小堆的大小并让这个元素下沉到合适的位置。


            62
         /             52     30
        /      /      
       28   41 22  13
      /    /      19  17 15 16
首先将最后一个元素放到顶端    
            16
         /             52     30
        /      /      
       28   41 22  13
      /    /   
     19  17 15  
然后和左右两个子节点比较,和并和子节点中较大的节点交换位置,16和52交换
            52
         /             16     30
        /      /      
       28   41 22  13
      /    /   
     19  17 15  
然后一直交换,16和41交换,然后发现16比它子节点15大,交换完毕
             52
         /              41       30
       /       /      
      28   16  22  13
     /     /   
    19  17 15  

下面我们用代码实现一下这个过程

    // 看堆中的最大元素
    public E findMax(){
        if(data.getSize() == 0)
            throw new IllegalArgumentException("Can not findMax when heap is empty.");
        return data[0];
    }

    // 取出堆中最大元素
    public E extractMax(){

        E ret = findMax();

        swap(0, data.getSize() - 1);
        data[data.getSize() - 1]= null;
          size --;
        siftDown(0); 
        return ret;
    }

    private void siftDown(int k){
        while(leftChild(k) < getSize()){
            int j = leftChild(k); // 在此轮循环中,data[k]和data[j]交换位置
            if( j + 1 < getSize() &&
                    data[j + 1].compareTo(data[j]) > 0 )
                j ++;
            // data[j] 是 leftChild 和 rightChild 中的最大值
            if(data[k].compareTo(data[j]) >= 0 )
                break;
            swap(k, j);
            k = j;
        }
    }

由于数组的大小固定之后就不能改变,所以我们这里使用动态数组ArrayList来代替数组的实现。下面放出完整代码:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * @author luozhiyun on 2019-04-07.
 */
public class MaxHeap<E extends Comparable<E>> {

    private List<E> list;

    public MaxHeap(){
        list = new ArrayList<>();
    }

    public MaxHeap(int cap) {
        list = new ArrayList<>(cap);
    }

    public MaxHeap(E[] es) {
        list = new ArrayList<>(es.length);
        for (int i = 0; i < es.length; i++) {
            list.add(es[i]);
        }
        for (int i = parent(list.size()-1); i >=0; i--) {
            siftUp(i);
        }
    }

    public int size() {
        return list.size();
    }

    public boolean isEmpty() {
        return list.isEmpty();
    }

    private int parent(int index) {
        return (index - 1) / 2;
    }

    private int leftChild(int index) {
        return index * 2 + 1;
    }

    private int rightChild(int index) {
        return index * 2 + 2;
    }

    public void add(E e) {
        list.add(e);
        siftUp(list.size() - 1);
    }

    private void siftUp(int index) {

        while (index > 0 &&
                list.get(parent(index)).compareTo(list.get(index)) < 0) {
            swap(index, parent(index));
            index = parent(index);
        }
    }

    private void swap(int i,int j) {
        E e = list.get(i);
        list.set(i, list.get(j));
        list.set(j, e);
    }

    public E findMax() {
        return list.get(0);
    }
    //取出堆中最大的元素
    public E extractMax() {
        E max = findMax();

        swap(0, list.size() - 1);
        list.remove(list.size() - 1);
        siftDown(0);
        return max;
    }

    private void siftDown(int index) {

        while (leftChild(index) < list.size()) {

            int l = leftChild(index);
            if (l + 1 < list.size() &&
                    list.get(l).compareTo(list.get(l + 1) )< 0) {
                l++;
            }

            if (list.get(index).compareTo(list.get(l)) >= 0) {
                break;
            }
            swap(index, l);
            index = l;
        }
    }

    public E replace(E e) {
        E max = findMax();
        list.set(0, e);
        siftDown(0);
        return max;
    }
    public static void main(String[] args) {
        int n = 100000 ;
        MaxHeap<Integer> integerMaxHeap = new MaxHeap<>();
        Random random = new Random();
        for (int i = 0; i < n; i++) {
            integerMaxHeap.add(random.nextInt(Integer.MAX_VALUE));
        }

        int[] ints = new int[n];
        for (int i = 0; i < n; i++) {
            ints[i] = integerMaxHeap.extractMax();
        }

        for (int i = 1; i < n; i++) {
            if (ints[i] > ints[i - 1]) {
                throw new IllegalArgumentException("Error");
            }
        }
        System.out.println("Test MaxHeap completed;");
    }
}

以上是关于二叉堆的主要内容,如果未能解决你的问题,请参考以下文章

合并果子(二叉堆)

龙珠游戏(二叉堆)

Java 数据结构 & 算法宁可累死自己, 也要卷死别人 13 二叉堆

Java 数据结构 & 算法宁可累死自己, 也要卷死别人 13 二叉堆

二叉堆(彻底整明白堆排序)

二叉堆