任务是从双数组中提取k
个最小元素及其索引,可能包括与k
最小的元素绑定的更多元素。 E.g:
input: {3.3,1.1,6.5,4.2,1.1,3.3}
output (k=3): {1,1.1} {4,1.1} {0,3.3} {5,3.3}
[这似乎是一项非常常见的任务,但我无法在SO上找到类似的线程 - 处理关系。希望我没有错过,也没有重复这个问题。]
我提出了以下解决方案,该解决方案有效并且似乎在复杂性方面相当有效。例如。随机1MLN双倍和k=10
MSVC 2013需要大约40ms。我想知道是否更好/更清洁/ 更高效(对于大数据和/或大k
)< / em>执行此任务的方式(k
值的验证和类似的事情是我们的范围)。避免使用所有元素分配队列?利用std::partial_sum
或std::nth_element
?
typedef std::pair<double, int> idx_pair;
typedef std::priority_queue<idx_pair, std::vector<idx_pair>, std::greater<idx_pair>> idx_queue;
std::vector<idx_pair> getKSmallest(std::vector<double> const& data, int k)
{
idx_queue q;
{
std::vector<idx_pair> idxPairs(data.size());
for (auto i = 0; i < data.size(); i++)
idxPairs[i] = idx_pair(data[i], i);
q = idx_queue(std::begin(idxPairs), std::end(idxPairs));
};
std::vector<idx_pair> result;
auto topPop = [&q, &result]()
{
result.push_back(q.top());
q.pop();
};
for (auto i = 0; i < k; i++)
topPop();
auto const largest = result.back().first;
while (q.empty() == false)
{
if (q.top().first == largest)
topPop();
else
break;
}
return result;
}
工作示例是here。
答案 0 :(得分:1)
正如提问者所指出的,我建议首先复制double的向量并使用nth_element找出第k个元素。 然后进行线性扫描并获得小于或等于第k个元素的元素。时间复杂度应该是线性的。
但是,比较双倍时应该小心。
vector<idx_pair> getKSmallest(vector<double> const& data, int k){
vector<double> data_copy = data;
nth_element(data_copy.begin(), data_copy.begin() + k, data_copy.end());
vector<idx_pair> result;
double kth_element = data_copy[k - 1];
for (int i = 0; i < data.size(); i++)
if (data[i] <= kth_element)
result.push_back({i, data[i]});
return result;
}
更新:也可以通过维护最大为k的最大堆来找到kth_element。
在nth_element方法中,只需要O(k)内存用于堆而不是O(n)内存。
它需要O(n log k)时间,但如果k很小,那么我认为它应该与O(n)方法相当。 我不确定,但我的理由是堆可能被缓存,你不需要花时间复制数据。
vector<idx_pair> getKSmallest(vector<double> const& data, int k)
{
priority_queue<double> pq;
for (auto d : data){
if (pq.size() >= k && pq.top() > d){
pq.push(d)
pq.pop();
}
else if (pq.size() < k)
pq.push(d);
}
double kth_element = pq.top();
vector<idx_pair> result;
for (int i = 0; i < data.size(); i++)
if (data[i] <= kth_element)
result.push_back({i, data[i]});
return result;
}
答案 1 :(得分:1)
这是@ piotrekg2建议的替代解决方案 - 使用nth_element
平均O(N)复杂度:
bool equal(double value1, double value2)
{
return value1 == value2 || std::abs(value2 - value1) <= std::numeric_limits<double>::epsilon();
}
std::vector<idx_pair> getNSmallest(std::vector<double> const& data, int n)
{
std::vector<idx_pair> idxPairs(data.size());
for (auto i = 0; i < data.size(); i++)
idxPairs[i] = idx_pair(data[i], i);
std::nth_element(std::begin(idxPairs), std::begin(idxPairs) + n, std::end(idxPairs));
std::vector<idx_pair> result(std::begin(idxPairs), std::begin(idxPairs) + n);
auto const largest = result.back().first;
for (auto it = std::begin(idxPairs) + n; it != std::end(idxPairs); ++it)
if (equal(it->first, largest))
result.push_back(*it);
return result;
}
实际上,代码看起来更清晰一些。但是,我已经运行了一些测试,根据经验,这个解决方案比std::priority_queue
的原始解决方案略慢。
注意:Petar下面的答案使用std::nth_element
提供了一个类似的解决方案,在我的实验中,这个解决方案比这个更好,并且比使用std::priority_queue
的解决方案更好 - 可能是因为消除了在对上操作并使用原始双精度操作。