C++ 在O(logn)时间内查找给定范围内元素数量的数据结构是什么?
我正在解决一个问题,我意识到我需要一个具有以下属性的数据结构,但即使在谷歌搜索几个小时后也找不到。我相信STL图书馆太丰富了,不可能没有这个,所以我在这里问C++ 在O(logn)时间内查找给定范围内元素数量的数据结构是什么?,c++,algorithm,stl,C++,Algorithm,Stl,我正在解决一个问题,我意识到我需要一个具有以下属性的数据结构,但即使在谷歌搜索几个小时后也找不到。我相信STL图书馆太丰富了,不可能没有这个,所以我在这里问 在O(log(n))time中插入任何元素(应该能够包含重复的元素) 同时删除O(log(n))time中的一个元素 如果我想查询[a,b]范围内的榄香烯数,我 应该在O(log(n))time中获取该计数 如果我从头开始编写,对于第1部分和第2部分,我将使用集合或多集合,并修改它们的find()方法(在O(log(N))time中运行),
O(log(n))
time中插入任何元素(应该能够包含重复的元素)O(log(n))
time中的一个元素O(log(n))
time中获取该计数集合
或多集合
,并修改它们的find()
方法(在O(log(N))
time中运行),以返回索引而不是迭代器,这样我就可以
abs(find(a)-find(b))
所以我得到了log(N)时间中的元素计数。但不幸的是,find()
返回和迭代器
我已经研究了multiset()
,但在O(log(n))
时间内我无法完成要求3。需要O(n)
有什么提示可以轻松完成吗?尽管标准库的功能确实很好,但我认为您不会在其中找到任何符合这些特定要求的内容。正如您所注意到的,类似集合的结构返回非随机访问迭代器——提供随机访问(或您需要的某种距离函数)将带来极大的复杂性 您可以通过实现可索引的跳过列表来实现目标,该列表提供O(log(n))插入、删除和索引查找,如下所述: 可在此处找到实施指南:
您应该能够通过标准或稍加修改的B-树来实现这一点
通常,对于B树实现,大多数标准操作都是O(log(n))。虽然不是STL的一部分,但您可以使用gcc扩展的一部分;具体而言,您可以按如下方式初始化。代码使用
gcc
编译,无需任何外部库:
#include<iostream>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace __gnu_pbds;
using namespace std;
int main()
{
tree<int, /* key */
null_type, /* mapped */
less<int>, /* compare function */
rb_tree_tag, /* red-black tree tag */
tree_order_statistics_node_update> tr;
for(int i = 0; i < 20; ++i)
tr.insert(i);
/* number of elements in the range [3, 10) */
cout << tr.order_of_key(10) - tr.order_of_key(3);
}
#包括
#包括
#包括
使用名称空间_gnu_pbd;
使用名称空间std;
int main()
{
树tr;
对于(int i=0;i<20;++i)
tr.插入(i);
/*范围[3,10]中的元素数*/
cout这个任务的两个明显的数据结构是跳过列表(Jack O'Reilly已经提到)和顺序统计树的一些变体(Behzad提到了,但没有真正解释)
顺序统计树在每个节点中存储一条额外的信息。您可以存储许多不同的内容,但我发现最容易理解的是,如果每个节点在其左子树中存储元素的数量
插入时,当您沿着树向下移动以存储元素时,每次下降到树的左侧时,您都会增加计数。因为您只是修改要遍历的节点,所以这不会更改插入的O(log N)
。当您平衡时,您必须进行相应的调整(但是,同样,在进行旋转时,您只修改已经修改的节点的计数,因此(同样)不会影响总体复杂性
当您需要查找从一个元素到另一个元素的距离时,只需查找两个节点,每个节点的复杂度为O(logn)。通过从根目录初始化索引,然后从根目录更新索引,即可获得树中每个元素的索引(向左下降时减去计数,向右下降时相加).它当然不是最节省空间的,给出了O(3n),但它满足上面列出的插入、删除和距离标准。下面使用了链表、映射和无序映射
该列表用于维护顺序。通常按顺序插入列表是线性时间,但在键到列表的迭代器映射的帮助下,我们可以插入常量时间。将有序数据存储在列表中的优点是,它使用随机访问迭代器,这意味着您可以获得常量时间std::distance调用
映射用于获取关于在列表中的何处插入节点的提示。这是通过为给定键创建一个新的映射项,然后将迭代器递减1来完成的。此新迭代器的值为我们提供链接列表中适当的插入位置
无序的_映射为我们提供了随机访问列表迭代器的o(1)查找,允许我们获得o(logn)删除和距离时间
class logn
{
public:
using list_it = std::list<int>::iterator;
void insert(int i)
{
const bool first = list.empty();
auto original_it = map.insert({i,list.end()}).first; // o(logn)
auto hint = original_it;
if (!first)
--hint;
umap[i] = list.insert(hint->second,i); // o(1)
original_it->second = umap[i];
}
void remove(int i)
{
auto it = umap.find(i); // o(1)
list.erase(it->second); // o(1)
umap.erase(it); // o(1)
map.erase(map.find(i)); // o(logn)
}
list_it get(int i) const
{
return umap.find(i)->second;
}
unsigned int distance(int lower, int upper) const
{
return std::distance(get(lower), get(upper));
}
private:
std::list<int> list;
std::unordered_map<int, list_it> umap;
std::map<int, list_it> map;
};
类日志
{
公众:
使用list_it=std::list::iterator;
空白插入(int i)
{
const bool first=list.empty();
auto original_it=map.insert({i,list.end()}).first;//o(logn)
自动提示=原始提示;
如果(!第一个)
--暗示;
umap[i]=list.insert(提示->第二个,i);//o(1)
初始值->秒=umap[i];
}
无效删除(int i)
{
autoit=umap.find(i);//o(1)
list.erase(它->秒);//o(1)
umap.erase(它);//o(1)
map.erase(map.find(i));//o(logn)
}
列表获取(int i)常量
{
返回umap.find(i)->second;
}
无符号整数距离(整数下限,整数上限)常数
{
返回标准::距离(get(下)、get(上));
}
私人:
std::列表;
std::无序地图umap;
地图;
};
请不要无评论的否决票!!我没有可靠的guid手册,但是如果你能找到“Skiena,Steven S”的“算法设计手册”,那么就这样做。这是我的算法来源。我很确定哈希表应该有记录时间,但我没有sure@RNar,哈希表有O(1)对于insert和delete,但在给定范围内查找元素的数量是O(n)。此外,具有重复值也是一个问题problem@upr如果我想到了这些:,我就把它们标记为书签。跳过列表是这个任务的正确数据结构