数据结构 - 堆

二叉堆

二叉堆是一颗二叉树,不必定是满二叉树,但必定是彻底二叉树,彻底二叉树指的是容许有缺失的部分,但缺失节点的部分必定是右下侧。api

堆中任意一个节点的值必定大于等于他的孩子节点。这个特色也说明了,根节点的元素是最大的元素。这样的堆叫作最大堆,相反若是任意一个节点小于等于他的孩子节点,那就是最小堆。这里要注意:不是越贴近根节点的值就越大。数组

因为最大堆是彻底二叉树,那么最大堆就能够用数组来做为底层实现。如图所示,编号1-10就表明一个数组的索引,或者用0-9也OK。
image.png数据结构

若是用1-10索引来表示的话,好比41这个元素,索引为2,他的左孩子索引就是4(i x 2) ,右孩子节点就是5 (i x 2 + 1)。
相反,知道左孩子或者右孩子,求父节点直接除2就能够了,4 / 2 ,5 / 2强转为int以后都是2。
若是用0-9索引表示这些元素,已知父亲节点索引为1,左孩子节点就是4( i x 2 + 1),右孩子节点是5 (i x 2 + 2),知道左孩子或右孩子索引3和4,父节点索引就是1(i - 1)/ 2。性能

知道了这些特色,开始用代码实现一个最大堆spa

//使用数组看成底层数据结构
public MyArray<E> myArray;
 
public MaxHeap(int capacity){
    myArray = new MyArray<>(capacity);
}

public MaxHeap(){
    myArray = new MyArray<>();
}

//父节点的索引 (i-1)/2
public int parent(int index){
    if(index == 0)
        throw new IllegalArgumentException("index:0,doesn't hava parent.");
    return (index - 1) / 2;
}

//左孩子节点 i\*2+1
public int leftChild(int index){
    return index \* 2 + 1;
}

//右孩子节点 i\*2+2
public int rightChild(int index){
    return index \* 2 + 2;
}

添加一个节点

image.png

添加节点的时候,要知足最大堆的性质须要听从两点,第一就是要添加到数组末尾的位置,知足彻底二叉树的特性。第二就是知足每个节点要比他的孩子节点要大的特性,52添加到末尾后,要不断的向上寻找父节点,和父节点比较大小后,和父节点元素作交换操做,找到本身合适的位置,咱们把这个操做称为ShiftUp(上浮)code

public void add(E e){
    myArray.addLast(e);
    shiftUp(myArray.getSize() - 1);
}

private void shiftUp(int index){
    //把当前index的值和父节点的值作比较,小于父节点就向上移动
    while(index > 0 && myArray.getByIndex(index).compareTo(myArray.getByIndex(parent(index))) > 0)
    {
        //对两个索引交换
        myArray.swap(index,parent(index));
        
        //从新标记index位置,用于下次交换逻辑
        index = parent(index);
    }
}

取出根元素

image.png
若是想取出根元素62,取出后索引为0的位置就为空了,为了知足最大堆的结构,就须要从新组织这颗二叉树。咱们采用将最后一个元素放到根元素的位置,而后再根据这个元素,不断向下去判断本身是否是比两个孩子节点都大,若是大就不须要移动。若是比孩子节点小,就向下挪动,由于有两个孩子,还须要比较这两个孩子谁更大,谁大就和谁交换。这个过程叫作shift down(下沉)。
image.pngblog

//取出最大的元素
public E extractMax()
{
    E max = findMax();
    myArray.swap(0,myArray.getSize() - 1);
    myArray.removeLast();
    shiftDown(0);
    return max;
}
//查看一下最大元素是谁
public E findMax()
{
    if(myArray.getSize() == 0)
        throw new IndexOutOfBoundsException("heap is empty.");
    return myArray.getByIndex(0);
}
private void shiftDown(int index) {
    //若是一个元素的左孩子大于了总数量,那么就结束
    while (leftChild(index) < myArray.getSize()) {
        //判断有右孩子的状况下,在与左孩子作比较
        int swapIndex = leftChild(index); //默认是左孩子大
        ////若是有右孩子而且右孩子比左孩子大
        if (swapIndex + 1 < myArray.getSize() &&
                myArray.getByIndex(swapIndex).compareTo(myArray.getByIndex(swapIndex + 1)) < 0) {
            swapIndex = rightChild(index);
        }
        //堆的性质:只要当前的父亲节点大于两个孩子节点便可,不须要一直往下看,直接结束循环
        if (myArray.getByIndex(index).compareTo(myArray.getByIndex(swapIndex)) > 0) {
            break;
        }
        myArray.swap(index, swapIndex);//和子节点交换元素
        index = swapIndex;//从新标记index的位置,便于下次循环逻辑
    }
}

Replace操做

replace就是替换根元素的操做,简单的思路能够是先取出根元素,也就是上面实现的extractMax()方法,而后在添加一个元素。但这样至关于两个O(logn)的操做。还有一种实现思路就是直接将根元素替换掉,而后作一个shift down就解决了。索引

//取出根元素,并替换成最新元素
public E replace(E e) {
    E e1 = findMax();
    myArray.set(0,e);
    shiftDown(0);
    return e1;
}

Heapify操做

heapify是将一个数组转换为一个堆的过程,这个实现彻底能够用添加操做来完成,可是复杂度是O(nlogn)级别的,若是将一个数组一次性的放到堆中,找到最后一个非叶子节点,从这个节点向前遍历,将每个元素作一个下沉操做就能够了。那如何定位最后一个非叶子节点呢?很简单就是最后一个叶子节点的父节点。
image.pngci

public MaxHeap(E[] arr){
    //将一个数组直接转换为最大堆
    myArray = new MyArray<>(arr);
    //parent(arr.getSize - 1)是除了叶子节点之外,开始向上遍历
    for(int x = parent(myArray.getSize() - 1); x >= 0 ; x--) {
        shiftDown(x);
    }
}

D叉堆

二叉堆是一个节点有两个儿子,D叉堆就是一个节点有D个儿子,这样树的深度就会变短。用法和二叉堆差很少,只不过元素在上浮和下沉的时候须要多判断一下。至于D取值为多少性能最好,仍是要实战的试一下的。
image.pngrem

相关文章
相关标签/搜索