复制在 std 向量中只出现一次的元素的最有效方法是什么?
What is the most efficient way of copying elements that occur only once in a std vector?
我有一个包含如下元素的标准矢量:
[0 , 1 , 2 , 0 , 2 , 1 , 0 , 0 , 188 , 220 , 0 , 1 , 2 ]
查找和复制在此向量中仅出现一次的元素的最有效方法是什么,不包括蛮力 O(n^2) 算法?在这种情况下,新列表应包含 [188, 220]
- 做一个
unordered_map<DataType, Count> count;
- 迭代输入向量增加每个值的计数。有点像
count[value]++;
- 遍历值为 1 的
count
映射复制键。
是O(n)
。你有散列,所以对于小数据集,法线贴图可能更有效,但从技术上讲它会是 O(n log n)
.
这是处理离散数据集的好方法。
代码示例:
#include <iostream>
#include <unordered_map>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v{1,1,2,3,3,4};
unordered_map<int,int> count;
for (const auto& e : v) count[e]++;
vector<int> once;
for (const auto& e : count) if(e.second == 1) once.push_back(e.first);
for (const auto& e : once) cout << e << '\n';
return 0;
}
我尝试了一些想法。但我看不到解决方法 map
。 unordered_multiset
几乎是一个好方法......除了它不允许你迭代键。它有一种检查键数的方法,但您需要另一组来检测键。我不认为这是一种更简单的方法。在具有 auto
s 的现代 c++ 中,计数很容易。我也查看了 algorithm
库,但我没有找到任何 transfrom
、copy_if
、generate
等可以有条件地转换元素(地图条目 - > 计数为 1 时的值)。
@luk32 的回答绝对是解决这个问题最省时的方法。但是,如果您内存不足并且买不起unordered_map
,还有其他方法可以做到。
您可以先使用std::sort()
对向量进行排序。然后可以在一次迭代中找到非重复项。总体复杂度为 O(nlogn)
.
如果问题略有不同,并且您知道只有一个非重复元素,则可以使用 this code(代码在 Java 中)。这里的复杂度是O(n)
.
通用最优算法很少。哪种算法效果最好通常取决于正在处理的数据的属性。删除重复项就是这样一个例子。
v
是不是很小并且大部分都是唯一值?
auto lo = v.begin(), hi = v.end();
std::sort(lo, hi);
while (lo != v.end()) {
hi = std::mismatch(lo + 1, v.end(), lo).first;
lo = (std::distance(lo, hi) == 1) ? hi : v.erase(lo, hi);
}
是v
小而且大部分都是重复的吗?
auto lo = v.begin(), hi = v.end();
std::sort(lo, hi);
while (lo != v.end()) {
hi = std::upper_bound(lo + 1, v.end(), *lo);
lo = (std::distance(lo, hi) == 1) ? hi : v.erase(lo, hi);
}
v
巨大吗?
std::unordered_map<int, bool> keyUniqueness{};
keyUniqueness.reserve(v.size());
for (int key : v) {
bool wasMissing = keyUniqueness.find(key) == keyUniqueness.end();
keyUniqueness[key] = wasMissing;
}
v.clear();
for (const auto& element : keyUniqueness) {
if (element.second) { v.push_back(element.first); }
}
以此类推
由于您使用了 std::vector
,我推测您希望最大限度地利用它的所有优势,包括 reference locality。为此,我们需要在此处输入一些内容。我对下面的代码进行了基准测试...
我这里有一个线性 O(n)
算法(实际上是 O(nlog(n))
),它有点像 brian 的回答,但我使用 OutputIterators 而不是就地进行。前提是已经排序了
template<typename InputIterator, typename OutputIterator>
OutputIterator single_unique_copy(InputIterator first, InputIterator last, OutputIterator result){
auto previous = first;
if(previous == last || ++first == last) return result;
while(true){
if(*first == *previous)
while((++first != last) && (*first == *previous));
else
*(result++) = *previous;
if(first == last) break;
previous = first;
++first;
}
return ++result;
}
这是一个示例用法:
int main(){
std::vector<int> vm = {0, 1, 2, 0, 2, 1, 0, 0, 1, 88, 220, 0, 1, 2, 227, -8};
std::vector<int> kk;
std::sort(vm.begin(), vm.end());
single_unique_copy(vm.begin(), vm.end(), std::back_inserter(kk));
for(auto x : kk) std::cout << x << ' ';
return 0;
}
正如预期的那样,输出是:
-8, 88, 220, 227
您的用例可能与我的不同,因此,请先介绍一下...:-)
编辑:
- 使用luk32的算法和我的...使用1300万个元素...
按降序创建,每
i % 5
重复一次。
- 在调试版本下,luk32:
9.34
秒,我的:7.80
秒
- 在-O3下,luk32:
2.71
秒,我的0.52
秒
- Mingw5.1 64 位,Windows10,1.73Ghz Core i5 4210U,6GB DDR3 1600Mhz 内存
- 此处为基准,http://coliru.stacked-crooked.com/a/187e5e3841439742
对于较小的数字,差异仍然存在,直到它成为非关键代码
我有一个包含如下元素的标准矢量:
[0 , 1 , 2 , 0 , 2 , 1 , 0 , 0 , 188 , 220 , 0 , 1 , 2 ]
查找和复制在此向量中仅出现一次的元素的最有效方法是什么,不包括蛮力 O(n^2) 算法?在这种情况下,新列表应包含 [188, 220]
- 做一个
unordered_map<DataType, Count> count;
- 迭代输入向量增加每个值的计数。有点像
count[value]++;
- 遍历值为 1 的
count
映射复制键。
是O(n)
。你有散列,所以对于小数据集,法线贴图可能更有效,但从技术上讲它会是 O(n log n)
.
这是处理离散数据集的好方法。
代码示例:
#include <iostream>
#include <unordered_map>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v{1,1,2,3,3,4};
unordered_map<int,int> count;
for (const auto& e : v) count[e]++;
vector<int> once;
for (const auto& e : count) if(e.second == 1) once.push_back(e.first);
for (const auto& e : once) cout << e << '\n';
return 0;
}
我尝试了一些想法。但我看不到解决方法 map
。 unordered_multiset
几乎是一个好方法......除了它不允许你迭代键。它有一种检查键数的方法,但您需要另一组来检测键。我不认为这是一种更简单的方法。在具有 auto
s 的现代 c++ 中,计数很容易。我也查看了 algorithm
库,但我没有找到任何 transfrom
、copy_if
、generate
等可以有条件地转换元素(地图条目 - > 计数为 1 时的值)。
@luk32 的回答绝对是解决这个问题最省时的方法。但是,如果您内存不足并且买不起unordered_map
,还有其他方法可以做到。
您可以先使用std::sort()
对向量进行排序。然后可以在一次迭代中找到非重复项。总体复杂度为 O(nlogn)
.
如果问题略有不同,并且您知道只有一个非重复元素,则可以使用 this code(代码在 Java 中)。这里的复杂度是O(n)
.
通用最优算法很少。哪种算法效果最好通常取决于正在处理的数据的属性。删除重复项就是这样一个例子。
v
是不是很小并且大部分都是唯一值?
auto lo = v.begin(), hi = v.end();
std::sort(lo, hi);
while (lo != v.end()) {
hi = std::mismatch(lo + 1, v.end(), lo).first;
lo = (std::distance(lo, hi) == 1) ? hi : v.erase(lo, hi);
}
是v
小而且大部分都是重复的吗?
auto lo = v.begin(), hi = v.end();
std::sort(lo, hi);
while (lo != v.end()) {
hi = std::upper_bound(lo + 1, v.end(), *lo);
lo = (std::distance(lo, hi) == 1) ? hi : v.erase(lo, hi);
}
v
巨大吗?
std::unordered_map<int, bool> keyUniqueness{};
keyUniqueness.reserve(v.size());
for (int key : v) {
bool wasMissing = keyUniqueness.find(key) == keyUniqueness.end();
keyUniqueness[key] = wasMissing;
}
v.clear();
for (const auto& element : keyUniqueness) {
if (element.second) { v.push_back(element.first); }
}
以此类推
由于您使用了 std::vector
,我推测您希望最大限度地利用它的所有优势,包括 reference locality。为此,我们需要在此处输入一些内容。我对下面的代码进行了基准测试...
我这里有一个线性 O(n)
算法(实际上是 O(nlog(n))
),它有点像 brian 的回答,但我使用 OutputIterators 而不是就地进行。前提是已经排序了
template<typename InputIterator, typename OutputIterator>
OutputIterator single_unique_copy(InputIterator first, InputIterator last, OutputIterator result){
auto previous = first;
if(previous == last || ++first == last) return result;
while(true){
if(*first == *previous)
while((++first != last) && (*first == *previous));
else
*(result++) = *previous;
if(first == last) break;
previous = first;
++first;
}
return ++result;
}
这是一个示例用法:
int main(){
std::vector<int> vm = {0, 1, 2, 0, 2, 1, 0, 0, 1, 88, 220, 0, 1, 2, 227, -8};
std::vector<int> kk;
std::sort(vm.begin(), vm.end());
single_unique_copy(vm.begin(), vm.end(), std::back_inserter(kk));
for(auto x : kk) std::cout << x << ' ';
return 0;
}
正如预期的那样,输出是:
-8, 88, 220, 227
您的用例可能与我的不同,因此,请先介绍一下...:-)
编辑:
- 使用luk32的算法和我的...使用1300万个元素...
按降序创建,每
i % 5
重复一次。 - 在调试版本下,luk32:
9.34
秒,我的:7.80
秒 - 在-O3下,luk32:
2.71
秒,我的0.52
秒 - Mingw5.1 64 位,Windows10,1.73Ghz Core i5 4210U,6GB DDR3 1600Mhz 内存
- 此处为基准,http://coliru.stacked-crooked.com/a/187e5e3841439742
对于较小的数字,差异仍然存在,直到它成为非关键代码