使用C ++

时间:2016-04-16 20:22:26

标签: c++ arrays algorithm loops matching

我目前有一个解决方案,但我觉得它不如此问题效率高,所以我想看看是否有更快的方法。

我有两个数组(例如std :: vectors)。两个数组只包含排序但值稀疏的唯一整数值​​,即:1,4,12,13 ......我想问的是有快速的方法我可以找到INDEX到其中一个值的数组是相同的。例如,array1的值为1,4,12,13,array2的值为2,12,14,16。 array2中的第一个匹配值索引为1。数组的索引是重要的,因为我有其他数组包含将使用此索引的数据"匹配"。

我不仅限于使用数组,也可以使用地图。我只比较了两个数组一次。在第一次匹配传球后,它们不会再次被重复使用。在任一阵列中都可以有少量到大量的值(300,000 +),但是并不总是具有相同数量的值(这会使事情变得更容易)

更糟糕的情况是线性搜索O(N ^ 2)。使用map会让我更好O(log N),但我仍然会将数组转换为值的映射,索引对。

我目前不做任何容器类型转换的是这个。循环遍历两个数组中较小的一个。将小数组(array1)的当前元素与大数组(array2)的当前元素进行比较。如果array1元素值大于array2元素值,则递增array2的索引,直到它不再大于array1元素值(while循环)。然后,如果array1元素值小于array2元素,则转到下一个循环迭代并再次开始。否则它们必须相等,并且我的索引是匹配值的任一数组。

所以在这个循环中,如果所有值都匹配,我最好是O(N),如果没有匹配,则最好是O(2N)。所以我想知道是否有更快的东西?很难确定这两个阵列的匹配频率,但我希望我更倾向于大多数阵列,而不是匹配。

我希望我能够很好地解释这个问题,并感谢任何有关改进此问题的反馈或提示。

代码示例:

std::vector<int> array1 = {4,6,12,34};
std::vector<int> array2 = {1,3,6,34,40};

for(unsigned int i=0, z=0; i < array1.size(); i++) 
{
    int value1 = array1[i];
    while(value1 > array2[z] && z < array2.size())
        z++;

    if (z >= array2.size())
        break; // reached end of array2

    if (value1 < array2[z])
        continue;

    // we have a match, i and z indices have same value

}

结果将匹配array1 = [1,3]和array2 = [2,3]

的索引

3 个答案:

答案 0 :(得分:2)

由于数组已经排序,你可以使用非常类似于mergesort的合并步骤。这只是查看每个数组的head元素,并丢弃下部元素(下一个元素成为head)。找到匹配时停止(或当任一阵列耗尽时,表示不匹配)。

这是O(n),是你可以为任意干扰做的最快的。对于某些聚类分布,&#34;跳过&#34;可以使用方法而不是总是查看下一个元素。对于某些分布,这可能导致优于O(n)的运行时间。例如,给定数组1,2,3,4,510,11,12,13,14,算法可以确定在少至一次比较(5 <10)中没有找到匹配。

答案 1 :(得分:2)

存储的号码范围是多少?

我的意思是,你说数字是整数,排序和稀疏(即非顺序),并且可能有超过300,000,但它们的实际范围是多少?

我问的原因是,如果有一个相当小的上限, u ,(例如, u = 500,000),这是最快,最便捷的解决方案可能只是将值用作索引。是的,你可能在浪费记忆,但是4 * 真的是很多记忆吗?这取决于您的应用程序和目标平台(即,如果这是针对内存受限的嵌入式系统,与使用具有32GiB RAM的笔记本电脑相比,它不太可能是个好主意。)

当然,如果这些值或多或少均匀地分布在0-2 ^ 31-1上,那么这个粗略的想法并不具有吸引力,但是可能存在可以利用其他输入值的属性只是比范围。您可以手写一个相当简单的哈希函数。

另一件值得考虑的事情是你是否真的需要能够快速检索索引,或者它是否有助于能够快速判断索引是否存在于另一个数组中。一个值是否存在于特定索引只需要一个位,因此您可以使用32x较少的内存来获得输入值范围的位图(即屏蔽掉5个LSB并将其用作位位置,然后移动剩余的27位5位于右侧,并将其用作数组索引。)

