📜  门| GATE CS 2021 |设置 2 |问题 6(1)

📅  最后修改于: 2023-12-03 15:28:39.154000             🧑  作者: Mango

门 | GATE CS 2021 | 设置 2 | 问题 6

这是 Gate 2021 CS 中的一个原题,考查的是数据结构和算法方面的知识。以下是解题思路和代码实现。

题目描述

我们需要使用一个栈和一个队列来模拟某个系统的缓存。系统中总共有 $n$ 个文件,文件编号分别为 $1, 2, \ldots, n$。缓存最多可以容纳 $k$ 个文件,当缓存满了之后,如果要读取一个不在缓存中的文件,那么就需要从缓存中将一个文件弹出,然后将该文件加入到缓存中。

请设计并实现一个算法,该算法接收一个包含文件编号和读取顺序的列表,对于每一个文件读取请求,如果该文件在缓存中,那么不需要进行任何操作,否则就需要将缓存中的一个文件弹出,然后将该文件加入到缓存中。

请计算并输出该算法的缓存命中率,即在所有查询中缓存中已经存在的文件的占比。

输入格式

第一行包含两个整数 $n$ 和 $k$,分别表示文件的数量和缓存的容量。

第二行包含 $n$ 个整数,表示每个文件的编号。

第三行包含 $m$ 个整数,表示每个文件的读取顺序。

输出格式

输出一个浮点数,表示缓存命中率。结果精度保留两位小数。

解题思路

考虑将缓存看作一个队列和一个栈,其中队列中存储的是已经在缓存中的文件编号,而栈中存储的是最近一次被访问的文件编号。每当需要缓存一个新文件时,我们首先判断缓存中是否已经存在该文件,如果存在,那么缓存命中率就不需要更新;否则,我们就需要将最近一次被访问的文件弹出栈顶元素,将该文件加入到队列尾部。

需要注意的一点是,当缓存空间已满时,我们需要将队列头部的元素弹出队列,并将栈中对应的元素直接弹出。这一步是为了保持队列中存储的文件编号和栈中存储的访问顺序是一致的。

代码实现

我们可以使用链表来实现队列和栈,从而使得代码实现更加简单清晰。以下是使用 Python 语言实现的代码片段。具体实现细节和注释在代码中均有体现。

class Node:
    def __init__(self, value=None):
        self.value = value
        self.prev = None
        self.next = None


class Queue:
    def __init__(self):
        self.head = Node()
        self.tail = Node()
        self.head.next = self.tail
        self.tail.prev = self.head

    def enqueue(self, value):
        new_node = Node(value)
        new_node.prev = self.tail.prev
        new_node.next = self.tail
        self.tail.prev.next = new_node
        self.tail.prev = new_node

    def dequeue(self):
        if self.head.next == self.tail:
            return None
        node = self.head.next
        self.head.next = node.next
        node.next.prev = self.head
        return node.value

    
class Stack:
    def __init__(self):
        self.top_node = Node()

    def push(self, value):
        new_node = Node(value)
        new_node.next = self.top_node
        self.top_node.prev = new_node
        self.top_node = new_node

    def pop(self):
        if self.top_node.value is None:
            return None
        node = self.top_node
        self.top_node = node.next
        return node.value


# 输入 n 和 k
n, k = map(int, input().split())

# 输入每个文件的编号
file_numbers = list(map(int, input().split()))

# 输入文件的访问顺序
access_orders = list(map(int, input().split()))

# 初始化队列和栈
cache = Queue()
accessed_files = Stack()

# 遍历所有的文件访问请求,并计算缓存命中率
hits = 0
for file_number in access_orders:
    if file_number in cache:
        hits += 1
        continue
    if len(cache) == k:
        oldest_file = cache.dequeue()
        accessed_files.pop()
    cache.enqueue(file_number)
    accessed_files.push(file_number)

hit_ratio = hits / len(access_orders)

# 输出缓存命中率
print("{:.2f}".format(hit_ratio))

以上代码可以完成该问题的求解,时间复杂度为 $O(mk)$,其中 $m$ 是文件访问请求的数量。因为每次访问都需要遍历整个队列判断是否存在该文件,所以时间复杂度比较高。如果想要进一步优化,可以将队列换成一个哈希表,这样可以将时间复杂度降低到 $O(m)$。