Быстрое пересечение множеств: C++ по сравнению с C#

Наследование является злом и должно быть осуждается.

Правда в том, что агрегация лучше во всех случаях. Языки ООП со статической типизацией не могут избежать наследования, это единственный способ описать, что метод хочет от типа. Но динамические языки и типизация утки могут жить без него. Рубиновые миксины намного мощнее наследования и намного более управляемы.

8
задан Bill the Lizard 29 August 2011 в 02:05
поделиться

13 ответов

Есть несколько проблем с вашим тестом.

Во-первых, вы не проверяете пересечение множеств, а «создаете пару массивов, заполняете их случайными числами, а затем выполняете пересечение множеств» . Вы должны рассчитывать время только для той части кода, которая вас действительно интересует. Даже если вы собираетесь делать эти вещи, их здесь не следует тестировать. Измеряйте одну вещь за раз, чтобы уменьшить неопределенность. Если вы хотите, чтобы ваша реализация C ++ работала лучше, вам сначала нужно знать, какая ее часть медленнее, чем ожидалось. Это означает, что вам нужно отделить код настройки от теста пересечения.

Во-вторых, вы должны запустить тест большое количество раз, чтобы учесть возможные эффекты кэширования и другие неопределенности. (И, вероятно, вывести одно общее время, скажем, для 1000 запусков, а не индивидуальное время для каждого. Таким образом вы уменьшите неопределенность таймера, который может иметь ограниченное разрешение и сообщать о неточных результатах при использовании в диапазоне 0-20 мс.

Кроме того, насколько я могу прочитать из документации, входные данные для set_intersection должны быть отсортированы, какой set2 не будет. Кажется, нет причин использовать unordered_map , когда unordered_set будет намного лучше соответствовать тому, что вы делаете.

Что касается необходимого кода настройки, обратите внимание, что вам, вероятно, не нужно заполнять векторы, чтобы пройти пересечение. И ваша собственная реализация, и set_intersection уже работают с итераторами, поэтому вы можете просто передать им пару итераторов в структуры данных, в которых уже находятся ваши входные данные.

Несколько более конкретных комментариев к вашему коду:

  • Используйте ++ итератор вместо итератора ++
  • вместо вызова vector.end () на каждой итерации цикла, вызывайте его один раз и кэшируйте результат
  • эксперимента с использованием отсортированных векторов vs std :: set vs unordered_set (не unordered_map )

Edit:

Я не пробовал ваш C # версия, поэтому я не могу правильно сравнить числа, но вот мой модифицированный тест. Каждый из них запускается 1000 раз на Core 2 Quad 2,5 ГГц с 4 ГБ ОЗУ:

std::set_intersection on std::set: 2606ms
std::set_intersection on tr1::unordered_set: 1014ms
std::set_intersection on sorted vectors: 171ms
std::set_intersection on unsorted vectors: 10140ms

Последний вариант немного несправедлив, потому что он должен и копировать, и сортировать векторы. В идеале, только сортировка должна быть частью теста. Я попытался создать версию, в которой использовался массив из 1000 несортированных векторов (поэтому мне не нужно было копировать несортированные данные на каждой итерации), но производительность была примерно такой же, или немного хуже, потому что это приведет к постоянным пропускам кеша, поэтому я вернулся к этой версии

И моему коду:

#define _SECURE_SCL 0

#include <ctime>
#include <vector>
#include <set>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <windows.h>

template <typename T, typename OutIter>
void stl_intersect(const T& set1, const T& set2, OutIter out){
    std::set_intersection(set1.begin(), set1.end(), set2.begin(), set2.end(), out);
}

template <typename T, typename OutIter>
void sort_stl_intersect(T& set1, T& set2, OutIter out){
    std::sort(set1.begin(), set1.end());
    std::sort(set2.begin(), set2.end());
    std::set_intersection(set1.begin(), set1.end(), set2.begin(), set2.end(), out);
}


template <typename T>
void init_sorted_vec(T first, T last){
    for ( T cur = first; cur != last; ++cur)
    {
        int i = cur - first;
        int value = 1000000000 + i;
        *cur = value;
    }
}

template <typename T>
void init_unsorted_vec(T first, T last){
    for ( T cur = first; cur != last; ++cur)
    {
        int i = rand() % 200000 + 1;
        i *= 10;

        int value = 1000000000 + i;
        *cur = value;
    }
}

struct resize_and_shuffle {
    resize_and_shuffle(int size) : size(size) {}

    void operator()(std::vector<int>& vec){
        vec.resize(size);

    }
    int size;
};

int main()
{
    srand ( time(NULL) );
    std::vector<int> out(100000);

    std::vector<int> sortedvec1(100000);
    std::vector<int> sortedvec2(1000);

    init_sorted_vec(sortedvec1.begin(), sortedvec1.end());
    init_unsorted_vec(sortedvec2.begin(), sortedvec2.end());
    std::sort(sortedvec2.begin(), sortedvec2.end());

    std::vector<int> unsortedvec1(sortedvec1.begin(), sortedvec1.end());
    std::vector<int> unsortedvec2(sortedvec2.begin(), sortedvec2.end());

    std::random_shuffle(unsortedvec1.begin(), unsortedvec1.end());
    std::random_shuffle(unsortedvec2.begin(), unsortedvec2.end());

    std::vector<int> vecs1[1000];
    std::vector<int> vecs2[1000];

    std::fill(vecs1, vecs1 + 1000, unsortedvec1);
    std::fill(vecs2, vecs2 + 1000, unsortedvec2);

    std::set<int> set1(sortedvec1.begin(), sortedvec1.end());
    std::set<int> set2(sortedvec2.begin(), sortedvec2.end());

    std::tr1::unordered_set<int> uset1(sortedvec1.begin(), sortedvec1.end());
    std::tr1::unordered_set<int> uset2(sortedvec2.begin(), sortedvec2.end());

    DWORD start, stop;
    DWORD delta[4];

    start = GetTickCount();
    for (int i = 0; i < 1000; ++i){
        stl_intersect(set1, set2, out.begin());
    }
    stop = GetTickCount();
    delta[0] = stop - start;

    start = GetTickCount();
    for (int i = 0; i < 1000; ++i){
        stl_intersect(uset1, uset2, out.begin());
    }
    stop = GetTickCount();
    delta[1] = stop - start;

    start = GetTickCount();
    for (int i = 0; i < 1000; ++i){
        stl_intersect(sortedvec1, sortedvec2, out.begin());
    }
    stop = GetTickCount();
    delta[2] = stop - start;

    start = GetTickCount();
    for (int i = 0; i < 1000; ++i){
        sort_stl_intersect(vecs1[i], vecs1[i], out.begin());
    }
    stop = GetTickCount();
    delta[3] = stop - start;

    std::cout << "std::set_intersection on std::set: " << delta[0] << "ms\n";
    std::cout << "std::set_intersection on tr1::unordered_set: " << delta[1] << "ms\n";
    std::cout << "std::set_intersection on sorted vectors: " << delta[2] << "ms\n";
    std::cout << "std::set_intersection on unsorted vectors: " << delta[3] << "ms\n";


    return 0;
}

Нет причин, по которым C ++ всегда должен быть быстрее, чем C #. У C # есть несколько ключевых преимуществ, которые требуют особого внимания, чтобы конкурировать с C ++. Первое, что я могу придумать, - это то, что динамическое размещение в .NET-мире смехотворно дешево. Каждый раз, когда вектор C ++, set или unordered_set (или любой другой контейнер) должен изменять размер или расширяться, это очень дорогостоящая операция malloc . В .NET выделение кучи - это немного больше, чем добавление смещения к указателю.

Так что, если вы хотите, чтобы версия C ++ конкурировала, вам, вероятно, придется решить эту проблему, позволяя вашим контейнерам изменять размер без необходимости выполнять фактические распределения кучи, вероятно, с помощью пользовательских распределителей для контейнеров (возможно, boost :: pool может быть хорошей ставкой, или вы можете попробовать свернуть свой собственный)

Другая проблема заключается в том, что set_difference работает только с отсортированным вводом , и чтобы воспроизвести результаты тестов, которые включают сортировку, мы должны делать новую копию несортированных данных на каждой итерации, что является дорогостоящим (хотя, опять же, использование настраиваемых распределителей очень поможет). Я не знаю, какую форму принимает ваш ввод, но возможно, что вы можете отсортировать ввод напрямую, не копируя его, а затем запустить set_difference непосредственно на нем. (Это было бы легко сделать, если ваш ввод представляет собой, по крайней мере, массив или контейнер STL.)

Одним из ключевых преимуществ STL является то, что он настолько гибок, что может работать практически с любой входной последовательностью. В C # вам в значительной степени нужно скопировать ввод в список или словарь или что-то в этом роде, но в C ++ вы можете уйти, запустив std :: sort и set_intersection на необработанном вводе.

Наконец, конечно, попробуйте запустить код через профилировщик и посмотреть, на что именно тратится время. Вы также можете попробовать запустить код через GCC. Мне кажется, что производительность STL в MSVC иногда немного нестабильна. Возможно, стоит протестировать с другим компилятором, чтобы увидеть, есть ли у вас аналогичные тайминги.

Наконец, вы можете найти эти сообщения в блоге, относящиеся к производительности C ++ и C #: http://blogs.msdn.com/ricom/archive/2005/05/10/416151.aspx

По сути, моральный дух их таков, что да, вы можете получить лучшую производительность на C ++, но это удивительный объем работы.

27
ответ дан 5 December 2019 в 04:37
поделиться

Вы ВСЕГДА передаете векторы по стоимости. Что было бы нормально, если бы вы их тоже не копировали.

модуль вставки не помещал значения в конец вектора, где он выполняется быстро. Он сделал это только при первой вставке, после чего он вставил значение в начало массива (где конец использовался для указания).

вы дважды искали значение в версии хеш-карты, когда вы обновляли значение. Почему обновляется это событие значения?

запустите этот код и опубликуйте свои тайминги.

// MapPerformance.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <hash_map>
#include <vector>
#include <iostream>
#include <time.h>
#include <algorithm>
#include <set>

#include <boost\unordered\unordered_set.hpp>

#include "timer.h"

using namespace std;
using namespace stdext;
using namespace boost;

int runIntersectionTest(const vector<int>& set1, const vector<int>& set2)
{
    // hash_map<int,int> theMap;
    // map<int,int> theMap;
    unordered_set<int> theSet;      

     theSet.insert( set1.begin(), set2.end() );

    int intersectionSize = 0;

    vector<int>::const_iterator set2_end = set2.end();

    for ( vector<int>::const_iterator iterator = set2.begin(); iterator != set2_end; ++iterator )
    {
        if ( theSet.find(*iterator) != theSet.end() )
        {
                intersectionSize++;
        }
    }

    return intersectionSize;
}

int runSetIntersection( vector<int> set1, vector<int> set2)
{   
    // Sort the data
    sort(set1.begin(),set1.end());
    sort(set2.begin(),set2.end());

    vector<int> intersection;
    intersection.reserve(1000);

    set_intersection(set1.begin(),set1.end(), set2.begin(), set2.end(), back_inserter(intersection));

    return intersection.size(); 
}

void createSets( vector<int>& set1, vector<int>& set2 )
{
    srand ( time(NULL) );

    set1.reserve(100000);
    set2.reserve(1000);

    // Create 100,000 values for set1
    for ( int i = 0; i < 100000; i++ )
    {
        int value = 1000000000 + i;
        set1.push_back(value);
    }

    // Try to get half of our values intersecting
    float ratio = 200000.0f / RAND_MAX;


    // Create 1,000 values for set2
    for ( int i = 0; i < 1000; i++ )
    {
        int random = rand() * ratio + 1;

        int value = 1000000000 + random;
        set2.push_back(value);
    }

    // Make sure set1 is in random order (not sorted)
    random_shuffle(set1.begin(),set1.end());
}

int _tmain(int argc, _TCHAR* argv[])
{
    int intersectionSize = 0;

    vector<int> set1, set2;     
    createSets( set1, set2 );

    Timer timer;
    for ( int i = 0; i < 1000; i++ )
    {
        intersectionSize = runIntersectionTest(set1, set2);
    }
    timer.Stop();

    cout << "Found the intersection of " << intersectionSize << " values (using unordered_map) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;

    timer.Reset();
    for ( int i = 0; i < 1000; i++ )
    {
        intersectionSize = runSetIntersection(set1,set2);
    }
    timer.Stop();

    cout << "Found the intersection of " << intersectionSize << " values (using set_intersection) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;

    getchar();

    return 0;
}
0
ответ дан 5 December 2019 в 04:37
поделиться

Latest benchmark:

Found the intersection of 504 values (using unordered_map) 1000 times, in 28827.6ms
Found the intersection of 495 values (using set_intersection) 1000 times, in 9817.69ms
Found the intersection of 504 values (using unordered_set) 1000 times, in 24769.1ms

I think the 504 - 495 difference happens because there are a couple dupe values.

Code:

// MapPerformance.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <hash_map>
#include <vector>
#include <iostream>
#include <time.h>
#include <algorithm>
#include <set>
#include <unordered_set>

#include <boost\unordered\unordered_map.hpp>

#include "timer.h"

using namespace std;
using namespace stdext;
using namespace boost;
using namespace tr1;


int runIntersectionTest2(const vector<int>& set1, const vector<int>& set2)
{
    // hash_map<int,int> theMap;
    // map<int,int> theMap;
    unordered_set<int> theSet;      

     theSet.insert( set1.begin(), set1.end() );

    int intersectionSize = 0;

    vector<int>::const_iterator set2_end = set2.end();

    for ( vector<int>::const_iterator iterator = set2.begin(); iterator != set2_end; ++iterator )
    {
        if ( theSet.find(*iterator) != theSet.end() )
        {
                intersectionSize++;
        }
    }

    return intersectionSize;
}

int runIntersectionTest(const vector<int>& set1, const vector<int>& set2)
{
    // hash_map<int,int> theMap;
    // map<int,int> theMap;
    unordered_map<int,int> theMap;  

    vector<int>::const_iterator set1_end = set1.end();

    // Now intersect the two sets by populating the map
    for ( vector<int>::const_iterator iterator = set1.begin(); iterator != set1_end; ++iterator )
    {
        int value = *iterator;

        theMap[value] = 1;
    }

    int intersectionSize = 0;

    vector<int>::const_iterator set2_end = set2.end();

    for ( vector<int>::const_iterator iterator = set2.begin(); iterator != set2_end; ++iterator )
    {
        int value = *iterator;

        unordered_map<int,int>::iterator foundValue = theMap.find(value);

        if ( foundValue != theMap.end() )
        {
            theMap[value] = 2;

            intersectionSize++;
        }
    }

    return intersectionSize;

}

int runSetIntersection(const vector<int>& set1_unsorted, const vector<int>& set2_unsorted)
{   
    // Create two vectors
    std::vector<int> set1(set1_unsorted.size());
    std::vector<int> set2(set2_unsorted.size());

    // Copy the unsorted data into them
    std::copy(set1_unsorted.begin(), set1_unsorted.end(), set1.begin());
    std::copy(set2_unsorted.begin(), set2_unsorted.end(), set2.begin());

    // Sort the data
    sort(set1.begin(),set1.end());
    sort(set2.begin(),set2.end());

    vector<int> intersection;
    intersection.reserve(1000);

    set_intersection(set1.begin(),set1.end(), set2.begin(), set2.end(), back_inserter(intersection));

    return intersection.size(); 
}

void createSets( vector<int>& set1, vector<int>& set2 )
{
    srand ( time(NULL) );

    set1.reserve(100000);
    set2.reserve(1000);

    // Create 100,000 values for set1
    for ( int i = 0; i < 100000; i++ )
    {
        int value = 1000000000 + i;
        set1.push_back(value);
    }

    // Try to get half of our values intersecting
    float ratio = 200000.0f / RAND_MAX;


    // Create 1,000 values for set2
    for ( int i = 0; i < 1000; i++ )
    {
        int random = rand() * ratio + 1;

        int value = 1000000000 + random;
        set2.push_back(value);
    }

    // Make sure set1 is in random order (not sorted)
    random_shuffle(set1.begin(),set1.end());
}

int _tmain(int argc, _TCHAR* argv[])
{
    int intersectionSize = 0;

    vector<int> set1, set2; 
    createSets( set1, set2 );

    Timer timer;
    for ( int i = 0; i < 1000; i++ )
    {
        intersectionSize = runIntersectionTest(set1, set2);
    }
    timer.Stop();

    cout << "Found the intersection of " << intersectionSize << " values (using unordered_map) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;

    timer.Reset();
    for ( int i = 0; i < 1000; i++ )
    {
        intersectionSize = runSetIntersection(set1,set2);
    }
    timer.Stop();

    cout << "Found the intersection of " << intersectionSize << " values (using set_intersection) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;

    timer.Reset();
    for ( int i = 0; i < 1000; i++ )
    {
        intersectionSize = runIntersectionTest2(set1,set2);
    }
    timer.Stop();

    cout << "Found the intersection of " << intersectionSize << " values (using unordered_set) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;

    getchar();

    return 0;
}
0
ответ дан 5 December 2019 в 04:37
поделиться

Обновление:

Я изменил код set_intersection, чтобы использовать векторы и сортировать их (вместо использования класса сортированного набора), и теперь это НАМНОГО быстрее:

Found the intersection of 319 values (using unordered_map) 1000 times, in 22187.5ms
Found the intersection of 315 values (using set_intersection) 1000 times, in 2401.62ms

Помните: больший набор создается отсортированным, поэтому в этом примере сортировка может не занять много времени.

Код C ++:

// MapPerformance.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <hash_map>
#include <vector>
#include <iostream>
#include <time.h>
#include <algorithm>
#include <set>

#include <boost\unordered\unordered_map.hpp>

#include "timer.h"

using namespace std;
using namespace stdext;
using namespace boost;

int runIntersectionTest(vector<int> set1, vector<int> set2)
{
    // hash_map<int,int> theMap;
    // map<int,int> theMap;
    unordered_map<int,int> theMap;

    // Now intersect the two sets by populating the map
    for ( vector<int>::iterator iterator = set1.begin(); iterator != set1.end(); iterator++ )
    {
        int value = *iterator;

        theMap[value] = 1;
    }

    int intersectionSize = 0;

    for ( vector<int>::iterator iterator = set2.begin(); iterator != set2.end(); iterator++ )
    {
        int value = *iterator;

        unordered_map<int,int>::iterator foundValue = theMap.find(value);

        if ( foundValue != theMap.end() )
        {
            theMap[value] = 2;

            intersectionSize++;
        }
    }

    return intersectionSize;

}

int runSetIntersection(vector<int> set1, vector<int> set2)
{   
    sort(set1.begin(),set1.end());
    sort(set2.begin(),set2.end());

    set<int> intersection;

    set_intersection(set1.begin(),set1.end(), set2.begin(), set2.end(), inserter(intersection, intersection.end()));

    return intersection.size(); 
}



int _tmain(int argc, _TCHAR* argv[])
{
    srand ( time(NULL) );

    vector<int> set1;
    vector<int> set2;

    set1.reserve(10000);
    set2.reserve(1000);

    // Create 100,000 values for set1
    for ( int i = 0; i < 100000; i++ )
    {
        int value = 1000000000 + i;
        set1.push_back(value);
    }

    // Create 1,000 values for set2
    for ( int i = 0; i < 1000; i++ )
    {
        int random = rand() % 200000 + 1;
        random *= 10;

        int value = 1000000000 + random;
        set2.push_back(value);
    }

    int intersectionSize = 0;


    Timer timer;
    for ( int i = 0; i < 1000; i++ )
    {
        intersectionSize = runIntersectionTest(set1, set2);
    }
    timer.Stop();

    cout << "Found the intersection of " << intersectionSize << " values (using unordered_map) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;

    timer.Reset();
    for ( int i = 0; i < 1000; i++ )
    {
        intersectionSize = runSetIntersection(set1,set2);
    }
    timer.Stop();

    cout << "Found the intersection of " << intersectionSize << " values (using set_intersection) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;

    getchar();

    return 0;
}
0
ответ дан 5 December 2019 в 04:37
поделиться

Хорошо, после большого количества отзывов я несколько раз обновлял исходный вопрос:

  • Каждый тест теперь запускается 1000 раз
  • В коде C # теперь используется таймер с более высоким разрешением
  • Теперь структуры данных заполняются ПЕРЕД тестами.

В результате C # все еще примерно в 5 раз быстрее, чем C ++.

Спасибо всем за ваши идеи / предложения.

0
ответ дан 5 December 2019 в 04:37
поделиться

Включены ли флаги оптимизации C ++?

0
ответ дан 5 December 2019 в 04:37
поделиться

Я знаю, что ваше решение работает хорошо, но пробовали ли вы использовать реализации STL:

Возможно, он уже оптимизирован для вашей платформы, поэтому я бы попробовал

0
ответ дан 5 December 2019 в 04:37
поделиться

Возможно, стоит также взглянуть на контейнер boost Disjoint Set , который специально оптимизирован для определенных видов операций с большими наборами.

Он работает, рассматривая группу множеств как объединение нескольких непересекающихся множеств, что позволяет очень дешево строить другие множества, такие как пересечения или объединения, после того, как начальный набор непересекающихся множеств построен. Если вы ожидаете выполнения множества операций над наборами, которые не сильно меняются, вы, вероятно, можете ожидать, что это будет очень быстро. Если, с другой стороны, вы будете использовать каждый набор один раз и выбросить его, вероятно, это не поможет.

В любом случае, вы окажете себе услугу, если хотя бы поэкспериментируете с этим, чтобы увидеть, не повлияет ли это на ваш конкретный случай.

2
ответ дан 5 December 2019 в 04:37
поделиться

Я бы изменил C ++ "runIntersectionTest" так, чтобы он принимал константные ссылки на контейнеры, а не создавал их для копирования при каждом вызове. (Код C # будет использовать ссылки)

2
ответ дан 5 December 2019 в 04:37
поделиться

Используйте это ...

vector<int> set1(10000);
vector<int> set2(1000);

... для получения векторов ненулевого начального размера. Тогда не используйте push_back, а просто обновите значения напрямую.

4
ответ дан 5 December 2019 в 04:37
поделиться

Поскольку вы используете Visual Studio, вам следует проверить, установлено ли у вас _SECURE_SCL значение 1 (обычно, если вы не установили его явно, это будет 1). Если он установлен, весь STL-код будет проверяться по диапазону, даже в релиз-билдах. Обычно замедление кода на 10-15%.

Похоже, Microsoft не знала, что, например, std :: vector уже имеет интерфейс, если вы хотите, чтобы проверка диапазона была: std :: vector :: at ()!

(Извините, пришлось отключить мой сундук)

В любом случае основная неэффективность заключается в том, что вы копируете контейнеры вместо того, чтобы передавать их по значению. Используйте ссылки (пытайтесь) сравнить яблоки и яблоки вместо яблок и бананов.

1
ответ дан 5 December 2019 в 04:37
поделиться

Одна проблема, которую я сразу вижу, заключается в том, что вы передаете наборы в C ++ по значению, а не по константной ссылке. Таким образом, вы копируете их каждый раз, когда передаете!

Кроме того, я бы не стал использовать набор для цели set_intersection . Я бы использовал что-то вроде

int runSetIntersection(const set<int>& set1, const set<int>& set2)
{   
    vector<int> intersection;
    intersection.reserve(10000) // or whatever the max is

    set_intersection(set1.begin(),set1.end(), set2.begin(), set2.end(), back_inserter(intersection));

    return intersection.size(); 
}

Этот код, однако, по-прежнему выделяется внутри функции. Еще быстрее было бы

int runSetIntersection(const set<int>& set1, const set<int>& set2, vector<int>& scratch)
{   
    scratch.reserve(10000) // or whatever the max is

    set_intersection(set1.begin(),set1.end(), set2.begin(), set2.end(), back_inserter(scratch));

    return scratch.size(); 
}

А затем выделить царапины перед запуском таймера.

Хотя, если вы просто ищете размер, рукописный цикл for в сочетании с set :: find может дать еще лучшие результаты .

9
ответ дан 5 December 2019 в 04:37
поделиться

Кстати, если у вас большие отсортированные наборы, std::set_intersection не самый быстрый алгоритм. std::set_intersection выполняет до 2*(m+n)-1 сравнений, но алгоритмы вроде алгоритма Baeza-Yates могут быть быстрее. Для малых m Баеза-Йейтс равен O(m * log(n)), а для n = alpha * m — O(n). Основная идея состоит в том, чтобы сделать своего рода двухсторонний бинарный поиск.

http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.91.7899&rep=rep1&type=pdf

Экспериментальный анализ алгоритма быстрого пересечения отсортированных последовательностей Рикардо Баэса-Йейтс и Алехандро Сэлинджер

ИЛИ

Р. Баеза-Йейтс. Алгоритм быстрого пересечения множеств для отсортированных последовательностей. В Материалы 15-го ежегодного симпозиума по комбинаторному сопоставлению образов (CPM 2004), Springer LNCS 3109, стр. 400-408, Стамбул, Турция, июль 2004 г.

Ниже приведены объяснение и реализация Эрика Фрея, где он показывает значительно более быстрые результаты, чем std::set_intersection с бинарным зондом. Я еще не пробовал его код.
http://fawx.com/

  1. Выберите срединный элемент A в меньший набор.
  2. Найдите его элемент позиции вставки, B, в больший набор.
  3. Если A и B равны, добавьте элемент к результат.
  4. Повторите шаги 1–4 для непустых подмножеств по обе стороны от элементов A и B.

;



/* * baeza_intersect */ template< template class Probe, class RandomAccessIterator, class OutputIterator> void baeza_intersect(RandomAccessIterator begin1, RandomAccessIterator end1, RandomAccessIterator begin2, RandomAccessIterator end2, OutputIterator out) { RandomAccessIterator probe1, probe2;

if ( (end1 - begin1) < ( end2 - begin2 ) ) { if ( begin1 == end1 ) return; probe1 = begin1 + ( ( end1 - begin1 ) >> 1 ); probe2 = lower_bound< Probe >( begin2, end2, *probe1 ); baeza_intersect< Probe >(begin1, probe1, begin2, probe2, out); // intersect left if (! (probe2 == end2 || *probe1 < *probe2 )) *out++ = *probe2++; baeza_intersect< Probe >(++probe1, end1, probe2, end2, out); // intersect right } else { if ( begin2 == end2 ) return; probe2 = begin2 + ( ( end2 - begin2 ) >> 1 ); probe1 = lower_bound< Probe >( begin1, end1, *probe2 ); baeza_intersect< Probe >(begin1, probe1, begin2, probe2, out); // intersect left if (! (probe1 == end1 || *probe2 < *probe1 )) *out++ = *probe1++; baeza_intersect< Probe >(probe1, end1, ++probe2, end2, out); // intersect right } }

/* * with a comparator */ template< template class Probe, class RandomAccessIterator, class OutputIterator, class Comparator > void baeza_intersect(RandomAccessIterator begin1, RandomAccessIterator end1, RandomAccessIterator begin2, RandomAccessIterator end2, OutputIterator out, Comparator cmp) { RandomAccessIterator probe1, probe2;

  if ( (end1 - begin1) < ( end2 - begin2 ) )
  {
    if ( begin1 == end1 )
      return;
    probe1 = begin1 + ( ( end1 - begin1 ) >> 1 );
    probe2 = lower_bound< Probe >( begin2, end2, *probe1, cmp );
    baeza_intersect< Probe >(begin1, probe1, begin2, probe2, out, cmp); // intersect left
    if (! (probe2 == end2 || cmp( *probe1, *probe2 ) ))
      *out++ = *probe2++;
    baeza_intersect< Probe >(++probe1, end1, probe2, end2, out, cmp); // intersect right
  }
  else
  {
    if ( begin2 == end2 )
      return;
    probe2 = begin2 + ( ( end2 - begin2 ) >> 1 );
    probe1 = lower_bound< Probe >( begin1, end1, *probe2, cmp );
    baeza_intersect< Probe >(begin1, probe1, begin2, probe2, out, cmp); // intersect left
    if (! (probe1 == end1 || cmp( *probe2, *probe1 ) ))
      *out++ = *probe1++;
    baeza_intersect< Probe >(probe1, end1, ++probe2, end2, out, cmp); // intersect right
  }
}

// probe.hpp

/** * binary probe: pick the next element by choosing the halfway point between low and high */ template< class RandomAccessIterator, class T > struct binary_probe { RandomAccessIterator operator()(RandomAccessIterator begin, RandomAccessIterator end, const T & value) { return begin + ( (end - begin) >> 1); } };

/** * lower_bound: like stl's lower_bound but with different kinds of probing * note the appearance of the rare template parameter template! */ template< template class Probe, class RandomAccessIterator, class T > RandomAccessIterator lower_bound(RandomAccessIterator begin, RandomAccessIterator end, const T & value) { RandomAccessIterator pit; Probe< RandomAccessIterator, T > pfunc; // probe-functor (wants to get func'd up)

while ( begin < end ) { pit = pfunc(begin, end, value); if ( *pit < value ) begin = pit + 1; else end = pit; } return begin; }

/* * this time with a comparator! */ template< template class Probe, class RandomAccessIterator, class T, class Comparator > RandomAccessIterator lower_bound(RandomAccessIterator begin, RandomAccessIterator end, const T & value, Comparator cmp) { RandomAccessIterator pit; Probe< RandomAccessIterator, T > pfunc;

while ( begin < end ) { pit = pfunc(begin, end, value); if ( cmp(*pit, value) ) begin = pit + 1; else end = pit; } return begin; }

2
ответ дан 5 December 2019 в 04:37
поделиться
Другие вопросы по тегам:

Похожие вопросы: