堆【算法】

时间:2019-11-07
本文章向大家介绍堆【算法】,主要包括堆【算法】使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

堆分为两种:最⼤堆和最⼩堆,两者之间的差别在于节点的排序⽅式。

  • 最⼤堆:⽗节点的值⽐每⼀个字节点的值都⼤。
  • 最⼩堆:⽗节点的值⽐每⼀个字节点的值都⼩。
  • 堆常常被当作优先队列来⽤,因为可以快速的访问到“最重要”堆元素.

堆和普通树的区别

堆不能取代⼆叉搜索树,它们之间有相似之处也有⼀些不同。

  • 节点的顺序:在⼆叉搜索树中,左⼦节点必须⽐⽗节点⼩,右⼦节点必须⽐⽗节点⼤。⽽堆并⾮如此。
  • 内存占⽤:普通树占⽤的内存空间⽐它们存储的数据要多。你必须为节点对象以及左/右节点指针分配• 额外多内存。⽽堆仅仅使⽤⼀个数组进⾏存储,且不⽤指针。
  • 平衡:⼆叉搜索树必须是“平衡”堆情况下,其⼤部分操作的复杂度才能达到O(log n)。你可以按任意顺序位置插⼊/删除数据,或者使⽤AVL树或者红⿊树,但是堆中实际上不需要整棵树都是有序的。我们只 需要满⾜堆属性即可,所以在堆中平衡不是问题。因为堆中的数据的组织⽅式可以保证O(log n)的性 能。。
  • 搜索:在⼆叉树中搜索会很快,但是在堆中搜索会很慢。在堆中搜索不是第⼀优先级因为使⽤堆的⽬的是将最⼤(或者最小)的节点放在最前面,从而快速的进行相关插入、删除操作。

⽤数组存储堆

对于任意⼀个堆,如下图,⾃上⽽下,从左到右把堆中的数据存放到数组⾥。

现在的问题是:⽤数组存储堆,那么就⽤不了指针,那怎么找到某⼀个节点的⽗节点和⼦节点呢?

对任意⼀个节点,i是他的索引,那么:

  • 其父节点:\(floor((i-1)/2)\)
  • 其左右子节点:\(2*i+1, 2*i + 2\)

如何快速查找到最后一个非叶子节点:

  • \((n-1)/2\)

代码

建堆

void make_heap(int* array, int size)
{
    for(int i=(size-1)/2; i>=0; --i)
    {
        // 从索引(size-1)/2 开始,调整堆
        adjust_heap(array, i, size);  
    }
}

void adjust_heap(int* array, int node, int size)
{
    int left = 2 * node + 1;
    int right = 2 * node + 2;
    int max = node;
    if(left < size && array[left] > array[max]){
        max = left;
    }
    if(right < size && array[right] > array[max])
    {
        max = right;
    }
    if(node != max)
    {
        swap(array[max], array[node]);
        adjust_heap(array, max, size);
    }
}

用堆排序数组

已知有一个最大堆,对数组进行非递减排序:
将堆的顶部,与最后一个元素交换。此时除了最后一个元素外,剩下元素所组成的树已经不是堆了。(因为此时顶部的元素可能比较小)。所以,要将剩下的元素通过 adjust函数调整成堆。

for(int i=size-1; i>=0; i--)
{
    swap(array[0], array[i]);
    adjust_heap(array, 0, i);
}

时间复杂度

  • 堆排序时间复杂度:\(O(n log n)\)
  • 建堆的时间复杂度:\(O(n log n)\)
  • 调整堆的复杂度:\(O(log(n))\)

原文地址:https://www.cnblogs.com/shenhaojing/p/11812455.html