- 堆是一个完全二叉树
- 每个节点的值都必须大于等于(或小于等于)其左右孩子节点的值
如果每个节点的值都大于等于左右孩子节点的值,这样的堆叫大顶堆;如果每个节点的值都小于等于左右孩子节点的值,这样的堆叫小顶堆。
插入:以大顶堆为例,从叶子结点插入,如果比父级元素大,则与父级元素交换位置,依次类推,直到到达根节点(小顶堆恰好相反)
删除:每次从堆顶删除元素后,需要从子节点中取值补齐堆顶,依次类推,直到叶子节点,就会致使存储堆的数组出现「空洞」,解决办法是将数组中的最后一个元素(最右边的叶子节点)移到堆顶,再重新对其进行堆化
应用场景:
1.优先级队列:在优先级队列中,数据的出队顺序不是先进先出,而是按照优先级来,优先级最高的,最先出队,背后的原理就是不断删除堆顶元素。
2.TopK排行榜:日常开发中,经常遇到类似求销售额Top10,浏览数Top10,点赞数Top10之类的需求,也可以通过堆排序来实现,原理就是维护一个大小为 K 的小顶堆,有新数据进入后,如果值比堆顶元素大,则删除堆顶元素,当前元素插入堆顶,并进行调整,最终这个小顶堆就是 TopK 数据了。
PHP
class Heap
{
private $arr = [];
private $count = 0;
public function push($num)
{
$this->count++;
$this->arr[$this->count] = $num;
$current = $this->count;
$parent = floor($current / 2);
while ($parent > 0 && $this->arr[$parent] < $this->arr[$current]) {
$tmp = $this->arr[$current];
$this->arr[$current] = $this->arr[$parent];
$this->arr[$parent] = $tmp;
$current = $parent;
$parent = floor($current / 2);
}
}
public function pull()
{
if ($this->count == 0) {
return false;
}
$return = $this->arr[1];
$this->arr[1] = $this->arr[$this->count];
$this->count--;
$current = 1;
while (true) {
$next = $current;
if ($current * 2 <= $this->count && $this->arr[$current * 2] > $this->arr[$current]) {
$next = $current * 2;
}
if ($current * 2 + 1 <= $this->count && $this->arr[$current * 2 + 1] > $this->arr[$next]) {
$next = $current * 2 + 1;
}
if ($next == $current) {
break;
}
$tmp = $this->arr[$next];
$this->arr[$next] = $this->arr[$current];
$this->arr[$current] = $tmp;
$current = $next;
}
return $return;
}
public function __toString()
{
return json_encode(array_values($this->a));
}
}
$heap = new Heap;
$data = range(1, 10);
shuffle($data);
foreach ($data as $num) {
$heap->push($num);
}
print_r($heap);
$reData = [];
while ($re = $heap->pull()) {
$reData[] = $re;
}
print_r($reData);
GO
package main
import (
"fmt"
"math"
)
type Heap struct {
arr []int
count int
}
func main() {
heap := newHeap()
for i := 1; i <= 10; i++ {
heap.push(i)
}
fmt.Println(heap.arr)
for {
re,empty := heap.pull()
if empty == true {
break
}
fmt.Println(re)
}
}
func newHeap() *Heap {
return &Heap{
count: 0,
}
}
func (heap *Heap) push(num int) {
heap.count++
heap.arr = append(heap.arr, num)
current := heap.count
parent := int(math.Floor(float64(current) / 2))
for {
if parent == 0 {
break;
}
if heap.arr[parent - 1] >= heap.arr[current - 1] {
break;
}
tmp := heap.arr[parent - 1]
heap.arr[parent - 1] = heap.arr[current - 1]
heap.arr[current - 1] = tmp
current = parent
parent = int(math.Floor(float64(current) / 2))
}
}
func (heap *Heap) pull() (re int, empty bool) {
if heap.count == 0 {
return -1, true
}
re = heap.arr[0]
empty = false
heap.arr[0] = heap.arr[heap.count - 1]
heap.count--
current := 1
var next int
for {
next = current
if current * 2 < heap.count && heap.arr[current * 2 - 1] > heap.arr[current - 1] {
next = current * 2
}
if current * 2 + 1 < heap.count && heap.arr[current * 2] > heap.arr[next - 1] {
next = current * 2 + 1
}
if next == current {
break
}
tmp := heap.arr[current - 1]
heap.arr[current - 1] = heap.arr[next -1]
heap.arr[next - 1] = tmp
current = next
}
return
}