建议一个合适的数据结构(在C ++中),以便解决下面提到的目的:
现在我正在使用向量...但是查找某个特定元素是否存在时,向量中的时间复杂度很高,因为我的元素没有排序。
是否有一些比矢量更好的数据结构来实现这一点..如果是......那么请给出一个例子。
答案 0 :(得分:3)
一种可能性是使用std::set或std::unordered_set,它基本上是一个哈希表,并自己维护元素之间的顺序。这将为您提供O(log(n))或摊销的O(1)查找复杂性以及在开头/结尾的常量插入/删除。在Java中,这称为LinkedHashSet。不幸的是,STL并不提供开箱即用的这种数据结构,但它应该很容易在set / unordered_set或map / unordered_map之上实现。
这是一段代码,说明了这个想法:
template <typename T>
class linked_set {
private:
// Comparator of values with dereferencing.
struct value_deref_less {
bool operator()(const T *lhs, const T *rhs) const {
return *lhs < *rhs;
}
};
typedef std::set<const T*, value_deref_less> Set;
Set set_; // Used for quick lookup
std::deque<T> store_; // Used for ordered storage. deque is used instead of
// vector because the former doesn't invalidate
// pointers/iterators when elements are pushed.
public:
void push_back(const T& value) {
store_.push_back(value);
set_.insert(&store_.back());
// TODO: handle the case of duplicate elements.
}
// TODO: better provide your own iterator.
typedef typename Set::iterator iterator;
iterator find(const T& value) { return set_.find(&value); }
// ...
};
答案 1 :(得分:2)
您将无法在两侧进行快速插入和使用相同容器进行快速搜索,至少如果您将可能性限制为STL。更多异国情调的非标准容器可能有所帮助。
但我在这些情况下通常选择的方法是使用两个容器。对于存储元素,显而易见的选项是std :: deque。对于搜索,请创建一个std::map<K,V>
,其中V是双端队列的迭代器。因为deques中的插入/删除不会使未涉及的迭代器无效,所以如果您始终记得同步地图和双端队列(即当您在双端队列上执行插入或删除时,也可以在地图上执行此操作),则应该没问题。 。
另一个更简单/更安全的选项,而不是使用迭代器 - 如果在地图中搜索后你只需要找到的元素(你不需要访问附近的元素等) - 就是在deque和map中都有智能指向实际对象的指针(更具体地说,shared_ptr)。同样,你必须小心保持同步;虽然如果它们失去同步不会是灾难性的,当然,你的程序的一致性可能会受到损害。
struct MyItem
{
std::string name;
int something;
int another;
MyItem(const std::string &name_, int something_, int another_)
:name(name_), something(something_), another(another_) {}
};
class MyContainer
{
public:
typedef std::shared_ptr<MyItem> MyItemPtr;
void push_front(MyItemPtr item)
{
deque.push_front(item);
assert(map.find(item->name) == map.end());
map[item->name] = item;
}
void push_back(MyItemPtr item)
{
deque.push_back(item);
assert(map.find(item->name) == map.end());
map[item->name] = item;
}
MyItemPtr pop_front()
{
item = deque.front();
deque.pop_front();
map.erase(item->name);
return item;
}
MyItemPtr pop_back()
{
item = deque.back();
deque.pop_back();
map.erase(item->name);
return item;
}
MyItemPtr find(const std::string &name)
{
std::map<std::string, MyItemPtr>::iterator iter = map.find(name);
if (iter == map.end())
return MyItemPtr();
else
return iter->second;
}
private:
std::deque<MyItemPtr> deque;
std::map<std::string, MyItemPtr> map;
};
使用它:
MyContainer container;
MyContainer::MyItemPtr a(new MyItem("blah", 1, 2));
container.push_back(a);
MyContainer::MyItemPtr b(new MyItem("foo", 5, 6));
container.push_front(b);
MyContainer::MyItemPtr f = container.find("blah");
if (f)
cout << f->name << ", " << f->something << ", " << f->another;
答案 2 :(得分:1)
您应该从std::map
开始,看看对数复杂度是否合适。
B +树会有点复杂,需要您自己的实施或研究才能找到开源的实施方案。但如果std::map
仍然证明不合适,那么根据您引用的要求和痛点(搜索),这是一个合理的选择。
例如,您可以将元素的值映射到std::list
中的迭代器。所有操作都是O(lg n)std::map
。
答案 3 :(得分:1)
您可以保留vector
,但也可以使用std::set
进行快速查询。
该集合不足以从开头/结尾删除元素,因为您实际上并不知道哪个是您插入的第一个/最后一个元素。您可以保留对这些元素的引用,但是为了支持删除,您将需要下一个等等,这会降级为使用一个容器。
答案 4 :(得分:0)
使用std::deque
。这是一个双端队列,它也可以用作标准接口的容器,例如std::stack
。
它通常使用准链接列表实现,并且在边缘插入和删除时分摊O(1)时间复杂度。
答案 5 :(得分:0)
如果有很多插入/删除链接列表会更合适 请注意链接列表(单个或双重)将产生相当大的开销(通常是指针的大小,但实现方式各不相同)。
标准模板库为您提供std :: list。