#P2325. 第2题-计网实验
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 1074
            Accepted: 232
            Difficulty: 6
            
          
          
          
                       所属公司 : 
                              华为
                                
            
                        
              时间 :2024年5月8日-暑期实习
                              
                      
          
 
- 
                        算法标签>哈希表          
 
第2题-计网实验
思路
本题的原题是力扣460题。
本题的目标是实现一个具有LFU(最近不常使用)策略的缓存系统。为此,我们设计了一个名为Node的数据结构,以及结合哈希表和红黑树(通过std::set)的复合数据结构来支持缓存操作。
数据结构定义
- 
Node 结构:每个
Node包含以下字段:cnt:表示节点被访问的次数。time:表示节点最近一次被访问的时间。key:节点的键,用于标识缓存项。value:节点的值,即缓存项的数据内容。
这个结构既用于哈希表中的值,也用于红黑树中的元素。
 - 
哈希表:使用
std::unordered_map存储键和指向节点的指针,以实现快速查找。 - 
平衡二叉树:使用
std::set来维护一个按(cnt, time)排序的节点集合,确保可以快速访问和删除最少使用的缓存项。 
核心操作
- 
读取(Read)操作:
- 检查键是否存在于哈希表中。
 - 如果存在,则更新节点的
cnt和time,重新插入到红黑树中以保持排序。 - 返回节点的值。
 
 - 
