Алгоритм, чтобы сказать, если два массива имеют одинаковые члены - PullRequest
26 голосов
/ 29 октября 2008

Какой лучший алгоритм для сравнения двух массивов, чтобы увидеть, имеют ли они одинаковые члены?

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

compare(
    [a, b, c, d],
    [b, a, d, c]
) ==> true

compare(
    [a, b, e],
    [a, b, c]
) ==> false

compare(
    [a, b, c],
    [a, b]
) ==> false

Ответы [ 16 ]

17 голосов
/ 29 октября 2008

Очевидные ответы будут:

  1. Сортировка обоих списков, затем проверка каждого элемент, чтобы увидеть, идентичны ли они
  2. Добавить элементы из одного массива в hashtable, затем переберите другой массив, проверяя, что каждый элемент в хеше
  3. алгоритм итеративного поиска Никфа

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

7 голосов
/ 29 октября 2008

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

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

Другой метод, который будет работать при наличии дубликатов, состоит в сортировке обоих массивов и выполнении линейного сравнения. Это должно быть O (N * log (N)).

5 голосов
/ 29 октября 2008

Предполагая, что вы не хотите нарушать исходные массивы, а пространство - это вопрос, еще одно решение O (n.log (n)), которое использует меньше места, чем сортировка обоих массивов:

  1. Возвращает FALSE, если массивы различаются по размеру
  2. Сортировка первого массива - время O (n.log (n)), требуется дополнительное место для размера одного массива
  3. Для каждого элемента во втором массиве проверьте, находится ли он в отсортированной копии первый массив с использованием бинарного поиска - O (n.log (n)) время

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

[Добавлено после рассмотрения решений, предлагающих поиск в словаре / наборе / хэше:]

На практике я бы использовал хеш. Несколько человек заявили о поведении O (1) для хэшей, что привело их к выводу, что решение на основе хэшей - O (N). Типичные операции вставки / поиска могут быть близки к O (1), а некоторые схемы хеширования гарантируют поиск в худшем случае O (1), но вставка в худшем случае - при построении хеша - не O (1). При любой конкретной структуре данных хеширования будет некоторый набор входных данных, которые будут вызывать патологическое поведение. Я подозреваю, что существуют структуры хеширования данных с объединенным наихудшим случаем для [insert-N-elements затем lookup-N-elements] O (N.log (N)) времени и пространства O (N).

4 голосов
/ 17 декабря 2008

Вы можете использовать сигнатуру (коммутативная операция над элементами массива) для дальнейшей оптимизации этого в случае, когда массив обычно отличается, сохраняя o(n log n) или выделение памяти. Сигнатура может иметь форму фильтра (-ов) Блума или даже простой коммутативной операции, такой как сложение или xor.

Простой пример (при условии, что длина стороны подписи и gethashcode - хороший идентификатор объекта; если объекты, скажем, int, то их значение является лучшим идентификатором, а некоторые сигнатуры будут длиннее, чем long)

public bool MatchArrays(object[] array1, object[] array2)
{
   if (array1.length != array2.length)
      return false;
   long signature1 = 0;
   long signature2 = 0;
   for (i=0;i<array1.length;i++) {
       signature1=CommutativeOperation(signature1,array1[i].getHashCode());
       signature2=CommutativeOperation(signature2,array2[i].getHashCode());
   }

   if (signature1 != signature2) 
       return false;

   return MatchArraysTheLongWay(array1, array2);
}

где (с использованием операции сложения; при необходимости используйте другую коммутативную операцию, например, фильтры Блума)

public long CommutativeOperation(long oldValue, long newElement) {
    return oldValue + newElement;
}
3 голосов
/ 17 декабря 2008

Это можно сделать разными способами:

1 - Грубая сила: для каждого элемента в массиве 1 проверьте, что элемент существует в массиве 2. Обратите внимание, что для этого потребуется указать позицию / индекс, чтобы дубликаты могли обрабатываться правильно. Это требует O (n ^ 2) с очень сложным кодом, даже не думайте об этом ...

2 - Сортируйте оба списка, затем проверьте каждый элемент, чтобы увидеть, идентичны ли они. O (n log n) для сортировки и O (n) для проверки, так что в основном O (n log n), сортировка может быть выполнена на месте, если испортить массивы не проблема, если нет необходимости иметь 2n памяти размера скопировать отсортированный список.

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

