我正在尝试合并两个数组/列表,其中必须比较数组的每个元素。如果两者中都有相同的元素,我将它们的总出现次数增加1。这些数组都是2D,其中每个元素都有一个计数器用于出现。我知道这两个数组都可以与O(n ^ 2)中的双for循环进行比较,但是我受到O(nlogn)的界限的限制。如果存在多个出现次数,则最终数组将具有来自两个列表的所有元素及其增加的计数器
Array A[][] = [[8,1],[5,1]]
Array B[][] = [[2,1],[8,1]]
合并完成后,我应该得到一个像这样的数组
Array C[][] = [[2,1],[8,2],[8,2],[5,1]]
元素的排列不一定是必要的。
从阅读中,Mergesort需要O(nlogn)
合并两个列表,但我目前处于遇到问题的包版广告中。任何伪代码视觉将不胜感激。
答案 0 :(得分:2)
我非常喜欢Stepanov's Efficient Programming,尽管它们很慢。在第6和第7节(如果我没记错的话),他讨论了算法add_to_counter()
和reduce_counter()
。当然,这两种算法都是微不足道的,但可以用来实现非递归合并排序,而不需要太多努力。唯一可能不明显的见解是组合操作可以将两个元素减少为一个序列,而不仅仅是一个元素。要在就地执行操作,你实际上使用一个合适的类来存储迭代器(即数组中的指针),以表示数组的局部视图。
我没有看过会议7之后的会议(实际上甚至没有完整的会话7),但我完全希望他实际上介绍如何使用会话7中生成的counter
来实现,例如,合并排序。当然,merge-sort的运行时复杂性为O(n ln n)
,并且在使用计数器方法时,它将使用O(ln n)
辅助空间。
答案 1 :(得分:0)
您可以编写一个算法来合并它们,方法是按顺序依次遍历两个序列,并在适当的位置插入。
我在这里选择了一个(看似更贴切的)数据结构:std::map<Value, Occurence>
:
#include <map>
using namespace std;
using Value = int;
using Occurence = unsigned;
using Histo = map<Value, Occurence>;
如果你坚持连续存储,那么boost::flat_map<>
应该是你的朋友(以及直接替代)。
算法(使用您的输入进行测试,阅读注释以获得解释):
void MergeInto(Histo& target, Histo const& other)
{
auto left_it = begin(target), left_end = end(target);
auto right_it = begin(other), right_end = end(other);
auto const& cmp = target.value_comp();
while (right_it != right_end)
{
if ((left_it == left_end) || cmp(*right_it, *left_it))
{
// insert at left_it
target.insert(left_it, *right_it);
++right_it; // and carry on
} else if (cmp(*left_it, *right_it))
{
++left_it; // keep left_it first, so increment it
} else
{
// keys match!
left_it->second += right_it->second;
++left_it;
++right_it;
}
}
}
这真的很直截了当!
测试计划:查看 Live On Coliru
#include <iostream>
// for debug output
static inline std::ostream& operator<<(std::ostream& os, Histo::value_type const& v) { return os << "{" << v.first << "," << v.second << "}"; }
static inline std::ostream& operator<<(std::ostream& os, Histo const& v) { for (auto& el : v) os << el << " "; return os; }
//
int main(int argc, char *argv[])
{
Histo A { { 8, 1 }, { 5, 1 } };
Histo B { { 2, 1 }, { 8, 1 } };
std::cout << "A: " << A << "\n";
std::cout << "B: " << B << "\n";
MergeInto(A, B);
std::cout << "merged: " << A << "\n";
}
印刷:
A: {5,1} {8,1}
B: {2,1} {8,1}
merged: {2,1} {5,1} {8,2}
如果你真的想要合并到一个新对象(C
),你可以稍微改变一下界面:
// convenience
Histo Merge(Histo const& left, Histo const& right)
{
auto copy(left);
MergeInto(copy, right);
return copy;
}
现在你可以写
Histo A { { 8, 1 }, { 5, 1 } };
Histo B { { 2, 1 }, { 8, 1 } };
auto C = Merge(A, B);
答案 2 :(得分:0)
需要两倍内存的简单算法是对两个输入(O(n log n))进行排序,然后从两个列表的头部顺序选取元素并进行合并(O(n))。总成本为O(n log n),带有O(n)额外内存(两个输入中最小的附加大小)
答案 3 :(得分:0)
这是我的基于桶计数的算法
时间复杂度:O(n)
内存复杂度:O(max),其中max是数组中的最大元素
输出: [8,2] [5,1] [2,1] [8,2]
代码:
#include <iostream>
#include <vector>
#include <iterator>
int &refreshCount(std::vector<int> &counters, int in) {
if((counters.size() - 1) < in) {
counters.resize(in + 1);
}
return ++counters[in];
}
void copyWithCounts(std::vector<std::pair<int, int> >::iterator it,
std::vector<std::pair<int, int> >::iterator end,
std::vector<int> &counters,
std::vector<std::pair<int, int&> > &result
) {
while(it != end) {
int &count = refreshCount(counters, (*it).first);
std::pair<int, int&> element((*it).first, count);
result.push_back(element);
++it;
}
}
void countingMerge(std::vector<std::pair<int, int> > &array1,
std::vector<std::pair<int, int> > &array2,
std::vector<std::pair<int, int&> > &result) {
auto array1It = array1.begin();
auto array1End = array1.end();
auto array2It = array2.begin();
auto array2End = array2.end();
std::vector<int> counters = {0};
copyWithCounts(array1It, array1End, counters, result);
copyWithCounts(array2It, array2End, counters, result);
}
int main()
{
std::vector<std::pair<int, int> > array1 = {{8, 1}, {5, 1}};
std::vector<std::pair<int, int> > array2 = {{2, 1}, {8, 1}};
std::vector<std::pair<int, int&> > result;
countingMerge(array1, array2, result);
for(auto it = result.begin(); it != result.end(); ++it) {
std::cout << "[" << (*it).first << "," << (*it).second << "] ";
}
return 0;
}
简短说明: 因为你提到过,最后的安排是没有必要的,我做了简单的合并(没有排序,谁问排序?)与计数,其中结果包含对计数器的引用,所以不需要遍历数组来更新计数器。