堆就是用数组实现的二叉树,所有它没有使用父指针或者子指针。堆根据“堆属性”来排序,“堆属性”决定了树中节点的位置。

堆的常用方法:

  • 构建优先队列
  • 支持堆排序
  • 快速找出一个集合中的最小值(或者最大值)

堆属性

堆分为两种:最大堆最小堆,两者的差别在于节点的排序方式。

在最大堆中,父节点的值比每一个子节点的值都要大。在最小堆中,父节点的值比每一个子节点的值都要小。这就是所谓的“堆属性”,并且这个属性对堆中的每一个节点都成立。

例子:

JAVA实现 小根堆_JAVA实现 小根堆

来自数组的树

堆的下标属性

如果 i 是节点的索引,那么下面的公式就给出了它的父节点和子节点在数组中的位置

parent(i) = floor((i - 1)/2)
left(i)   = 2i + 1
right(i)  = 2i + 2

parent(i) =floor((i-1)/2)

left(i) =2i+1

right(i)=2i+2

我们将写公式放到前面的例子中验证一下。

Node

Array index (i)

Parent index

Left child

Right child

10

0

-1

1

2

7

1

0

3

4

2

2

0

5

6

5

3

1

7

8

1

4

1

9

10

堆的基本操作: 

堆一般用数组表示

1.插入(Insert)
以最大堆为栗子,插入其实就是把插入结点放在堆最后面,然后与父亲比较,如果父亲值小于它,那么它就和父亲结点交换位置,重复该过程,直到插入节点遇到一个值比它大的父亲或者它成为树根结点

以最大堆为栗子,在堆中插入值为20的结点(红色结点代表新进入)

JAVA实现 小根堆_数组_02

20明显大于它的父亲结点值,所以和7交换位置,交换后新的父亲值还是比它小,继续交换

JAVA实现 小根堆_JAVA实现 小根堆_03

JAVA实现 小根堆_数组_04

JAVA实现 小根堆_最小堆_05

 

 一步一步与它前面比它小的父亲结点交换位置,最后20成为根结点

最小堆同理,只不过要求父亲结点要比它小,如果父亲结点大于它,就得交换

2.删除(Pop)
删除就是删除最大堆中的最大值或者最小堆中的最小值,也就是树根

以删除最大堆树根为例子,删除其实就是整个堆中少了一个结点,不妨把位于最后面的一个结点当成新的树根,然后与它左右孩子比较,与值最大并且值大于它的孩子进行交换(好好读这句话),直到它的孩子都是小于它的,或者变成树叶

还是用最大堆为例,删除树根20

JAVA实现 小根堆_最小堆_06

JAVA实现 小根堆_JAVA实现 小根堆_07

把最后的结点8提到树根

然后就按说明步骤,找到一个位置,它的孩子都小于它或者他变成树叶

JAVA实现 小根堆_JAVA实现 小根堆_07

JAVA实现 小根堆_JAVA实现 小根堆_09


JAVA实现 小根堆_最小堆_10

堆代码实现:

//最大堆
public class MaxHeap {
    private int[] data;//存放堆数据的数组
    private int size; //当前堆的大小
    private int capacity; //堆的最大容量

    public MaxHeap(int size) {
        data = new int[size];
        this.size = 0;
        this.capacity = size;
    }

    /**
     * 获取某个结点的父结点索引
     *
     * @param index
     * @return
     */
    private int parent(int index) {
        if (index == 0) {
            throw new RuntimeException("根结点没有父结点");
        }

        return (index - 1) / 2;
    }

    /**
     * 获取某个结点的左孩子索引
     *
     * @param index
     * @return
     */
    private int lchild(int index) {
        return (2 * index) + 1;
    }

    /**
     * 获取某个结点的右孩子索引
     *
     * @param index
     * @return
     */
    private int rchild(int index) {
        return (2 * index) + 2;
    }

    //插入元素
    public void insert(int d) {
        if (size == capacity) {
            System.out.println("堆已满");
            return;
        }
        data[size] = d;
        shiftUp(size);
        size++;
    }

    //移除元素
    public int removeMax() {

        if (size == 0) {
            System.out.println("堆已经是空的了!");
            return -1;
        }
        int t = data[0];
        //将最后一个元素放到第一个元素位置
        data[1] = data[size];
        //然后将第一个元素下移到适当位置
        shiftDown(1);
        size--;


        return t;
    }

    //堆删除元素时的元素下移
    private void shiftDown(int i) {
        while ((2 * i + 1) <= size) {
            int j = i * 2 + 1;
            // 让j指向他的孩子结点中的大的那一个
            if (j + 1 <= size && data[j] < data[j + 1]) {
                j = j + 1;
            }
            if (data[i] > data[j]) {
                break;
            }

            //元素下移
            int t=data[i];
            data[i]=data[j];
            data[j]=t;
            i=j;
        }
    }

    private void shiftUp(int index) {

        while (index > 0 && data[index] > data[(index - 1) / 2]) {
            int temp = data[index];
            data[index] = data[(index - 1) / 2];
            data[(index - 1) / 2] = temp;
            index = (index - 1) / 2;
        }

    }


}