对于维护有序结构,我们一般采用平衡树,如 AVL-Tree,RB-Tree,Splay,Treap 等。不过除了Treap,剩下那几个都挺复杂的。
设计跳表(skip list),正是为了以另外一种简便直观的方式来完成对于有序集合的维护。
跳表是分层次、相互耦合的多个链表
如何在这样一种多层链表中应用二分查找?图中S0层显然为O(n)的空间复杂度,如何保证整体空间复杂度为O(n)的呢?
跳表自下而上:
假设有 h 层,则 ∑ i = 0 h ⌊ n 2 i ⌋ < ∑ i = 0 h n 2 i = n ∑ i = 0 h 1 2 i = 2 n ( 1 − 1 2 h + 1 ) < 2 n = O ( n ) \begin{align} & 假设有h层,则 \\ & \sum_{i=0}^{h} \left \lfloor \frac{n}{2^i} \right \rfloor \lt \sum_{i=0}^{h} \frac{n}{2^i} = n\sum_{i=0}^{h} \frac{1}{2^i} = 2n(1 - \frac{1}{2^{h+1}}) \lt 2n = O(n) \end{align} 假设有h层,则i=0∑h⌊2in⌋<i=0∑h2in=ni=0∑h2i1=2n(1−2h+11)<2n=O(n)
这样分配每层节点的数目,巧妙地保证了O(N)的空间复杂度
这里提前提一下:每列元素的高度通过抛硬币决定(1/2 01分布),从而保证了每层的期望节点数,这也是我们后续时间复杂度证明要用到的
以下面这张图查找21为例,红线代表指针p 的移动轨迹:
跳表层数 h = O(logn)
竖向跳转:O(logn)
横向跳转:
我们发现同层间横向跳转的节点都是本列的最高点
(
否则可以反证法证明可以在更高层提前跳转
)
事实上,我们在确定每列高度时通过抛硬币决定,第
i
层的每个节点为本列最高点的概率为
(
1
2
)
i
那么我们可以计算每层横向跳转的期望为:
E
=
∑
i
=
1
∞
i
2
i
=
2
\begin{align} & 我们发现同层间横向跳转的节点都是本列的最高点(否则可以反证法证明可以在更高层提前跳转)\\ & 事实上,我们在确定每列高度时通过抛硬币决定,第 i 层的每个节点为本列最高点 的概率为 (\frac{1}{2})^{i}\\ & 那么我们可以计算每层横向跳转的期望为:E = \sum_{i=1}^{\infin}\frac{i}{2^i} = 2\\ \end{align}
我们发现同层间横向跳转的节点都是本列的最高点(否则可以反证法证明可以在更高层提前跳转)事实上,我们在确定每列高度时通过抛硬币决定,第i层的每个节点为本列最高点的概率为(21)i那么我们可以计算每层横向跳转的期望为:E=i=1∑∞2ii=2
因而查找时间复杂度:O(logn),后面的插入,删除和查找也是类似的流程,时间复杂度同样也是O(logn)
插入的话有一个问题:是否允许重复元素出现
这里约定一下,本文的跳表维护的是多重集,允许重复元素
下图是插入17的示例:
不再画图
写这篇博客参考了两个版本:
清华数据结构课本,邓俊辉泛型版本:四联表结构(Quadlist),尝试写了下跟舞蹈链一样恶心,会大大增加博客体量,遂放弃
静态数组,非泛型版本
const int N = 1E5;
const int B = 25;
struct Node
{
int key;
int next[B]; // 塔内向上第 i + 1 层 节点的 后继节点
}pool[N + 2];
int update[N]; // 辅助数组,用于保存搜索路径上的位置,用于 插入 / 删除
const int N = 1E5;
const int B = 25;
struct Node
{
int key;
int next[B]; // 塔内向上第 i + 1 层 节点的 后继节点
}pool[N + 2];
int update[N]; // 辅助数组,用于保存搜索路径上的位置,用于 插入 / 删除
int rand_level()
{
static std::mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
int res = 1;
while ((rng() & 1) && res < B) {
++ res;
}
return res;
}
int find(int key)
{
int p = head;
for (int i = B - 1; ~i; i--)
{
while (pool[p].next[i] != tail && pool[pool[p].next[i]].key < key) {
p = pool[p].next[i];
}
}
p = pool[p].next[0];
return p;
}
void insert(int key)
{
int p = head;
int K = rand_level();
for (int i = B - 1; ~i; -- i)
{
while (pool[p].next[i] != tail && pool[pool[p].next[i]].key < key) {
p = pool[p].next[i];
}
update[i] = p;
}
int node = new_node(key);
for (int i = K - 1; ~i; -- i)
{
pool[node].next[i] = pool[update[i]].next[i];
pool[update[i]].next[i] = node;
}
}
void erase(int key)
{
int p = head;
for (int i = B - 1; ~i; --i)
{
while (pool[p].next[i] != tail && pool[pool[p].next[i]].key < key) {
p = pool[p].next[i];
}
update[i] = p;
}
// shrink(pool[p].next[0]);
for (int i = B - 1; ~i; --i)
{
if (pool[pool[update[i]].next[i]].key == key) {
pool[update[i]].next[i] = pool[pool[update[i]].next[i]].next[i];
}
}
}
原题链接
[P2286
思路分析
维护一个有序集合
我们用一个有序集合维护 权值,这里的权值可能是宠物的权值,也可能是客人的权值
有序集合的维护可以用跳表来实现
时间复杂度:O(nlogn)
AC代码
#include <bits/stdc++.h>
// #define DEBUG
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
constexpr int inf32 = 1E9 + 7;
constexpr i64 inf64 = 1E18 + 7;
constexpr int N = 1E5;
constexpr int B = 25;
constexpr int P = 1000000;
struct Node
{
int key;
int next[B]; // 塔内向上第 i 层 节点的 后继节点
} pool[N + 2];
int update[B];
std::vector<int> nodes;
int head, tail;
int rand_level()
{
static std::mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
int res = 1;
while ((rng() & 1) && res < B) {
++res;
}
return res;
}
int new_node(int key)
{
int res = nodes.back();
nodes.pop_back();
pool[res].key = key;
return res;
}
void init()
{
head = new_node(-inf32);
tail = new_node(inf32);
for (int i = 0; i < B; ++ i) {
pool[head].next[i] = tail;
}
}
void insert(int key)
{
int p = head;
int K = rand_level();
for (int i = B - 1; ~i; -- i)
{
while (pool[p].next[i] != tail && pool[pool[p].next[i]].key < key) {
p = pool[p].next[i];
}
update[i] = p;
}
int node = new_node(key);
for (int i = K - 1; ~i; -- i)
{
pool[node].next[i] = pool[update[i]].next[i];
pool[update[i]].next[i] = node;
}
}
int find(int key)
{
int p = head;
for (int i = B - 1; ~i; i--)
{
while (pool[p].next[i] != tail && pool[pool[p].next[i]].key < key) {
p = pool[p].next[i];
}
}
// p = pool[p].next[0];
return p;
}
void shrink(int p) {
nodes.push_back(p);
}
void erase(int key)
{
int p = head;
for (int i = B - 1; ~i; --i)
{
while (pool[p].next[i] != tail && pool[pool[p].next[i]].key < key) {
p = pool[p].next[i];
}
update[i] = p;
}
shrink(pool[p].next[0]);
for (int i = B - 1; ~i; --i)
{
if (pool[pool[update[i]].next[i]].key == key) {
pool[update[i]].next[i] = pool[pool[update[i]].next[i]].next[i];
}
}
}
auto INIT = [] {
nodes.resize(N + 2);
for (int i = 0; i < nodes.size(); ++ i) {
nodes[i] = nodes.size() - i - 1;
}
init();
return 0;
}();
void solve() {
int n;
std::cin >> n;
int ans = 0;
int cur = 0, siz = 0;
for (int i = 0; i < n; ++ i) {
int a, b;
std::cin >> a >> b;
if (siz == 0) {
cur = a;
insert(b);
++ siz;
} else if (a == cur){
insert(b);
++ siz;
} else {
int j = find(b);
if (pool[pool[j].next[0]].key - b < b - pool[j].key) {
j = pool[j].next[0];
}
ans = (1LL * ans + std::abs(pool[j].key - b)) % P;
erase(pool[j].key);
-- siz;
}
}
std::cout << ans << '\n';
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
#ifdef DEBUG
int START = clock();
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
int t = 1;
// std::cin >> t;
while (t --) {
solve();
}
#ifdef DEBUG
std::cerr << "run-time: " << clock() - START << '\n';
#endif
return 0;
}
因篇幅问题不能全部显示,请点此查看更多更全内容