2

我正在尝试合并两个数组/列表,其中必须比较数组的每个元素。如果它们两者中存在相同的元素,我将它们的总出现次数增加一。数组都是二维的,其中每个元素都有一个计数器用于其出现。我知道这两个数组都可以与 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)合并两个列表,但是我目前遇到了绑定问题的障碍。任何伪代码视觉将不胜感激。

4

4 回答 4

2

我非常喜欢Stepanov 的 Efficient Programming,尽管它们相当慢。在第 6 节和第 7 节中(如果我没记错的话),他讨论了算法add_to_counter()reduce_counter(). 当然,这两种算法都是微不足道的,但可以用来实现非递归合并排序而不需要太多努力。唯一可能不明显的见解是组合操作可以将两个元素简化为一个序列,而不仅仅是一个元素。要就地执行操作,您实际上会使用合适的类来存储迭代器(即,数组中的指针)来表示数组的部分视图。

我还没有看过会话 7 之后的会话(实际上甚至还没有观看完整的会话 7),但我完全希望他实际上介绍了如何使用counter会话 7 中生成的内容来实现,例如合并排序。当然,合并排序的运行时复杂度是O(n ln n),并且当使用计数器方法时,它将使用O(ln n)辅助空间。

于 2013-10-19T19:31:40.240 回答
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;
        }
    }
}

这真的很简单!

测试程序:See 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);

也可以看到Live on Coliru

于 2013-10-19T19:44:38.090 回答
0

一个需要两倍内存的简单算法是对两个输入进行排序(O(n log n)),然后从两个列表的头部依次选择元素并进行合并(O(n))。总成本将是 O(n log n) 和 O(n) 额外内存(两个输入中最小的额外大小)

于 2013-10-19T20:19:56.697 回答
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;
}

简短的解释:因为你提到,最后的安排是不必要的,我做了简单的合并(没有排序,谁问排序?)计数,其中结果包含对计数器的引用,所以不需要遍历数组来更新计数器。

于 2013-10-19T21:17:17.410 回答