写入(Write)操作:
- 如果键存在,更新其值及访问的元数据(
cnt和time),并更新红黑树。 - 如果键不存在,创建一个新的节点,加入哈希表和红黑树。
 - 如果缓存已满(即达到预设的最大容量),则先从红黑树中移除最少使用的缓存项,然后移除对应的哈希表条目。
 
 - 如果键存在,更新其值及访问的元数据(
 - 
查询(Query)操作:
- 直接通过哈希表检查键是否存在,并返回相应的值或-1(不存在的标志)。
 
 
在这个实现中,我们通过组合哈希表和红黑树来维护LRU缓存,这不仅确保了操作的效率,也简化了最少使用元素的管理。这种策略适用于需要频繁读写且对性能要求较高的场景。
代码
Python
import collections
import time
# 定义一个名为 Node 的类,类似于C++中的结构体
class Node:
    def __init__(self, cnt, tim, key, value):
        self.cnt = cnt  # 计数器,用于记录某个键的访问次数
        self.tim = tim  # 时间戳,用于记录某个键最后一次被访问的时间
        self.key = key  # 键
        self.value = value  # 键对应的值
    # 定义小于操作符,用于比较两个 Node 对象
    def __lt__(self, other):
        # 如果计数器相同,按时间戳排序;否则按计数器排序
        if self.cnt == other.cnt:
            return self.tim < other.tim
        return self.cnt < other.cnt
# 定义全局变量
n = 0  # 缓存的容量
tim = 0  # 全局时间戳
mapp = {}  # 存储键和值的映射
S = set()  # 用于存储所有的 Node 对象
# 获取某个键对应的值
def get(key):
    global tim
    if n == 0 or key not in mapp:  # 如果缓存容量为0或键不在映射中,返回-1
        return -1
    cache = mapp[key]  # 获取对应的缓存节点
    S.remove(cache)  # 从集合中删除旧节点
    cache.cnt += 1  # 增加访问计数器
    tim += 1  # 更新全局时间戳
    cache.tim = tim  # 更新节点的时间戳
    S.add(cache)  # 将更新后的节点重新插入集合
    mapp[key] = cache  # 更新映射中的节点
    return cache.value  # 返回节点的值
# 插入或更新某个键值对
def put(key, value):
    global tim
    if n == 0:  # 如果缓存容量为0,直接返回
        return
    if key not in mapp:  # 如果键不在映射中
        if len(mapp) == n:  # 如果映射中的元素数量等于缓存容量
            oldest = min(S)  # 获取集合中最小的节点(即要被替换的节点)
            S.remove(oldest)  # 从集合中删除该节点
            del mapp[oldest.key]  # 从映射中删除该节点
        tim += 1  # 更新全局时间戳
        cache = Node(1, tim, key, value)  # 创建一个新的节点
        mapp[key] = cache  # 将节点插入映射
        S.add(cache)  # 将节点插入集合
    else:  # 如果键已经在映射中
        cache = mapp[key]  # 获取对应的缓存节点
        S.remove(cache)  # 从集合中删除旧节点
        cache.cnt += 1  # 增加访问计数器
        tim += 1  # 更新全局时间戳
        cache.tim = tim  # 更新节点的时间戳
        cache.value = value  # 更新节点的值
        S.add(cache)  # 将更新后的节点重新插入集合
        mapp[key] = cache  # 更新映射中的节点
# 主函数
if __name__ == "__main__":
    s = input()  # 读取操作类型
    n = int(input())  # 读取缓存容量
    while True:
        try:
            s = input()  # 读取操作类型
            if s[0] == 'w':  # 如果操作是写入
                m = int(input())  # 读取写入操作的数量
                for _ in range(m):  # 逐个读取并写入键值对
                    a, b = map(int, input().split())
                    put(a, b)
            elif s[0] == 'r':  # 如果操作是读取
                x = int(input())  # 读取要查询的键
                get(x)
            else:  # 如果操作是查询当前缓存的值
                x = int(input())  # 读取要查询的键
                if x in mapp:  # 如果键在映射中
                    print(mapp[x].value)  # 打印对应的值
                else:
                    print(-1)  # 如果键不在映射中,打印-1
        except EOFError:
            break  # 读取结束后,退出循环
C++
#include <bits/stdc++.h>
using namespace std;
struct Node {
    int cnt, tim, key, value;
    Node() {}
    Node(int a, int b, int c, int d):cnt(a), tim(b), key(c), value(d){}
    bool operator < (const Node& nod) const {
        return cnt == nod.cnt ? tim < nod.tim : cnt < nod.cnt;
    }
};
int n, tim;
unordered_map<int, Node> mapp;
set<Node> S;
int get(int key) {
    if (n == 0 || mapp.find(key) == mapp.end()) {
        return -1;
    }
    Node cache = mapp[key];
    S.erase(cache);
    cache.cnt += 1;
    cache.tim = ++tim;
    S.insert(cache);
    mapp[key] = cache;
    return cache.value;
}
void put(int key, int value) {
    if (n == 0) return;
    auto it = mapp.find(key);
    if (it == mapp.end()) {
        if (mapp.size() == n) {
            mapp.erase(S.begin()->key);
            S.erase(S.begin());
        }
        Node cache = Node(1, ++tim, key, value);
        mapp[key] = cache;
        S.insert(cache);
    } else {
        Node cache = it->second;
        S.erase(cache);
        cache.cnt += 1;
        cache.tim = ++tim;
        cache.value = value;
        S.insert(cache);
        it->second = cache;
    }
}
int main() {
    string s;cin >> s;
    cin >> n;
    int x;
    while (cin >> s) {
        if (s[0] == 'w') {
            int m;
            cin >> m;
            while (m --) {
                int a, b;
                cin >> a >> b;
                put(a, b);
            }
        } else if(s[0] == 'r') {
        	cin >> x;
            get(x);
        } else while (cin >> x) {
            if (mapp.count(x)) cout << mapp[x].value << endl;
            else cout << -1 << endl;
        }
    }
}
Java
import java.util.*;
class Node implements Comparable<Node> {
    int cnt, tim, key, value;
    Node(int cnt, int tim, int key, int value) {
        this.cnt = cnt;
        this.tim = tim;
        this.key = key;
        this.value = value;
    }
    @Override
    public int compareTo(Node nod) {
        return this.cnt == nod.cnt ? this.tim - nod.tim : this.cnt - nod.cnt;
    }
}
public class Main {
    static int n, tim;
    static Map<Integer, Node> mapp = new HashMap<>();
    static TreeSet<Node> S = new TreeSet<>();
    // 获取缓存值
    static int get(int key) {
        if (n == 0 || !mapp.containsKey(key)) {
            return -1;
        }
        Node cache = mapp.get(key);
        S.remove(cache);
        cache.cnt += 1;
        cache.tim = ++tim;
        S.add(cache);
        mapp.put(key, cache);
        return cache.value;
    }
    // 插入缓存值
    static void put(int key, int value) {
        if (n == 0) return;
        if (!mapp.containsKey(key)) {
            if (mapp.size() == n) {
                Node firstNode = S.first();
                mapp.remove(firstNode.key);
                S.remove(firstNode);
            }
            Node cache = new Node(1, ++tim, key, value);
            mapp.put(key, cache);
            S.add(cache);
        } else {
            Node cache = mapp.get(key);
            S.remove(cache);
            cache.cnt += 1;
            cache.tim = ++tim;
            cache.value = value;
            S.add(cache);
            mapp.put(key, cache);
        }
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String s = scanner.next();
        n = scanner.nextInt();
        while (scanner.hasNext()) {
            s = scanner.next();
            if (s.charAt(0) == 'w') {
                int m = scanner.nextInt();
                for (int i = 0; i < m; i++) {
                    int a = scanner.nextInt();
                    int b = scanner.nextInt();
                    put(a, b);
                }
            } else if (s.charAt(0) == 'r') {
                int x = scanner.nextInt();
                get(x);
            } else {
                while (scanner.hasNextInt()) {
                    int x = scanner.nextInt();
                    System.out.println(mapp.containsKey(x) ? mapp.get(x).value : -1);
                }
            }
        }
        scanner.close();
    }
}
javaScript
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
// LRU 缓存类
class LRUCache {
    constructor(capacity) {
        this.n = capacity;
        this.tim = 0;
        this.mapp = new Map();
        this.set = new Set();
    }
    // 获取缓存值
    get(key) {
        if (this.n === 0 || !this.mapp.has(key)) {
            return -1;
        }
        let cache = this.mapp.get(key);
        this.set.delete(cache);
        cache.cnt += 1;
        cache.tim = ++this.tim;
        this.set.add(cache);
        return cache.value;
    }
    // 插入缓存值
    put(key, value) {
        if (this.n === 0) return;
        if (!this.mapp.has(key)) {
            if (this.mapp.size === this.n) {
                let firstNode = [...this.set].sort((a, b) => a.cnt === b.cnt ? a.tim - b.tim : a.cnt - b.cnt)[0];
                this.mapp.delete(firstNode.key);
                this.set.delete(firstNode);
            }
            let cache = { cnt: 1, tim: ++this.tim, key, value };
            this.mapp.set(key, cache);
            this.set.add(cache);
        } else {
            let cache = this.mapp.get(key);
            this.set.delete(cache);
            cache.cnt += 1;
            cache.tim = ++this.tim;
            cache.value = value;
            this.set.add(cache);
            this.mapp.set(key, cache);
        }
    }
}
// 处理输入
(async function () {
    let cache;
    while (line = await readline()) {
        let parts = line.split(":");
        let command = parts[0].trim();
        if (command === "capacity") {
            cache = new LRUCache(parseInt(await readline()));
        } else if (command === "write") {
            let count = parseInt(await readline());
            for (let i = 0; i < count; i++) {
                let [key, value] = (await readline()).split(" ").map(Number);
                cache.put(key, value);
            }
        } else if (command === "read") {
            let key = parseInt(await readline());
            cache.get(key); // 这里仅用于更新 LRU 访问顺序
        } else if (command === "query") {
            let key = parseInt(await readline());
            console.log(cache.get(key));
        }
        else{
            break;
        }
    }
    rl.close();
})();
        题目描述
这学期的小明的计网老师教学水平真是一言难尽,还要用ensp来做一些繁琐的模拟实验,但是计网实验跟写代码有什么关系呢?来不及为赶不完的DDL哀悼了,有一个实验描述如下:
无线通信移动性需要在基站上配置邻区(本端基站的小区LocalCell与周边邻基站的小区NeighborCelI映射)关系,为了能够加速无线算法的计算效率,设计一个邻区关系缓存表,用于快速的通过本小区LocalCell查询到邻小区NeighborCell。但是缓存表有一定的规格限制,因此到达规格并且需要插入新的数据时,需要删除邻区数据,选择删除邻区数据对象的策略为:
(1)使用次数最少的;
(2)如果(1)返回有多个对象,则选择最久未使用的。
请设计并实现一个满足以上要求的数据结构和算法实现。
注:假设每个LocalCell至多只有一个NeighborCell。
输入描述
1、首行以字符"capacity:"标识设置一个整数容量;
2、以"write:"标识开始进行若干组[LocalCell,NeighborCell]邻区数据的输入,每组数据为一行;如果"write:"已经存在的LocalCell数据,更新其对 应的NeighborCell,并刷新使用时间和次数加1;如果某邻区数据被删除,缓存表不再保留其记录;
3、以"read:"标识进行一次读取LocalCell的使用操作,刷新使用时间和次数加1;
4、最后以"query:"标识查询输出操作,输入正整数LocalCell,查询NeighborCell;
注:
(1)写入和读取都表示对LocalcelI的使用操作;
(2)capacity、LocalCellI和NeighborCelI都是正整数,范围在[1,10000];
(3)输入的总行数不超过30000行。
输出描述
每个查询输入正整数LocalCell对应NeighborCell,表示在邻区关系缓存表中的记录。
1、找到,则返回NeighborCell; 2、没有找到,则返回-1;
样例一
输入
capacity:
3
write:
3
1 2
4 3
2 3
read:
2
write:
1
3 1
query:
1
输出
-1
解释
1、设定容量capacity为3
2、write输入3组数据,
3、read读取2使用,刷新该邻区对使用时间和次数;
4、再write输入1组数据,因为已经超了容量3,所以把最早输入且未使用的数据”12”剔除;
5、最后进行query查询1因为已经被剔除了,所以返回-1;
Limitation
1s, 1024KiB for each test case.