C++: функция оптимальной сложности

В интервью мне задали вопрос:

У нас есть пакеты, как показано ниже (пакеты как компоненты программной системы) -

pkg1 -> pkg2, pkg3, pkg4

pkg2 -> pkg5, pkg6

pkg7 -> pkg8, pkg9

pkg9 -> pkg10, pkg11

Где, Pkg1 зависит от pkg2, 3, 4.

pkg2 зависит от pkg5, 6 и так далее.

Эти пакеты необходимо собрать (скомпилировать). Независимые пакеты могут быть построены параллельно для максимально быстрой компиляции.

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

Вывод (на stdout) - Независимые пакеты - pkg3, pkg4, pkg5, pkg6, pkg8, pkg10, pkg11 Зависимые пакеты - pkg1, pkg2, pkg7, pkg9


В своем решении я использовал std :: map для входных пакетов и использовал std :: set и std :: vector для зависимых и независимых пакетов соответственно.

Временная сложность, которую я смог достичь, составила O (mn), где m - количество зависимых пакетов, а n - количество независимых пакетов. Можем ли мы добиться большего? Как? Любая помощь / руководство приветствуются.

Заранее спасибо.

Изменить 1: Подумал, свою реализацию приведу здесь -

#include <iostream>
void filterPackages(const std::map<int, std::vector<int> > &packages)
{
    std::vector<int> potential_independent_packages;
    std::set<int> dependent_packages;

    for(auto iter : packages)
    {
        dependent_packages.insert(iter.first);
        for(auto iter1 : iter.second)
        {
            potential_independent_packages.push_back(iter1);
        }
    }

    std::cout<<"Independent package - ";
    for( auto iter2 : potential_independent_packages)
    {
        if (dependent_packages.found(iter2) == std::set::end)
        {
            std::cout<<iter2;
        }
    }

    std::cout<<std::endl<<"Dependent packages - ";
    for( auto iter3 : dependent_packages)
    {
        std::cout<<iter3;
    }
}

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

πάντα ῥεῖ 19.12.2018 20:00

Постановка проблемы очень расплывчата. Какая ограничивающая переменная следует оптимизировать? Общее количество пакетов? Только пакеты без каких-либо зависимостей считаются independent? Появляются ли пакеты без зависимостей в ключах входной карты или их имена / номера должны быть собраны из зависимостей других пакетов? Нужно ли сортировать выходные данные по имени / номеру пакета? Могу ли я вообще выбрать любую структуру ввода? Я мог бы просто потребовать, чтобы ввод был контейнером, подобным map, который может перечислять все значения во времени O(n) вместе с тем, отображаются ли они как ключевые или нет.

user10605163 19.12.2018 20:05

Чтобы ответить на действительные вопросы пользователя10605163 - Общее количество пакетов? - Предполагается, что находится в сотнях. Только пакеты без каких-либо зависимостей должны считаться независимыми? - Да. Отображаются ли пакеты без зависимостей в ключах входной карты или их имена / номера должны быть собраны из зависимостей других пакетов? Да, пакеты без зависимостей нужно собирать из зависимостей. Суть также в выборе оптимальной структуры входных данных.

aj_test 19.12.2018 21:32

Нужно ли сортировать выходные данные по имени / номеру пакета? Нет Могу ли я вообще выбрать любую структуру ввода? Я мог бы просто потребовать, чтобы ввод был контейнером, похожим на карту, который может перечислять все значения за O (n) раз вместе с тем, отображаются ли они как ключевые или нет. -Да, это ваша функция, поэтому вы можете выбрать и опубликовать подпись. Я тоже выбрал std :: map.

aj_test 19.12.2018 21:37

Ваш код на самом деле является O(n*m*ln m) наихудшим случаем, где n - количество пакетов Всего и m - количество зависимых пакетов, если все зависимые пакеты зависят от всех других пакетов. Тогда potential_independent_packages будет иметь размер n*m, и вы собираетесь проверить dependent_packages для каждого из них на коэффициент ln(m). Вы также потенциально можете печатать пакеты несколько раз.

user10605163 19.12.2018 22:27

По-прежнему вопросы: вы собираетесь использовать среднюю или наихудшую временную сложность? Гарантируется ли, что номера пакетов будут последовательными (т. Е. Наибольший номер пакета равен общему количеству пакетов минус один)? Правильно ли я понял, что все входные контейнеры из стандартной библиотеки в порядке? Потому что снова я мог бы просто использовать контейнер ввода, в котором есть функция O(r), возвращающая список независимых пакетов, где r - количество таких, и это, очевидно, было бы оптимальным.

user10605163 19.12.2018 22:47
std::set_difference может помочь.
Jarod42 20.12.2018 01:18
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
72
1

Ответы 1

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

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

(Что такое «глубина зависимости»? Думайте о зависимости как о направленном дереве, тогда глубина элемента - это самый длинный путь от элемента до листьев, от которых он зависит. Формально, если у пакета нет зависимостей, то его глубина равна нулю, а для других - Depth [package] = max (Depth [depends_of_the_package]) + 1. Обратите внимание, что пакеты одинаковой глубины обязательно независимы друг от друга)

Я бы использовал следующий код

class PackageOptimizer
{
    public:
    struct PackageDependency
    {  
        size_t Count() const
        {
            return m_end - m_begin;
        }
        size_t m_begin;
        size_t m_end;
    };

    vector<size_t> FindLeaves()
    {
        vector<size_t> leaves;
        for(size_t i = 0; i<m_deps.size(); i++)
        {
            if (m_deps[i].m_begin == m_deps[i].m_end)
                leaves.push_back(i);
        }

        return leaves;
    }

    vector<int> ComputeDepthMap()
    {
        vector<int> Depth(m_deps.size());
        vector<int> Count(m_deps.size(),0);
        vector<size_t> currSet = FindLeaves();
        vector<size_t> nextSet;

        int currDepth = 0;
        while(!currSet.empty())
        {
           for(size_t elem : currSet)
           {
               Depth[elem] = currDepth;

               for( size_t  upPackage = m_depsInv[elem].m_begin;
                            upPackage < m_depsInv[elem].m_end; 
                            upPackage++)
                {
                    Count[upPackage]++;

                    if (Count[upPackage] == m_deps[upPackage].Count())
                    {
                        nextSet.push_back(upPackage);
                    }
                }
           } 
           swap(nextSet, currSet);
           currDepth++;
           nextSet.clear();
        }

        return Depth;
    }

    private:
    // package i is dependent on
    // m_indices[m_deps[i].m_begin], m_indices[m_deps[i].m_begin+1], ... m_indices[m_deps[i].m_end-1]
    vector<PackageDependency>   m_deps; 
    vector<size_t>              m_indices;

    // following packages are dependant on package i
    // m_indicesInv[m_depsInv[i].m_begin], m_indicesInv[m_depsInv[i].m_begin+1], ... m_indicesInv[m_deps[i].m_end-1]
    vector<PackageDependency>   m_depsInv; 
    vector<size_t>              m_indicesInv;
};

Просто не хватает методов для запуска m_deps, m_indices, m_depsInv, m_indicesInv, но это не сложно. С помощью вычислений Depth вы можете легко организовать пакеты в соответствии с порядком выполнения. Кроме того, вам необходимо убедиться, что в пакетах нет циклов зависимости, иначе этот код никогда не завершится.

С точки зрения производительности этот алгоритм занимает около O(n + m), где n - количество пакетов, а m - общее количество зависимостей. Примерно столько же времени займет создание m_deps, m_indices, m_depsInv, m_indicesInv.

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

Другие вопросы по теме