我偶然发现了这个问题:我似乎无法在正常std::set
的索引位置选择项目。这是STD中的一个错误吗?
下面是一个简单的例子:
#include <iostream>
#include <set>
int main()
{
std::set<int> my_set;
my_set.insert(0x4A);
my_set.insert(0x4F);
my_set.insert(0x4B);
my_set.insert(0x45);
for (std::set<int>::iterator it=my_set.begin(); it!=my_set.end(); ++it)
std::cout << ' ' << char(*it); // ups the ordering
//int x = my_set[0]; // this causes a crash!
}
我可以做些什么来解决这个问题?
答案 0 :(得分:53)
它不会导致崩溃,它只是不编译。 set
无权通过索引访问。
你可以得到这样的第n个元素:
std::set<int>::iterator it = my_set.begin();
std::advance(it, n);
int x = *it;
当然假设my_set.size() > n
。您应该知道此操作需要的时间大约与n
成比例。在C ++ 11中有一种更好的编写方式:
int x = *std::next(my_set.begin(), n);
同样,你必须先知道n
是否在界限内。
答案 1 :(得分:7)
std::set的常规实现是使用binary search trees,尤其是self-balancing binary search trees,例如red-black trees
他们没有给你恒定的时间访问第n个元素。但是,你似乎想要第一个。所以试试C++11:
auto it = my_set.begin();
int first=0;
if (it != my_set.end()) first = *it;
答案 2 :(得分:0)
这不是STD中的错误。 std::set
中没有随机访问权限。如果您需要按索引进行随机访问,则可以使用std::vector
答案 3 :(得分:0)
您无法在固定时间内访问它。
但是您可以在O(n)时间内到达任何元素。 例如
std::set<int>::iterator it;
it=my_set.begin();
advance(it,n);
cout<<*it;
答案 4 :(得分:0)
我认为 std::set
没有比 O(n)
时间更好地做到这一点的方法,但我最近使用可以完成大多数事情的集合和二叉索引树制作了这个数据结构std::set
可以,但是它也可以在O(log n)
时间内获取某个元素的索引,以及在O((log n) * (log n))
时间内获取特定索引处的元素:
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <math.h>
#include <vector>
#include <queue>
#include <bitset>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
using namespace std;
typedef pair<int, int> pii;
typedef pair<pii, int> piii;
typedef long long ll;
typedef pair<ll, ll> pll;
#define max(n, m) ((n>m)?n:m)
#define min(n, m) ((n<m)?n:m)
#define f first
#define s second
struct ss
{
// binary index tree (to mark elements)
int bit[1000010]; // set this number to the max you will use
// set (to store the numbers in order)
set<int> nums;
// the maximum element in the set (NOTE: this data structure works with marking in the BIT array, but you can make this better by using an unordered set to store all values that could appear inside of the set, but this will increase runtime by a high constant factor)
int mx;
// constructor
ss(int maxEl)
{
mx = maxEl + 5;
}
int sum(int arr[], int idx)
{
int ans = 0;
idx ++;
if(idx > mx + 5) return -1;
while(idx > 0)
{
ans += arr[idx];
idx -= idx & (-idx);
}
return ans;
}
void update(int arr[], int idx, int val, int size)
{
idx ++;
while(idx <= size)
{
arr[idx] += val;
idx += idx & (-idx);
}
}
int bs(int l, int r, int idx)
{
int mid = (l + r) / 2;
if(l == r) return mid + 1;
if(l == r - 1)
{
if(sum(bit, r) == idx) return r + 1;
return r;
}
if(sum(bit, mid) <= idx) return bs(mid, r, idx);
return bs(l, mid - 1, idx);
}
// regular set functions
set<int>::iterator find(int num) { return nums.find(num); }
set<int>::iterator lower_bound(int num) { return nums.lower_bound(num); }
set<int>::iterator upper_bound(int num) { return nums.upper_bound(num); }
int size() { return (int)nums.size(); }
set<int>::iterator begin() { return nums.begin(); }
set<int>::iterator end() { return nums.end(); }
bool empty() { return nums.empty(); }
// slightly modified insert and erase functions to also mark stuff in BIT (still O(log n) though)
void insert(int num)
{
if(nums.find(num) == nums.end())
update(bit, num, 1, mx); // marks the element in the BIT if it doesn't already exist
nums.insert(num);
}
void erase(int num)
{
if(nums.find(num) != nums.end())
update(bit, num, -1, mx); // unmarks the element in the BIT if it exists in the set
nums.erase(num);
}
// gets index (0-indexed) of a specific element in O(log n), returns -1 if element not in set
int idx(int num)
{
if(nums.find(num) == nums.end())
return -1;
return sum(bit, num - 1);
}
// gets the iterator of the element at a specific index (0-indexed) in O((log n) * (log n)), returns end of set if idx is invalid
set<int>::iterator at(int idx)
{
if(idx < 0 || idx >= nums.size())
return nums.end();
return nums.find(bs(0, mx, idx));
}
};
int main()
{
ss test = ss(1000);
test.insert(1);
test.insert(3);
test.insert(5);
test.insert(1);
test.insert(9);
test.insert(1000);
cout << *test.at(1) << "\n";
test.erase(3);
cout << *test.at(1) << "\n";
cout << test.idx(1) << "\n";
cout << *test.at(-1) << "\n";
}
这个集合确实有一些缺陷,因为它标记了二叉索引树中的元素,所以如果没有一些额外的修改,元素不能是负数或非常大,但在某些情况下它仍然是有帮助的。此外,使用 std::map
或某种其他类型的映射可以使集合与负数、大数以及其他数据类型一起使用,但这会使运行时间增加 O(log n)
和我认为您必须事先知道可能出现在集合中的所有元素,以便您可以将它们以正确的顺序存储在地图中。
编辑:我刚刚意识到已经有一个称为有序集的基于策略的数据结构,它具有与集合相同的功能,但可以在 O(log n)。在此处阅读更多信息:https://www.geeksforgeeks.org/ordered-set-gnu-c-pbds/。虽然这可能不适用于所有编译器
答案 5 :(得分:-2)
std::set<int> my_set;
my_set.insert(0x4A);
my_set.insert(0x4F);
my_set.insert(0x4B);
my_set.insert(0x45);
int arr[my_set.size()];
set<int>::iterator it = my_set.begin();
for (int i = 0; i < my_set.size(); i++) {
arr[i] = *it;
it++;
}
cout << arr[0];
编辑:已编辑的代码。您不能使用索引访问set,但如果您想要将元素从set复制到数组中,上面的方法将提供“索引”i,前提是您已经创建了一个足够大小的数组。