说我有一个“事件”对象队列,其中每个对象都有一个“事件类型”和某种数据字段。在C ++中,可能是这样的:
enum class EventType
{
A,
B,
C
};
struct Event
{
EventType type;
std::string message;
};
说,我想要这些事件的队列,并且希望该队列具有以下属性:
EventType
的所有现有事件。因此,当事件从队列中弹出时,它始终是其EventType
EventType
会按推送的顺序弹出。 (pop()
不希望有任何参数)。 因此,队列相对于Event
是FIFO,相对于LIFO
是EventType
。
示例:
以下事件被推入队列(字母是事件类型,数字是实例号)
A1 A2 C1 C2 C3 B1 B2 A3
如果随后调用pop()3次,则按顺序返回的项目将是
C3 B2 A3
这种数据结构有名称吗,并且有实现的例子吗?
答案 0 :(得分:0)
免责声明:设计非常高效的数据结构是与数据的域(体系结构)和上下文严格相关的内容。以下解决方案旨在在一般情况下在理论上有效。有关更多详细信息,请参见最终注意事项。
这个想法很简单。
保留表示队列的基础数据结构。
每次插入新事件时,请检查类型是否已经存在。在这种情况下,我们可以删除该元素。这样,我们维护第一个属性(每个类型仅一个实例,这意味着我们始终会弹出该类型的最新实例)。
最后,我们将新实例插入队列的末尾。因此,我们保留第二个属性。
#include <algorithm>
#include <list>
#include <utility>
class QueueWithAGoodName {
public:
using reference = Event&;
void push(Event event) {
if (const auto finder =
std::find_if(queue_.begin(),
queue_.end(),
[eventType = event.type](const Event& event) {
return event.type == eventType;
});
finder != queue_.end()) {
queue_.erase(finder);
}
queue_.push_back(std::move(event));
}
reference front() { return queue_.front(); }
void pop() { queue_.pop_front(); }
private:
std::list<Event> queue_;
};
在我的示例中,我使用了std::list
作为基础数据结构。确实,如果您在“中间”频繁删除,则std::list
很方便。此外,删除后,迭代器不会无效。如果我们想改善队列,这是一个很好的特性。
QueueWithAGoodName::push
具有运行时时间复杂度:O(N)
(因为我们应用了线性扫描来查找类型)。其中N
是队列的大小。
QueueWithAGoodName::pop
和QueueWithAGoodName::front
是O(1)
。N
(队列的大小)的上限是EventType
的数量。O(N)
可以近似为O(1)
。在这种情况下,由于缓存的原因,std::vector
作为基础数据结构可能更有效。push
是O(N)
。如果您有几种EventType
类型,则可以使推入操作中的数据结构更有效。简而言之,可以使用辅助std::unordered_map<EventType, list::iterator>
(即 hash map )来避免线性搜索。擦除后,std::list
不会使迭代器无效,因此哈希表保持一致。