题目描述
我们都知道一句话那就是:函数是一种特殊的映射。对于数组这种数据结构,其天生具有“映射性”:通过下标索引获得存储在对应索引中的值。如果我们将数组的索引类比函数的输入(定义域中的某个值),存储在索引中的值类比函数的输出(值域中的某个值),是不是数组在一定程度上可以代表一个函数呢。
不妨来看下这个例子:
int f[N];
for (int i = 0; i <= N; i ++) f[i] = i * i;
输出一下 f 数组?
index: 0 1 2 3 ... N
value: 0 1 4 9 ... N^2
有没有觉得 f 数组在一定程度上代表了 f ( x ) = x 2 , ( 0 ≤ x ≤ N ) f(x) = x^2 , (0 ≤ x≤N) f(x)=x2,(0≤x≤N)呢?
综上,我想表达的是对于算法中的映射关系,我们可以将这种关系想象成函数,再用数组来进行实现。
可以去喝杯咖啡思考一下,或者先来看手工模拟堆这个具体的问题,一会将会直接用上面提出的方法。
在之前堆排序的问题中,我们分析了 downAdjust
、createHeap
、deleteTop
这三个操作。那如果想要往堆里添加一个元素,应该怎么办呢?可以把想要添加的元素放在数组最后(也就是二叉树的最后一个结点后面,即堆底),然后进行向上调整操作。向上调整总是把欲调整结点与父结点比较,如果值比父结点小,那么就交换其与父结点,这样反复比较,直到达到堆顶或者父结点的权值较小为止,向上调整代码如下,时间复杂度为 O ( l o g n ) O(logn) O(logn):
void upAdjust(int v)
{
// v / 2 代表父结点位置
while (v / 2 && heap[v / 2] > heap[v])
{
swap(heap[v], heap[v / 2]);
v /= 2;
}
}
这样由这四个操作就可以模拟更加复杂的堆了(附加存储位置与插入次序的映射)。在这道题目中由于涉及对第 k 个插入的数操作,因此我们需要新增两个数组:point_heap(记为ph)、heap_to_pinter(记为hp)。
ph[i] = v:堆中第 i 个插入的元素的下标是 v。
hp[v] = i:堆中下标为 v 的位置的元素是第 i 个插入的。
既然每个堆元素的存储位置与插入次序产生了映射关系,当我们想要交换两个堆元素时,其存储位置与插入次序也要进行交换!对于上面的图例,我们不妨按照本文顶部的介绍,用数组来代替函数,在那里的介绍中,我们“刻意而为之”对数组每位赋 i * i 的值,使得 f 数组在一定程度上拟合成了 f ( x ) = x 2 , ( 0 ≤ x ≤ N ) f(x) = x^2 , (0 ≤ x≤N) f(x)=x2,(0≤x≤N)。那在本题中这种拟合是怎么发生的呢?从我们的代码中看:
if (op == "I")
{
cin >> x;
ssize ++, idx ++; // 堆总数、插入位序增加
// 这是对于《数组实现映射》的不断喂“数据”,使其拟合某个函数
ph[idx] = ssize, hp[ssize] = idx;
// 新添加数据放在堆底再向上调整
heap[ssize] = x;
upAdjust(ssize);
}
每次添加新元素时都是拟合函数的过程。其中:ph 为 f ( ) f() f(),hp 为 g ( ) g() g()。i 为 x 1 x_1 x1,j 为 x 2 x_2 x2。
上图中四个等式分别为:
f ( x 1 ) = v f(x_1)=v f(x1)=v——ph[i]=v \qquad g ( v ) = x 1 g(v)=x_1 g(v)=x1——hp[v]=i
f ( x 2 ) = u f(x_2)=u f(x2)=u——ph[j]=u \qquad g ( u ) = x 2 g(u)=x_2 g(u)=x2——hp[u]=j
有了这些关系后,我们交换元素时就要把映射考虑进去了。
swap(ph[i], ph[j])
可写成 s w a p ( f ( x 1 ) , f ( x 2 ) swap(f(x_1),f(x_2) swap(f(x1),f(x2))。但i, j(下标) 即 x 1 , x 2 x_1, x_2 x1,x2 (输入)是未知的。swap(ph[hp[v]], ph[hp[u]])
。swap(hp[v], hp[u])
可写成 s w a p ( g ( v ) , g ( u ) swap(g(v),g(u) swap(g(v),g(u))。这些量都是已知的。可以看出来带映射关系的堆元素交换操作不再是一个 swap() 能解决的了吧。那我们不如将其写成一个整体,命名为 map_swap(),实现如下:
void map_swap(int v, int u)
{
swap(ph[hp[v]], ph[hp[u]]); // 存储位置交换
swap(hp[v], hp[u]) // 插入位序交换
swap(heap[v], heap[u]); // 堆元素本身的交换
}
另外需要将 downAdjust
、upAdjust
中用到的 swap()
都替换成 map_swap()
。
下面依次分析一下题目要求的五种操作:
I
:添加操作,对于第 idx 个插入的元素,都将其 ph[idx] 设为 ssize(表示堆大小,说明将第 idx 次添加的元素放到堆底处),再把hp[ssize] 设为 idx(说明ssize 位置处的元素是第 idx 次添加的)最后再向上调整。
PM
:由小根堆的性质,输出堆顶元素即可。
DM
:删除最小元素。首先交换堆顶底元素,减少堆总元素数(代表删除堆底元素操作),对于新来的堆顶元素向下调整即可。
D
:删除第 k 个添加的数。首先通过 ph[k] 获取第 k 个添加的数的存储地址,接着与删除最小值步骤相同,只不过是将堆顶元素位置替换成第 k 个添加的元素位置。
C
:修改第 k 个添加的数。同上一种操作,也是先取出存储地址,接着修改 heap[存储地址] 即可。
对于删除和修改第 k 个元素,由于会用堆底元素对第 k 个元素进行覆盖,此时有三种情况:
而每次只会出现三者之一,因此可以统一执行 downAdjust(k)
、upAdjust(k)
。并且这两个函数最多只会执行一个。
#include
#include
using namespace std;
const int N = 100010;
// ph[i] = v:堆中第 i 个插入的元素的下标是 v
// hp[v] = i:堆中下标为 v 的位置的元素是第 i 个插入的
int heap[N], ssize, ph[N], hp[N];
void map_swap(int v, int u)
{
swap(ph[hp[v]], ph[hp[u]]); // 存储位置交换
swap(hp[v], hp[u]); // 插入位序交换
swap(heap[v], heap[u]); // 堆元素本身的交换
}
void downAdjust(int v)
{
// 用 t 来表示子树中的最小值, l 为左孩子位置,r 为右孩子位置
int t = v, l = v * 2, r = v * 2 + 1;
// 判断左儿子是否存在以及左孩子值是否小于根结点的值
if (l <= ssize && heap[l] < heap[t]) t = l;
// 判断右儿子是否存在以及右孩子值是否小于根结点的值
if (r <= ssize && heap[r] < heap[t]) t = r;
// 如果 t 发生了变化(不等于 v)即说明在子树中找到了比根结点更小的值
// 那么 v 结点就要继续向下调整
if (v != t)
{
// 将其交换,维护小根堆的特性
map_swap(v, t);
// 以找到的较小值的位置为根结点继续向下调整
downAdjust(t);
}
}
void upAdjust(int v)
{
// v / 2 代表父结点位置
while (v / 2 && heap[v] < heap[v / 2])
{
map_swap(v, v / 2);
v /= 2;
}
}
int main()
{
int n, idx = 0; // idx 表示插入元素的位序
cin >> n;
while (n --)
{
string op;
int k, x;
cin >> op;
if (op == "I")
{
cin >> x;
ssize ++, idx ++; // 堆总数、插入位序增加
// 这是对于《数组实现映射》的不断喂“数据”
ph[idx] = ssize, hp[ssize] = idx;
// 新添加数据放在堆底再向上调整
heap[ssize] = x;
upAdjust(ssize);
}
else if (op == "PM") cout << heap[1] << endl;
else if (op == "DM")
{
// 将堆底元素与堆顶元素互换,再向下调整
map_swap(1, ssize);
ssize --;
downAdjust(1);
}
else if (op == "D")
{
cin >> k;
// 用 k 获取第 k 个插入的元素在堆中的下标
k = ph[k];
map_swap(k, ssize);
// 删除后,堆总数减一
ssize --;
upAdjust(k), downAdjust(k);
}
else
{
cin >> k >> x;
k = ph[k];
// 找到存储位置后,用 x 替换
heap[k] = x;
upAdjust(k), downAdjust(k);
}
}
}