手写堆(小顶堆)
堆使用数组存储,下标从1开始(下标从0开始也可以)。
下标为u
的节点:
- 左子节点下标为:
2 * u
(下标从0开始,左子节点则为2 * i + 1
) - 右子节点下标为:
2 * u + 1
(下标从0开始,左子节点则为2 * i + 1
) - 父节点下标为:
u / 2
(下标从0开始,父节点则为(u - 1) / 2
)
up操作
如果当前结点的值小于父节点,就与父节点交换,重复这一步骤,直到当前节点的值大于父节点。
void up(int u)
{
while(u / 2 && h[u / 2] < h[u]) // 存在父节点并且父节点的值小于当前节点的值
{
swap(h[u], h[u / 2]);
u /= 2; // 再对父节点进行up
}
}
down操作
将当前节点与两个子节点(如果有两个子节点)中的较小值交换,再对那个交换后的子节点进行down
操作。
void down(int u)
{
int t = u; // t记录两个子节点中较小者
if(u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
if(u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if(t != u)
{
swap(h[u], h[t]); // 与两个子节点中的较小者交换
down(t); // 对交换后的子节点继续进行down
}
}
建立堆
建立堆有两种方式:
-
一个一个插入,一次插入的时间复杂度为
O(logn)
,所以总的时间复杂度为O(nlogn)
。 -
对下标为
n / 2 ~ 1
的节点依次进行down
操作,时间复杂度为O(n)
。for(int i = n / 2; i; --i) down(i); // 这种建立堆的方式比较快
插入一个数
先在数组最后添加一个数,将堆的大小(即数组的大小+1),再对其进行up
操作即可。
h[++cnt] = x; // 在数组最后添加一个数,将堆的大小(即数组的大小+1)
up(cnt); // 对其进行up操作
删除堆中最小的元素
因为堆中的最小元素就是数组中的第一个元素(堆的性质),所以只要将数组的最后一个元素赋给第一个元素,将堆的大小(即数组的大小-1),再对第一个元素进行down
操作即可。
h[1] = h[cnt--]; // 将数组的最后一个元素赋给第一个元素
down(1); // 对第一个元素进行down操作