Почему вектор быстрее, чем unordered_map? - PullRequest
6 голосов
/ 01 апреля 2019

Я решаю проблему с LeetCode, но никто еще не смог объяснить мою проблему.

Проблема как таковая:

Учитывая произвольную строку примечания с требованием выкупа и другую строку, содержащую буквы из всех журналов, напишите функцию, которая будет возвращать true, если примечание с требованием выкупа может быть построено из журналов; в противном случае он вернет false.

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

Примечание: Вы можете предположить, что обе строки содержат только строчные буквы.

canConstruct("a", "b") -> false
canConstruct("aa", "ab") -> false
canConstruct("aa", "aab") -> true

Мой код (который занимает 32 мс):

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        if(ransomNote.size() > magazine.size()) return false;
        unordered_map<char, int> m;

        for(int i = 0; i < magazine.size(); i++)
            m[magazine[i]]++;

        for(int i = 0; i < ransomNote.size(); i++)
        {
            if(m[ransomNote[i]] <= 0) return false;
            m[ransomNote[i]]--;
        }
        return true;
    }
};

Код (который я не знаю, почему быстрее - занимает 19 мс):

bool canConstruct(string ransomNote, string magazine) {
        int lettersLeft = ransomNote.size(); // Remaining # of letters to be found in magazine
        int arr[26] = {0};
        for (int j = 0; j < ransomNote.size(); j++) {
            arr[ransomNote[j] - 'a']++; // letter - 'a' gives a value of 0 - 25 for each lower case letter a-z
        }

        int i = 0;
        while (i < magazine.size() && lettersLeft > 0) {
            if (arr[magazine[i] - 'a'] > 0) {
                arr[magazine[i] - 'a']--;
                lettersLeft--;
            }
            i++;
        }
        if (lettersLeft == 0) {
            return true;
        } else {
            return false;
        }
    }

Оба они имеют одинаковую сложность и используют одну и ту же структуру для решения проблемы, но я не понимаю, почему один занимает почти вдвое больше времени, чем другой. Время запроса вектора - O (1), но то же самое для unordered_map. Та же история с добавлением записи / ключа к любому из них.

Пожалуйста, кто-нибудь может объяснить, почему время выполнения так сильно меняется?

Ответы [ 2 ]

6 голосов
/ 01 апреля 2019

Первое, что нужно отметить, это то, что хотя среднее время запроса unordered_map постоянно, наихудший случай не O(1).Как вы можете видеть здесь , он на самом деле увеличивается до порядка O(N), N, обозначающего размер контейнера.

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

По сути, с точки зрения сложности производительность vector в худшем случае более эффективначем у unordered_map.Кроме того, большинство аппаратных систем предлагают такие функции, как кеширование, которые дают использование vector еще больше.(т.е. меньшие постоянные факторы в O(1) операциях)

3 голосов
/ 01 апреля 2019

Ваш второй подход использует простой массив C, где доступ к элементу является простым разыменованием указателя. Но это не так с unordered_map. Следует отметить два момента:

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

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

Преимущество unordered_map вы увидите только тогда, когда у вас будет большое количество ключей (здесь вместо фиксированных 26).

...