В интервью мне задали вопрос:
У нас есть пакеты, как показано ниже (пакеты как компоненты программной системы) -
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;
}
}
Постановка проблемы очень расплывчата. Какая ограничивающая переменная следует оптимизировать? Общее количество пакетов? Только пакеты без каких-либо зависимостей считаются independent? Появляются ли пакеты без зависимостей в ключах входной карты или их имена / номера должны быть собраны из зависимостей других пакетов? Нужно ли сортировать выходные данные по имени / номеру пакета? Могу ли я вообще выбрать любую структуру ввода? Я мог бы просто потребовать, чтобы ввод был контейнером, подобным map, который может перечислять все значения во времени O(n) вместе с тем, отображаются ли они как ключевые или нет.
Чтобы ответить на действительные вопросы пользователя10605163 - Общее количество пакетов? - Предполагается, что находится в сотнях. Только пакеты без каких-либо зависимостей должны считаться независимыми? - Да. Отображаются ли пакеты без зависимостей в ключах входной карты или их имена / номера должны быть собраны из зависимостей других пакетов? Да, пакеты без зависимостей нужно собирать из зависимостей. Суть также в выборе оптимальной структуры входных данных.
Нужно ли сортировать выходные данные по имени / номеру пакета? Нет Могу ли я вообще выбрать любую структуру ввода? Я мог бы просто потребовать, чтобы ввод был контейнером, похожим на карту, который может перечислять все значения за O (n) раз вместе с тем, отображаются ли они как ключевые или нет. -Да, это ваша функция, поэтому вы можете выбрать и опубликовать подпись. Я тоже выбрал std :: map.
Ваш код на самом деле является O(n*m*ln m) наихудшим случаем, где n - количество пакетов Всего и m - количество зависимых пакетов, если все зависимые пакеты зависят от всех других пакетов. Тогда potential_independent_packages будет иметь размер n*m, и вы собираетесь проверить dependent_packages для каждого из них на коэффициент ln(m). Вы также потенциально можете печатать пакеты несколько раз.
По-прежнему вопросы: вы собираетесь использовать среднюю или наихудшую временную сложность? Гарантируется ли, что номера пакетов будут последовательными (т. Е. Наибольший номер пакета равен общему количеству пакетов минус один)? Правильно ли я понял, что все входные контейнеры из стандартной библиотеки в порядке? Потому что снова я мог бы просто использовать контейнер ввода, в котором есть функция O(r), возвращающая список независимых пакетов, где r - количество таких, и это, очевидно, было бы оптимальным.





Это не было четко указано в описании, но я полагаю, вы имели в виду, что пакет не может быть скомпилирован, если некоторые из пакетов, от которых он зависит, еще не были скомпилированы.
При таком предположении существует довольно простой код, оптимизирующий порядок компиляции. Вычислить глубину зависимости для каждого пакета, а затем компилировать в параллельных пакетах одинаковой глубины, начиная с пакетов наименьшей глубины.
(Что такое «глубина зависимости»? Думайте о зависимости как о направленном дереве, тогда глубина элемента - это самый длинный путь от элемента до листьев, от которых он зависит. Формально, если у пакета нет зависимостей, то его глубина равна нулю, а для других - 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.
Хотя в реальной жизни разные пакеты требуют разного времени для компиляции. и у вас всегда есть ограничение на количество пакетов, которые вы можете эффективно компилировать одновременно.
Временная сложность операций в различных реализациях стандартных контейнеров C++ уже хорошо задокументированный, и вам лучше сначала обратиться к нему, прежде чем спрашивать здесь.