4 - Best of Best (Среди вышеперечисленных): вычесть или взять разность каждого элемента в одном и том же индексе двух массивов и, наконец, суммировать вычитаемые значения. Например, A1 = {1,2,3}, A2 = {3,1,2}, Diff = {- 2,1,1} теперь суммируют Diff = 0, что означает, что они имеют одинаковый набор целых чисел. Этот подход требует O (n) без дополнительной памяти. Код C # будет выглядеть следующим образом:

    public static bool ArrayEqual(int[] list1, int[] list2)
    {
        if (list1 == null || list2 == null)
        {
            throw new Exception("Invalid input");
        }

        if (list1.Length != list2.Length)
        {
            return false;
        }

        int diff = 0;

        for (int i = 0; i < list1.Length; i++)
        {
            diff += list1[i] - list2[i];
        }

        return (diff == 0);
    }

4 вообще не работает, это худшее

2 голосов
/ 26 августа 2014

Если элементы массива заданы как разные, то XOR (побитовое XOR) всех элементов обоих массивов, если ответ ноль, то оба массива имеют одинаковый набор чисел. Временная сложность O (n)

1 голос
/ 11 ноября 2010

Псевдокод:

A:array
B:array
C:hashtable

if A.length != B.length then return false;

foreach objA in A
{
H = objA;
if H is not found in C.Keys then
C.add(H as key,1 as initial value);
else
C.Val[H as key]++;
}

foreach objB in B
{
H = objB;
if H is not found in C.Keys then
return false;
else
C.Val[H as key]--;
}

if(C contains non-zero value)
return false;
else
return true;
1 голос
/ 29 октября 2008

Идем здесь в глубокие воды, но:

Сортированные списки сортировка может быть O(nlogn), как указано. просто чтобы уточнить, не имеет значения, что существует два списка, потому что: O(2*nlogn) == O(nlogn), тогда сравнение каждого элемента - это еще один O (n), поэтому сортировка обоих, а затем сравнение каждого элемента - это O (n) + O (nlogn), что это: O(nlogn)

Hash-таблицы: Преобразование первого списка в хеш-таблицу - это O (n) для чтения + стоимость хранения в хеш-таблице, которую, я думаю, можно оценить как O (n), дает O (n). Затем вам нужно будет проверить наличие каждого элемента в другом списке в созданной хеш-таблице, что является (по крайней мере?) O (n) (при условии, что проверка существования элемента хеш-таблицы является постоянной). В итоге мы получаем O(n) для чека.

Интерфейс Java List определяет равные , поскольку каждый соответствующий элемент равен.

Интересно, что определение интерфейса Java Collection почти не поощряет реализацию функции equals () .

Наконец, интерфейс Java Set для документации реализует именно это поведение. Реализация должна быть очень эффективной, но в документации не упоминается производительность. (Не удалось найти ссылку на источник, вероятно, она строго лицензирована. Загрузите и посмотрите на нее самостоятельно. Она поставляется с JDK.) Глядя на источник, HashSet (который часто используется для реализации Set) делегирует равные () реализация к AbstractSet, которая использует функцию containsAll () AbstractCollection, снова используя функцию contains () из hashSet. Таким образом, HashSet.equals () работает в O (n), как и ожидалось. (циклически просматривая все элементы и просматривая их в постоянном времени в хэш-таблице.)

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

1 голос
/ 29 октября 2008

Какое «лучшее» решение, очевидно, зависит от того, какие у вас есть ограничения. Если это небольшой набор данных, сортировка, хэширование или сравнение методом грубой силы (например, сообщение nickf ) будут очень похожи. Поскольку вы знаете, что имеете дело с целочисленными значениями, вы можете получить время сортировки O (n) (например, сортировку по основанию), и в хэш-таблице также будет использоваться время O (n). Как всегда, у каждого подхода есть свои недостатки: сортировка потребует от вас дублирования данных или деструктивной сортировки массива (без потери текущего порядка), если вы хотите сэкономить место. Очевидно, что для создания хеш-таблицы у хеш-таблицы будут накладные расходы памяти. Если вы используете метод nickf, вы можете сделать это с минимальными накладными расходами памяти, но вам придется иметь дело со средой выполнения O (n 2 ). Вы можете выбрать наиболее подходящий для ваших целей.

1 голос
/ 29 октября 2008

Если вы сначала отсортируете оба массива, вы получите O (N log (N)).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...