定义

堆是一种特别的树状结构,我们首先来看看维基百科的上定义。

(英语:Heap)是计算机科学中的一种特别的树状数据结构。若是满足以下特性,即可称为堆:“给定堆中任意节点 P 和 C,若 P 是 C 的母节点,那么 P 的值会小于等于(或大于等于) C 的值”。若母节点的值恒小于等于子节点的值,此堆称为最小堆(min heap);反之,若母节点的值恒大于等于子节点的值,此堆称为最大堆(max heap)。在堆中最顶端的那一个节点,称作根节点(root node),根节点本身没有母节点(parent node)。

总结来说,堆是一个完全二叉树,最多只有两个子节点,并且必须保证根节点是最大的值或者最小的值,所以对于一个堆而言,根节点是最大的值或者是最小的值。

存储

因为堆是一棵完全二叉树,所以使用数组可以高效的存储数据。对于使用数组存储的方式,有两个性质非常关键,对于一个非根节点的节点i来说,如果它的下标为k,那么它的父节点的下标为 (k -1)/2,子节点的下标为2 *k +12 *k + 2

基本操作

  • 堆化
  • 插入
  • 删除
堆化

对于任意一个无序数组而言,实现堆化的步骤如下,我们以构建一个最大堆为例:

  1. 找到第一个非叶子节点,对这个节点的左子树和右子树与节点比较,将大的元素放置到父节点的位置,直到父节点已经是最大值或者改节点已经是叶子节点。
  2. 依次对所有的非叶子节点进行第一步的操作
private Heap(int[] data)
{
    int last_p = data.length - 1;
    //i是第一个非叶子节点
    for (int i = (last_p - 1) / 2; i >= 0; i--)
    {
        heapify(data, i, last_p);
    }
    this.data = data;
}

private void heapify(int[] data, int start, int end)
{
    int value = data[start];
    int current_p = start;
    //左孩子
    int left_p = 2 * current_p + 1;
    while (left_p <= end)
    {
        //右节点大于左节点
        if (left_p < end && data[left_p] < data[left_p + 1])
        {   
            //移动位置到右节点
            left_p++;
        }
        //当前的父节点已经是最大值
        if (data[left_p] < value)
        {
            break;
        }
        else
        {   //子节点上移到父节点的位置
            data[current_p] = data[left_p];
            current_p = left_p;
            left_p = current_p * 2 + 1;
        }
    }
    data[current_p] = value;
}
插入

插入的算法如下:.将新增的节点放在数组的最末端,也就是数组的最后一个位置。然后计算出父节点的位置,让当前节点与父节点比较,如果父节点比较小,交换位置。重复上述步骤,直到父节点大于当前节点或者当前节点是根节点。

public int insert(int value)
{
    checkSize();
    int position = data.length - 1;
    data[position] = value;

    int current_p = position;
    int parent_p = (position - 1) / 2;
    while (current_p > 0)
    {
        if (data[parent_p] > value)
        {
            break;
        }
        else
        {
            data[current_p] = data[parent_p];
            current_p = parent_p;
            parent_p = (current_p - 1) / 2;
        }
    }
    data[current_p] = value;
    return position;
}
删除

删除的算法如下:将数组最后一个节点与当前需要删除的节点替换,删除最后一个节点,对于替换的节点来说,相当于进行了依次插入操作,不过这次是从上往下的插入。算法与remove相同,也是比较最大的值进行替换,直到不满足条件为止。

删除根节点
public int remove()
{
    int root = data[0];
    int last = data[data.length - 1];
    data[0] = last;
    data = Arrays.copyOf(data, data.length - 1);
    if (data.length == 0)
    {
        return root;
    }
    //从顶点开始调整
    int current_p = 0;
    int l = current_p * 2 + 1;
    int value = data[current_p];
    while (l < data.length)
    {
        if (l < data.length - 1 && data[l] < data[l + 1])
        {
            l++;  //右孩子大
        }
        if (data[l] <= value)
        {
            break;
        }
        else
        {
            data[current_p] = data[l];
            current_p = l;
            l = current_p * 2 + 1;
        }
    }
    data[current_p] = value;
    return root;
}

应用

堆应用比较多的一个用处就是堆排序,对于一个数组进行堆化之后,第一个数组是最大的值,然后交换第一个数和最后一个数,这样最大的数就落在了最后一个数组的位置。缩小数组,重复之前的步骤,最后就得到了一个排序好的数组。

int[] data = new int[] {20, 40, 80, 33, 111, 47, 21, 90, -1};
HeapSort hs = new HeapSort();
hs.heap_array(data, data.length - 1);

for (int i = 0; i < data.length - 1; i++)
{
    int tmp = data[0];
    data[0] = data[data.length - 1 - i];
    data[data.length - 1 - i] = tmp;
    hs.heap_array(data, data.length - 2 - i);
}
System.out.println(Arrays.toString(data));

代码地址: https://gitee.com/devnew/Algo...


summerpxy
44 声望1 粉丝