最后,混合方法可能值得考虑,你可以决定你准备使用多少内存(比如你决定256KiB,它对应64Ki 4字节整数)然后用它作为查找表进入更小的子问题。假设您有300,000个LSB非常均匀分布的值。然后你可以使用16个LSB作为索引的查找表,这些列表(平均)只有4或5个元素长,然后你可以通过其他方式搜索。几年前,我研究过一些有大约200,000,000个单元的模拟软件,每个单元都有一个单元ID;某些实用程序功能使用二进制搜索来按ID标识单元格。通过这种策略,我们能够显着且非侵入地加速它。不是一个完美的解决方案,但是有很大改进。 (如果LSB不是均匀分布的,可能是您可以利用的属性,或者您可以选择一系列位,或者进行一些散列。)

我猜结果是“考虑某种哈希”,即使是“身份哈希”或简单的掩蔽/模数,还有一点“你的解决方案不一定非常一般”,而且有些“你的解决方案并不一定非常节省空间“顶上的酱汁。

答案 2 :(得分:1)

我使用一种算法编写了这个函数的实现,该算法在稀疏分布方面表现得比普通线性合并更好。

对于类似的分布,它具有O(n)复杂度,但是分布大大不同的范围,它应该在线性下执行,在最佳情况下接近O(log n)。但是,我无法证明最坏的情况并不比O(n log n)好。另一方面,我也无法找到最坏的情况。

我模仿它以便可以使用任何类型的范围,例如子范围或原始数组。从技术上讲,它也适用于非随机访问迭代器,但复杂性要大得多,因此不建议这样做。在这种情况下,我认为应该可以修改算法以回退到线性搜索,但我并没有打扰。

通过类似的分布,我的意思是这对数组有许多交叉。通过穿越,我的意思是,如果要按排序顺序将两个数组合并在一起,则可以从一个数组切换到另一个数组。

#include <algorithm>
#include <iterator>
#include <utility>

// helper structure for the search
template<class Range, class Out>
struct search_data {
    // is any there clearer way to get iterator that might be either
    // a Range::const_iterator or const T*?
    using iterator = decltype(std::cbegin(std::declval<Range&>()));
    iterator curr;
    const iterator begin, end;
    Out out;
};

template<class Range, class Out>
auto init_search_data(const Range& range, Out out) {
    return search_data<Range, Out>{
        std::begin(range),
        std::begin(range),
        std::end(range),
        out,
    };
}

template<class Range, class Out1, class Out2>
void match_indices(const Range& in1, const Range& in2, Out1 out1, Out2 out2) {
    auto search_data1 = init_search_data(in1, out1);
    auto search_data2 = init_search_data(in2, out2);

    // initial order is arbitrary
    auto lesser = &search_data1;
    auto greater = &search_data2;

    // if either range is exhausted, we are finished
    while(lesser->curr != lesser->end
            && greater->curr != greater->end) {
        // difference of first values in each range
        auto delta = *greater->curr - *lesser->curr;

        if(!delta) { // matching value was found
            // store both results and increment the iterators
            *lesser->out++ = std::distance(lesser->begin, lesser->curr++);
            *greater->out++ = std::distance(greater->begin, greater->curr++);
            continue; // then start a new iteraton
        }

        if(delta < 0) { // set the order of ranges by their first value
            std::swap(lesser, greater);
            delta = -delta; // delta is always positive after this
        }

        // next crossing cannot be farther than the delta
        // this assumption has following pre-requisites: 
        // range is sorted, values are integers, values in the range are unique
        auto range_left = std::distance(lesser->curr, lesser->end);
        auto upper_limit =
            std::min(range_left, static_cast<decltype(range_left)>(delta));

        // exponential search for a sub range where the value at upper bound
        // is greater than target, and value at lower bound is lesser
        auto target = *greater->curr;
        auto lower = lesser->curr;
        auto upper = std::next(lower, upper_limit);
        for(int i = 1; i < upper_limit; i *= 2) {
            auto guess = std::next(lower, i);
            if(*guess >= target) {
                upper = guess;
                break;
            }
            lower = guess;
        }

        // skip all values in lesser,
        // that are less than the least value in greater
        lesser->curr = std::lower_bound(lower, upper, target);
    }
}

#include <iostream>
#include <vector>

int main() {
    std::vector<int> array1 = {4,6,12,34};
    std::vector<int> array2 = {1,3,6,34};

    std::vector<std::size_t> indices1;
    std::vector<std::size_t> indices2;

    match_indices(array1, array2,
                  std::back_inserter(indices1),
                  std::back_inserter(indices2));

    std::cout << "indices in array1: ";
    for(std::vector<int>::size_type i : indices1)
        std::cout << i << ' ';

    std::cout << "\nindices in array2: ";
    for(std::vector<int>::size_type i : indices2)
        std::cout << i << ' ';
    std::cout << std::endl;
}