我正在尝试解决以下问题:正在将数字插入容器中。每次插入一个数字时,我都需要知道容器中有多少元素大于或等于当前插入的数字。我相信这两种操作都可以以对数复杂度完成。
我的问题:
C++ 库中是否有可以解决问题的标准容器?我知道std::multiset
可以在对数时间内插入元素,但是如何查询呢?或者我应该实现一个数据结构(例如二叉搜索树)来解决它?
我正在尝试解决以下问题:正在将数字插入容器中。每次插入一个数字时,我都需要知道容器中有多少元素大于或等于当前插入的数字。我相信这两种操作都可以以对数复杂度完成。
我的问题:
C++ 库中是否有可以解决问题的标准容器?我知道std::multiset
可以在对数时间内插入元素,但是如何查询呢?或者我应该实现一个数据结构(例如二叉搜索树)来解决它?
好问题。我认为 STL 中没有任何东西可以满足您的需求(前提是您必须有对数时间)。正如 aschepler 在评论中所说,我认为最好的解决方案是实现 RB 树。你可以看看 STL 源代码,特别是stl_tree.h
看看你是否可以使用它的一部分。
更好的是,看看:(C++ 中的排名树)
其中包含实现的链接:
是的,您应该使用多重集来计算对数复杂度。但是计算距离是个问题,因为 set/map 迭代器是双向的,而不是 RandomAccess,std::distance 的复杂度为 O(n):
multiset<int> my_set;
...
auto it = my_map.lower_bound(3);
size_t count_inserted = distance(it, my_set.end()) // this is definitely O(n)
my_map.insert(make_pair(3);
您的复杂性问题很复杂。这是一个完整的分析:
如果您希望每次插入的复杂度为 O(log(n)),则需要一个已排序的结构作为一个集合。如果您希望结构在添加新项目时不重新分配或移动项目,则插入点距离计算将为 O(n)。如果事先知道插入大小,则在排序容器中不需要对数插入时间。您可以插入所有项目然后排序,它与集合中的 n * O(log(n)) 插入一样多。唯一的选择是使用一个专用的容器,比如加权 RB-tree。根据您的问题,这可能是解决方案,或者确实有些矫枉过正。
multiset
and distance
,你在插入时是 O(n.log(n)) (是的,n 次插入 * log(n) 每个插入时间),在距离计算上是 O(nn),但计算距离非常快。存在一种称为有序集的东西,它允许您在 O(logN) 时间内插入/删除元素(以及 std::set 必须提供的几乎所有其他功能)。它还提供了另外 2 个功能:查找第 K 个元素和**查找第 X 个元素的排名。问题是这不允许重复:(
不过不用担心!我们将使用单独的索引/优先级映射重复项,并定义一个新结构(称为 Ordered Multiset)!我在下面附上了我的实现以供参考。
最后,每次你想找到大于 x 的元素数时,调用函数 upper_bound(小于或等于 x 的元素数)并从有序多重集的大小中减去这个数字!
注意:PBDS 使用大量内存,所以这是一个约束,我建议使用二叉搜索树或 Fenwick 树。
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
struct ordered_multiset { // multiset supporting duplicating values in set
int len = 0;
const int ADD = 1000010;
const int MAXVAL = 1000000010;
unordered_map<int, int> mp; // hash = 96814
tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> T;
ordered_multiset() { len = 0; T.clear(), mp.clear(); }
inline void insert(int x){
len++, x += MAXVAL;
int c = mp[x]++;
T.insert((x * ADD) + c); }
inline void erase(int x){
x += MAXVAL;
int c = mp[x];
if(c) {
c--, mp[x]--, len--;
T.erase((x*ADD) + c); } }
inline int kth(int k){ // 1-based index, returns the
if(k<1 || k>len) return -1; // K'th element in the treap,
auto it = T.find_by_order(--k); // -1 if none exists
return ((*it)/ADD) - MAXVAL; }
inline int lower_bound(int x){ // Count of value <x in treap
x += MAXVAL;
int c = mp[x];
return (T.order_of_key((x*ADD)+c)); }
inline int upper_bound(int x){ // Count of value <=x in treap
x += MAXVAL;
int c = mp[x];
return (T.order_of_key((x*ADD)+c)); }
inline int size() { return len; } // Number of elements in treap
};
ordered_multiset s;
for(int i=0; i<n; i++) {
int x; cin>>x;
s.insert(x);
int ctr = s.size() - s.upper_bound(x);
cout<<ctr<<" ";
}
输入(n = 6):10 1 3 3 2
输出:0 1 1 1 3
听起来像是一个案例count_if
-尽管我承认这并不能解决对数复杂度的问题,但这需要排序类型。
vector<int> v = { 1, 2, 3, 4, 5 };
int some_value = 3;
int count = count_if(v.begin(), v.end(), [some_value](int n) { return n > some_value; } );
编辑完成以修复 lambda 函数的语法问题
如果整个数字范围足够小(大约几百万),则可以使用Fenwick 树相对容易地解决此问题。
尽管Fenwick 树不是 STL 的一部分,但它们都非常易于实现且节省时间。更新和查询的时间复杂度O(log N)
和常数因子都很低。