逻辑上是树(并且是完全二叉树),但是存储上顺序存储(数组)而非链式存储。
既然树和数组能够一一对应上,必然遵从某种关系,经分析易得下列结论:
C h i l d l e f t = P a r e n t ∗ 2 + 1 Child_{left}=Parent * 2 + 1 Childleft=Parent∗2+1
C h i l d r i g h t = P a r e n t ∗ 2 + 2 Child_{right}=Parent * 2 + 2 Childright=Parent∗2+2
P a r e n t = ( S o n − 1 ) / 2 Parent = (Son - 1 ) / 2 Parent=(Son−1)/2
当然,上述公式前提是从index = 0
开始存储的。
如果是从index = 1
开始存储的,则是:
int left(int parent){
return 2 * parent;
}
int right(int parent){
return 2 * parent + 1;
}
int parent(int son){
//判断处理无效输入
if (son <= 0){
return -1;
}
return (son - 1) / 2;
}
从index = 1
开始存储最直观的好处就是树的第i
层第一个元素下标index = 2^(i-1)
然后增删改查有个共同点:围绕两个 *** 作进行。
也就是向上调整和向下调整:
-
向上调整:如果这个结点的权值大于它父亲的权值,就交换,重复此过程直到不满足或者到根。
-
向下调整:在该结点的儿子中,找一个最大的,与该结点交换,重复此过程直到底层。
void up(int x) {
while (x > 1 && heap[x] > heap[x / 2]) {
swap(heap[x], heap[x / 2]);
x /= 2;
}
}
void down(int x) {
while (x * 2 <= n) {
int t = x * 2;
if (t + 1 <= n && heap[t + 1] > heap[t]) t++;
if (heap[t] <= heap[x]) break;
std::swap(heap[x], heap[t]);
x = t;
}
}
向上调整结束条件之一是调整元素下标x > 1
,因为根节点i = 1
不用被调整。另一个条件是该节点大于其父节点,满足这俩就将该节点和其父节点交换(显然这是大根堆)
向下调整就略显繁琐一点。先需要取左子节点(用t
保存其下标),然后再看看旁边的右子节点存在与否,存在的话是不是比它大。选择最大的那个子节点,跟父节点比较,符合交换条件的话(即子节点比父节点大)交换。
插入在堆底,删除在堆顶
插入 *** 作就是在最下一层最右边的叶子之后插入,或者说是数组最后一个存有内容的单元后面那个地方插入
插入完未必符合二叉堆的性质,再向上调整就是了。
时间复杂度 O ( l o g n ) O(logn) O(logn)
删除就是删除根节点,或者说是heap[0]
。一般是把根结点和最后一个结点直接交换。然后删除最后一个节点,然后对新的根节点向下调整。
时间复杂度 O ( l o g n ) O(logn) O(logn)
void insertHeap(int key){
n = n + 1;
heap[n] = key;
up(n);
}
void deleteHeap(){
// 删除树根
swap(heap[1], heap[n]);
n = n - 1;
down(1);
}
实现
完整代码如下
#include
using namespace std;
int heap[2048];
int n = 0;
// 获取父节点或者左右子节点的 *** 作
int left(int parent){
return 2 * parent;
}
int right(int parent){
return 2 * parent + 1;
}
int parent(int son){
if (son <= 0){
return -1;
}
return (son - 1) / 2;
}
// 上下调整的 *** 作
void up(int x) {
while (x > 1 && heap[x] > heap[x / 2]) {
swap(heap[x], heap[x / 2]);
x /= 2;
}
}
void down(int x) {
while (x * 2 <= n) {
int t = x * 2;
if (t + 1 <= n && heap[t + 1] > heap[t]) t++;
if (heap[t] <= heap[x]) break;
std::swap(heap[x], heap[t]);
x = t;
}
}
// 堆插入与删除的 *** 作
void insertHeap(int key){
// 插入处始终是尾部
n = n + 1;
heap[n] = key;
up(n);
}
void deleteHeap(){
// 删除的始终是树根节点
swap(heap[1], heap[n]);
n = n - 1;
down(1);
}
// 打印整个堆的内容
void printHeap(){
for(int i = 1; i <= n ; ++i){
cout<<heap[i]<<" ";
}
cout<<endl;
}
参考
https://oi-wiki.org/ds/binary-heap/
https://blog.csdn.net/qq_35862309/article/details/108455062
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)