二叉堆

二叉堆是一個數組,它能夠被當作一個近似的徹底二叉樹,樹上的每個節點對應數組中的一個元素。除了最底層外,該樹是徹底充滿的,並且是從左向右填充。二叉堆能夠有兩種形式:最大堆和最小堆,這裏我主要講解最大堆。最大堆的定義是:堆中某個節點的值老是不大於其父節點的值。java

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;
}

二叉堆的核心是」添加節點」和」刪除節點」,理解這兩個算法,二叉堆也就基本掌握了。下面對它們進行介紹。dom

向堆中插入一個元素

從最後一個節點的地方插入元素,而後和父節點比較並進行交換位置。若是堆的有序狀態由於某個節點變得比它的父節點更大而被打破,那麼咱們就須要經過交換它和它的父節點來修復堆。
好比向堆中插入52:code

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);
        }
    }

刪除堆中最大元素

咱們從數組頂端刪去最大的元素並將數組的最後一個元素放到頂端,減少堆的大小並讓這個元素下沉到合適的位置。rem

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

下面咱們用代碼實現一下這個過程get

// 看堆中的最大元素
    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來代替數組的實現。下面放出完整代碼:io

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;");
    }
}
相關文章
相關標籤/搜索