有限地合并两个列表

时间:2013-10-19 18:55:04

标签: c++ arrays big-o

我正在尝试合并两个数组/列表,其中必须比较数组的每个元素。如果两者中都有相同的元素,我将它们的总出现次数增加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)合并两个列表,但我目前处于遇到问题的包版广告中。任何伪代码视觉将不胜感激。

4 个答案:

答案 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);

查看 Live on Coliru, too

答案 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;
}

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