• 二叉堆


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

                      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 - 1)/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;");
        }
    }
    
    
  • 相关阅读:
    递归算法转换为非递归算法的技巧
    22. 平面列表
    14. 二分查找
    那点人生小智慧
    9. Fizz Buzz 问题
    8. 旋转字符串
    6. 合并排序数组:
    归并排序
    远方的她
    微服务体系下如何快速构建一个服务
  • 原文地址:https://www.cnblogs.com/luozhiyun/p/10742308.html
Copyright © 2020-2023  润新知