2010-06-28 6 views
2

У меня есть два объекта vector<MyType*> под названием A и B. Класс MyType имеет поле ID, и я хочу получить MyType*, которые находятся в A, но не в B. Я работаю над приложением для анализа изображений, и я надеялся найти быстрое/оптимизированное решение.C++ Разница между двумя векторами <MyType*> A и B

С наилучшими пожеланиями, Поллукс

+0

Предположительно, потому что вы спрашиваете, они не отсортированы по ID? – Cascabel

+0

Насколько они велики? Насколько важен «вектор»? Можете ли вы изменить на 'set'? Более подробная информация необходима для обеспечения хорошего ответа. – Stephen

+0

Я изменил свое первоначальное сообщение, чтобы включить решение, которое еще быстрее, и может работать с несортированными векторами. Однако это несколько сложно. – stinky472

ответ

2

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

struct CompareId 
{ 
    bool operator()(const MyType* a, const MyType* b) const 
    { 
     return a>ID < b->ID; 
    } 
}; 
... 
sort(A.begin(), A.end(), CompareId()); 
sort(B.begin(), B.end(), CompareId()); 

vector<MyType*> C; 
set_difference(A.begin(), A.end(), B.begin(), B.end(), back_inserter(C)); 

Другим решением является использование упорядоченного контейнера, как станд :: набора с CompareId используется для шаблона StrictWeakOrdering аргумента. Я думаю, что было бы лучше, если бы вам нужно было применить множество заданных операций. У этого есть свои собственные накладные расходы (будучи деревом), но если вы действительно обнаружите, что это проблема эффективности, вы можете реализовать быстрый распределитель памяти, чтобы быстро вставлять и удалять элементы (обратите внимание: сделайте это только в том случае, если вы профилируете и определяете это как узкое место).

Предупреждение: переход на несколько сложную территорию.

Есть еще одно решение, которое вы можете рассмотреть, которое может быть очень быстрым, если это применимо, и вам никогда не придется беспокоиться о сортировке данных. В принципе, создайте любую группу объектов MyType, которые имеют один и тот же ID-хранилище общий счетчик (ex: указатель на unsigned int).

Для этого потребуется создать карту идентификаторов для счетчиков и потребовать выборку счетчика с карты каждый раз, когда объект MyType создается на основе его идентификатора. Поскольку у вас есть объекты MyType с дублирующимися идентификаторами, вам не нужно вставлять их на карту так часто, как вы создаете объекты MyType (большинство из них, вероятно, могут просто получить существующий счетчик).

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

static unsigned int counter = 0; 
unsigned int traversal_counter() 
{ 
    // make this atomic for multithreaded applications and 
    // needs to be modified to set all existing ID-associated 
    // counters to 0 on overflow (see below) 
    return ++counter; 
} 

Теперь вернемся к тому, где у вас есть векторы A и B, хранящие MyType *. Чтобы получить элементы из A, которые не находятся в B, мы сначала вызываем traversal_counter(). Предполагая, что это первый раз, когда мы его назовем, это даст нам обходное значение 1.

Теперь перебираем каждый объект MyType * в B и устанавливаем общий счетчик для каждого объекта от 0 до значения обхода, 1.

Теперь перебирать все MyType * объект А. те, которые имеют значение счетчика, которое не соответствует текущему значению обхода (1) являются элементами а, которые не содержится в B.

что происходит когда вы переполняете счетчик обхода? В этом случае мы перебираем все счетчики, хранящиеся в ID-карте, и устанавливаем их обратно на ноль вместе с самим счетчиком обхода. Это нужно будет только раз в 4 миллиарда обходов, если это 32-битный беззнаковый int.

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

2

Сортировка оба вектора (std::sort) в соответствии с ID, а затем использовать std::set_difference. Вам нужно будет определить пользовательский компаратор перейти на оба этих алгоритмов, например

struct comp 
{ 
    bool operator()(MyType * lhs, MyType * rhs) const 
    { 
     return lhs->id < rhs->id; 
    } 
}; 
+0

Привет Авакар, спасибо за ваш ответ! Можете ли вы показать пример того, как использовать set_difference таким образом, чтобы получить новый вектор с разницей? – pollux

+0

Может ли OP просто определить 'operator <' для 'MyType'? – Bill

+0

@ Не заполняйте MyType *. – stinky472

1

Сначала рассмотрите проблему. Вы хотите «все в A не в B». Это означает, что вам придется посетить «все в А». Вам также нужно будет посетить все в B, чтобы знать, что есть и не находится в B. Таким образом, это предполагает, что должно быть решение O(n) + O(m) или взять на себя смелость преодолеть разницу между n и m, O(2n).

Давайте рассмотрим подход std::set_difference. Каждый вид - O(n log n), а set_difference - O(n). Таким образом, метод sort-sort-set_difference равен O(n + 2n log n). Назовем это O(4n).

Другим подходом было бы сначала разместить элементы B в наборе (или карте). Итерация по B для создания набора - O(n) плюс вставка O(log n) каждого элемента, за которым следует итерация по A O (n), с поиском для каждого элемента A (log n), дает общее количество: O(2n log n). Назовем это O(3n), что немного лучше.

Наконец, используя unordered_set (или unordered_map), и предполагая, что мы получаем средний случай O(1) вставки и O(1) поиска, мы имеем подход, который O(2n). Ага!

Настоящая победа здесь в том, что unordered_set (или карта) , вероятно, - самый естественный выбор для представления ваших данных в первую очередь, то есть правильный дизайн дает оптимизированную реализацию. Это не всегда происходит, но приятно, когда это происходит!

+0

«[...] подход - это O (n + 2n log n). Назовем это O (4n)». Вы должны прочитать это: http://en.wikipedia.org/wiki/Big_O_notation – avakar

+0

Приятный анализ алгоритмических сложностей и очень приятный, что вы указали неупорядоченный_set, но неупорядоченный векторный случай будет O (N) * O (M), а не O (N) + O (M), или O (N^2). Нам приходится многократно искать B для каждого элемента из A. Также O (N * logN) значительно отличается от O (2N). Если N равно 1000, 2N будет равно 2000, а N * logN будет ~ 10000. Я не думаю, что мы можем это упростить. – stinky472

0

Если B предвещает A, то при заполнении A вы можете бронировать в векторе C.