用跳跃表解决第 k 大的数

一 问题描述

小明和小宝正在玩数字游戏,游戏有 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;
    }
}

七 测试

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值