一 问题描述
小明和小宝正在玩数字游戏,游戏有 n 轮,小明在每轮中都可以写一个数,或者问小宝第 k 大的数是什么。游戏格式为:I c,表示小明写下一个数 c。Q,表示小明问第 k 大的数。请对小明的每个询问都给出第 k 大的数。
二 输入和输出
1 输入
输入包含多个测试用例。每个测试用例的第 1 行都包含两个正整数n、k,表示 n 轮游戏和第 k 大的数。然后是 n 行,格式为 I c 或 Q。
2 输出
对每个询问 Q,都单行输出第 k 大的数。
三 输入和输出样例
1 输入样例
8 3
I 1
I 2
I 3
Q
I 5
Q
I 4
Q
2 输出样例
1
2
3
四 算法分析
本问题可以使用跳跃表,第 k 大,即第 total-k+1 小。
五 算法实现
1 查询第 k 小的元素
在跳跃表中如何查询第 k 小?在每个节点都增加一个域 sum[i],用于记录当前节点到下一个节点的元素个数。
算法步骤:
(1)指针 p 指向跳跃表的头节点。
(2)从跳跃表的顶层向下逐层判断。
(3)执行循环,若 p 不空且 p->sum[i] 小于 k ,则 k 减去 p->sum[i],p 后移一位指向其后继;否则进入下一层。
(4)直到最后一层处理完毕,此时 p 的后继元素就是第 k 小的元素。
算法图解:
例如,跳跃表如下图所示,在表中查询第 6 小的元素,过程如下。
(1)i=3,k=6,p=head,p->sum[3]=4 < k ,k=k-p->sum[3]=6-4=2,p 指针后移一位指向元素20,此时 p->sum[3]=4> k,进入下一层。
(2)i=2,p->sum[2]=4>k,进入下一层。
(3)i=1,p->sum[1]=2≥k ,进入下一层。
(4)i=0,p->sum[0]=1<k ,k=2-1=1,p 指针后移一位指向元素 25,此时 p->sum[0]=1≥k ,已到底层,无法进入下一层,算法结束。此时 p 的后继元素 30 就是第 6 小的节点。
2 查找元素
在跳跃表中查找小于 val 的元素个数,在查找过程中用 tot[i] 累加当前层到当前点的节点数。
算法步骤:
(1)指针 p 指向跳跃表的头节点。
(2)从跳跃表的顶层向下逐层判断。
(3)执行循环,若 p 的后继不空且 p 的后继元素小于 val,则 tot[i] 累加 p->sum[i],p 后移一位;否则进入下一层。
(4)当前层处理完毕时,下一层累计节点数的初值等于上一层的累计节点数 tot[i-1]=tot[i],updata[i] 记录搜索过程中各级走过的最大节点位置 updata[i]=p。
(5)直到最后一层处理完毕,此时 p 指向比 val 小的最大元素,tot[0] 表示小于 val 的数量。
图解:
例如,跳跃表如下图所示,在表中查询小于 20 的元素个数,过程如下。
(1)i=3,tot[3]=0,p 的后继元素为 20 ,不小 于 20 ,tot[2]=tot[3],updata[3]=p ,进入下一层。
(2)i=2,p 的后继元素为 20,不小于 20,tot[1]=tot[2],updata[2]=p ,进入下一层。
(3)i=1,p 的后继元素为 5,小于 20 ,tot[1]+=p ->sum[1]=1,p 后移一位指向元素 5,p 的后继元素为 20,不小于 20,tot[0]=tot[1],updata[1]=p ,进入下一层。
(4)i=0,p 的后继元素为 8,小于 20 ,tot[0]+=p ->sum[0]=2,p 后移一位指向元素 8,p 的后继元素为12,小于 20,tot[0]+=p->sum[0]=3,p 后移一位指向元素 12,p 的后继元素为 20,不小于 20,此时 i=0,不再赋值tot[i-1];否则下标越界,updata[0]=p ,算法结束。
(5)此时 p 指向 20 的前一个节点(若查找失败,则会指向小于它的最大节点),tot[0] 等于3,表示小于 20 的元素个数为3。
3 插入元素
插入元素时,除了需要查找插入位置,还需要更新 sum[],插入元 素后节点总数 total++。
算法步骤
(1)通过随机化程序得到新插入节点的层次 lay,若 lay>level,则更新头节点的 level+1~lay 层,head->sum[i ]=total,total 为跳跃表的节点总数,level 为跳跃表的最大层次,并更新 level=lay。
(2)通过查找,得到小于 val 的元素,在查询过程中记录 tot[] 和 updata[]。
(3)创建新节点 s 并初始化。
(4)将新节点插入每层的 updata[] 之后,更新 s->sum[i] 和 updata[i ]->sum[i]。
(5)若 lay<level,则更新 lay+1~level 层的 updata[i ]->sum[i]。
图解:
例如,跳跃表如下图所示。
在跳跃表中插入元素20,过程如下。
(1)通过随机化程序得到新插入节点的层次 lay=3,lay 大于跳跃表的层 次 level , 因此更新跳跃表头节点的层次为 3 , head->sum[3]=total=7。
(2)通过查找得到小于 20 的元素个数,在查询过程中记录 tot[] 和 updata[]。
(3)创建新节点 s 并初始化。
(4)将新节点插入每层的 updata[] 之后,并更新 s->sum[i] 和 updata[i]->sum[i]。
• i =3:s- >sum[3]=7-(3-0)=4,updata[3]->sum[3]=7-4+1=4。
• i =2:s ->sum[2]=7-(3-0)=4,updata[2]->sum[2]=7-4+1=4。
• i =1:s ->sum[1]=4-(3-1)=2,updata[1]->sum[1]=4-2+1=3。
• i =0:s ->sum[0]=1-(3-3)=1,updata[0]->sum[0]=1-1+1=1。
插入元素的过程如下图所示。
六 代码
package com.platform.modules.alg.alglib.hdu4006;
import java.util.Random;
public class Hdu4006B {
private int INF = 0x3f3f3f3f;
private int MAX_LEVEL = 16;
public String output = "";
int total; // 总节点数
char ch;
public String cal(String input) {
int n, k, val;
String[] line = input.split("\n");
String[] words = line[0].split(" ");
n = Integer.parseInt(words[0]);
k = Integer.parseInt(words[1]);
Init();
total = 0;
for (int i = 1; i <= n; i++) {
String[] command = line[i].split(" ");
ch = command[0].charAt(0);
if (ch == 'I') {
val = Integer.parseInt(command[1]);
Insert(val);
total++;
} else {
output += String.format("%d\n", Get_kth(total - k + 1));
}
}
return output;
}
class Node {
int val;
int sum[] = new int[MAX_LEVEL];
Node forward[] = new Node[MAX_LEVEL];
}
Node head;
Node updata[] = new Node[MAX_LEVEL];
int level;
int tot[] = new int[MAX_LEVEL];
public Hdu4006B() {
for (int i = 0; i < updata.length; i++) {
updata[i] = new Node();
}
}
void Init() {
level = 0;
head = new Node();
for (int i = 0; i < MAX_LEVEL; i++) {
head.forward[i] = null;
head.sum[i] = 0;
}
head.val = -INF;
}
// 按规则选择数据应该在哪层插入
int RandomLevel() {
int lay = 0;
while (new Random().nextInt(100) % 2 == 0 && lay < MAX_LEVEL - 1)
lay++;
return lay;
}
// 查找最接近 val 的元素
int Find(int val) {
Node p = head;
tot[level] = 0;
for (int i = level; i >= 0; i--) {
while (p.forward[i] != null && p.forward[i].val < val) {
tot[i] += p.sum[i];
p = p.forward[i];
}
if (i > 0)
tot[i - 1] = tot[i]; // 不判断会下标越界
updata[i] = p; // 记录搜索过程中各级走过的最大节点位置
}
return tot[0]; // 返回小于等于val的数量
}
void Insert(int val) {
Node s;
int lay = RandomLevel();
if (lay > level) { // 要插入的层>现有层数
for (int i = level + 1; i <= lay; i++)
head.sum[i] = total;
level = lay;
}
Find(val); // 查询
s = new Node(); // 创建一个新节点
s.val = val;
for (int i = 0; i < MAX_LEVEL; i++) {
s.forward[i] = null;
s.sum[i] = 0;
}
for (int i = 0; i <= lay; i++) { // 插入操作
s.forward[i] = updata[i].forward[i];
updata[i].forward[i] = s;
s.sum[i] = updata[i].sum[i] - (tot[0] - tot[i]);
updata[i].sum[i] -= s.sum[i] - 1;
}
for (int i = lay + 1; i <= level; i++)
updata[i].sum[i]++;
}
int Get_kth(int k) {
Node p = head;
for (int i = level; i >= 0; i--) {
while (p != null && p.sum[i] < k) {
k -= p.sum[i];
p = p.forward[i];
}
}
return p.forward[0].val;
}
}
七 测试