Ответы пользователя по тегу Структуры данных
  • Как вывести кратчайший путь в графе?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Вам надо в вашем алгоритме еще завести словарь, куда вы для каждой вершины будете складывать предыдущую в пути. Там, где вы соседнюю вершину кладете в очередь - там надо сарзу смотреть, посещенная она или нет и класть только если нет. Вот в этот момент вы знаете, что вы пришли в graph[city][i] из city.

    В конце циклом по этому массиву пердыдущих вершин пройдитесь и так получите путь в обратном порядке.
    Ответ написан
  • Как устроено окто дерево? Как происходит отсечение видимых грайней?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Вернуть все данные в узле. это же самое что дальше продолжать обходить все внутренние рекурсией или стеком узлы, и составить список. В чем профит то тогда? Как можно вернуть все данные по щелчку.


    Да, именно так, надо обойти все узлы дальне.
    Но при этом уже не надо проверять на видимость и код там попроще.

    А профит тут в том, что вы вот так вот обходите не все дерево. а только маленькую его часть.

    Окто дерево используется, чтобы отсечь те объекты, которые сзади или сбоку от камеры и точно не видны. Оно не помогает отсекать объекты, закрытые стеной. Тут, действительно, используется z-buffer.
    Ответ написан
    4 комментария
  • Что такое пул в программировании?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    pool - переводится не только как "бассейн", но и как "общий фонд". Пул в пограммировании - это набор однотипных ресурсов, которые переиспользуются по мере надобности. А при осовбождении - возвращаются в общую кучу. Так экономятся расходы на создание и уничтожение этих ресурсов. Бывают пулы почти чего угодно: потоков, буферов, каких-то объектов.

    Например, вместо того, чтобы запускать поток под каждое новое подключение в сервере, у вас есть 20 потоков, которые просто ничего не делают, а когда появляетсся подключение, какой-то из свободных потоков его обрабатывает. Если свободных потоков нет, возвращается ошибка, или соединение ждет в очереди. Когда оно обработано, поток возвращается в пул, вместо уничтожения. Это работает хорошо, потому что создание потоков - сложная и дорогая операция. Гораздо дешевле поток усыпить и засунуть какой-то дискриптор в какую-то структуру данных.
    Ответ написан
    Комментировать
  • Хэш-таблица без разрешения коллизий?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Нет. Ну, только если вы не будете заводить таблицу на 4 миллиарда с копейками элементов (2^32) и использовать тривиальную хеш-функцию.

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

    Вообще, теоретически, для фиксированного набора элементов можно подобрать хеш-функцию без коллизий. Она тогда называется идеальная хеш-функция. И тогда размер таблицы может быть очень маленьким - аж до количества этих элементов. Но вам надо подбирать новую хеш-функцию для каждого набора хранимых чисел.
    Ответ написан
    Комментировать
  • Как выбрать диапазоны значений по вхождению значения в диапазон?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Дерево интервалов. Оно же interval tree.

    Оно как раз позволяет быстро искать все отрезки, пересекающие данную точку, но изменять его очень тяжело. Хорошо, что вам это и не надо.
    Ответ написан
    Комментировать
  • Как реализовать приоритетную очередь с функциями extractMax и add, которая поддерживает одинаковые элементы?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    то в первом пункте мне не понятно как связаны индексы i и 2*i


    Если нумерация массива идет с 1, то два ребенка элемента i будут 2*i и 2*i+1. У вас же нумерация с 0, т.ч. у вас дети - 2*i+1 и 2*i+2.

    Первое условие достигается правильной расстановкой строгого и нестрогого неравнества в условиях в алгоритме.

    Похоже, проблема в том, что у вас нумерация с 0 и, если новый элемент оказывается итак максимальным, вы вернете индекс 0, вместо нужного 1. Если вы элемент вниз просеиваете, то у вас там +1 стоит в res.first, но изначально у вас-то там 0.

    Далее, вы вектор неправильно используете. Вы делаете reserve и потом работаете с пустым вектором, как-будто он фиксированного размера. Вам надо делать resize вместо reserve. Или еще лучше, вместо вашей переменной size вы используйте arr.size(). При изменении размера массива делайте pop_back() и push_back().
    Ответ написан
    8 комментариев
  • Как правильно сделать граф?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    Странная структура... Ну если вам так хочется, то "сделать поиск по кругу на N радиус от ячейки" делается обходом в ширину.
    Ответ написан
    Комментировать
  • Хештаблицы, можно ли мешать open addressing и chaining(решено)?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    Мешать можно. Особо память это не сократит, а реализацию сильно усложнит.
    Ответ написан
    Комментировать
  • Как присвоить динамическому массиву типа void* значение в Си?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    Если это произвольный тип, то какой у него размер? Какими значениями вы его заполнять-то пытаетесь?

    А так, скорее всего вам подойдет функция memset. Какими-нибудь нулями все заполнить - отлично можно. Хоть там int, хоть char, хоть float.
    Ответ написан
  • Для чего внутри связного списка нужен массив?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    В тексте задания уже есть ответ на ваш вопрос:
    To better utilize memory, the list should include an array T=8 of structures representing a block


    Для экономии памяти. Такие гибридные структуры обладают характеристиками средними между списком и массивом. В список можно быстро вставлять и удалять из него элементы. В массивах можно быстро искать k-ый элемент и он занимает меньше памяти (не нужны указатели).

    Ваша структура позволяет добавлять и удалять элементы быстрее чем в массиве, но при этом занимает меньше памяти чем просто список, хоть и добавление и удаление элементов там медленнее, чем в списке.
    Ответ написан
    Комментировать
  • Можно ли сбалансировать бинарное дерево поиска без использования поворотов дерева?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Можно. Декартово дерево можно реализовать через split/merge. Даже если у вас нет второго ключа, то можно релизовать это храня высоты деревьев или вообще случайно решать, стоит ли вставлять элемент сюда (где вызывать split) и какая из двух вершин станет корнем поддерева при merge.
    Ответ написан
  • Как в целом пишут представление (структуры данных) деревьев в коде? и в чём суть Деревьев?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    1) Можно представлять дерево всеми способами, как и граф. Но в этом случае о деревьях обычно и не говорят. Ну граф, ну без цикла может даже быть. Ну и что? Пользы никакой не несет. Вот если же дерево представлять подвешанным - с одним корнем, с детьми и родителем у каждой вершины, то уже есть куча полезных применений. В основном при реализации всяких хитрых структур данных.

    2) Дерево представляется в виде массива. Но не любое. А только полу-полное двоичное дерево (у каждой вершины до 2 детей. Если ребенок один, то левый. Все уровни, кроме последнего заполненны целиком, последний заполнен слева-направо).

    3) Дерево нельзя представить в виде списка в общем случае. Потому что список - линеен, а дерево нет. Можно детей в каждой вершине хранить списком. Ну или вырожденное дерево, где у каждой вершины есть только один ребенек.

    4) Нет.

    5) Нет.
    Ответ написан
  • Чем отличаются двунаправленные графы от графа с дугами в обе стороны?

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

    Почему вообще придумали использовать ориентированне графы? Потому что они полезны. Например, отношение "любовь" между людьми можно описать ориентированным графом, а вот неориентированным - нет (ибо есть безответная любовь).

    Иногда имеет смысл рассматривать неориентированный граф как оринетированный с парными дугами, если в процессе работы симетрия как-то нарушается. Например в алгоритмах поиска максимального потока на графе так и происходит. Поэтому он не работает с неориентированными графами - в них надо каждое ребро разбить на два туда и обратно.
    Ответ написан
    9 комментариев
  • Как найти бинарное дерево с заданной структурой в изначальном дереве?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Судя по комментариям, вам надо найти полное совподение поддерева: Так что поддерево вершины имеет ровно столько же точно также расставленных вершин, как и второе дерево.

    Умное и быстрое решение, к сожалению, будет сложно написать без std::unordered_map. Такое решение будет работать за O(n). Самостоятельно хеш таблицу писать будет сложно. Но если без хеш таблицы, то решение будет работать также медленно, как и тупое рекурсивное прикладывание, поэтому приведу тут все решение.

    Основная идея - надо классифицировать все вершины по форме их поддеревьев. Назначить каждой форме номер. Потом остается только найти вершины с тем же номером, что и корень второго дерева.

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

    Пустые вершины всегда имеют какой-то номер, допустим, 0.

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

    void Match(Tree *stack, Tree *needle) {
      std::unordered_map<std::pair<int, int>, int> shapes;
      int needle_shape = ClassifyShape(needle, shapes, -1);
      ClassifyShape(stack, shapes, needle_shape);
    }
    
    int ClassifyShape(Tree *tree, std::unordered_map<std::pair<int, int>, int> &shape, int find_shape) {
      if(!tree) return 0;
      int l = ClassifyShape(tree->left);
      int r = ClassifyShape(tree->right);
      auto pat = make_pair(l, r);
      auto it =  shapes.find(pat);
      int this_shape = -1;
      if (it == shapes.end()) {
        this_shape = shapes.size();
        shapes[pat] = this_shape;
      } else {
        this_shape =  it->second;
      }
      if (this_shape == find_shape) {
        std::cout << "Found match at node " << tree->value << std::endl;
      }
      return this_shape;
    }


    В нивном решении надо заменить unordered_map shapes на массив пар чисел (или массив структур или два массива чисел). Циклом в нем проищите текущую пару l, r, если не нашли, то допишите в конец.

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

    Но, возможно вашему преподу такое будет слишком заумно. Тогда вам нужна аккуратная рекурсивная функция сравнения:
    bool Compare(Tree* a, Tree *b) {
      if (a->left && !b->left) return false;
      if (a->right && !b->right) return false;
      return (!a->left || Compare(a->left, b->left)) && (!a->right || Compare(a->right, b->right));
    }


    Тут все просто. Если у корней разная конфигурация детей, то деревья точно не совпали. А дальше надо чтобы рекурсивно совпали поддеревья в левых и правых детях.

    А дальше вам надо рекурсивно пройтись по первому дереву и вызвать вот эту Compare для текущей вершины и корня второго дерева. Если совпало - то выводите текущую вершину.
    Ответ написан
  • PHP: как в односвязном списке удалить из середины элемент по его номеру?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Подсказка: вам надо найти не levelToCut-ый элемент, а элемент с номером levelToCut-1, ведь это у него надо перезаписать next на next->next
    Ответ написан
  • Как эффективно хранить неопределенное количество разных типов данных?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    Ну, без дырок совсем не получится. Во-первых, сами структуры могут быть с дырками из-за выравнивания. Потом, не любой тип можно положить куда угодно, опять же, из-за выравнивания. Далее, при удалении будут образовываться дырки, которые не факт что и можно заполнить (без медленного перелопачивания всей структуры).

    Евгений Шатунов правильно говорит, что это уже алгоритм аллокатора получается, а не структура данных. Ну а дальше, можно как-то выделять сырую память под ваши элементы, и в соседнем векторе указателей хранить где будет лежать i-ый элемент для быстрого доступа.
    Ответ написан
  • Какая структура данных лучше подойдет для случайного удаления из коллекции?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    Обычно во всяких кешах удаляют не случайный элемент, а какой-то особенный. Например, самый старый. Или как можно реже использующийся.

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

    При вставке - просто добавляйте в вектор ключ и вставляйте в дерево значение {значение, последний индекс в векторе}. При удалении по ключу - в известном месте вектора пропишите последний элемент и укоротите вектор на 1. Не забудьте обновить ссылку на вектор у того ключа, который с последнего места переехал.

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

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

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    Там не происходиит копирование данных. Просто среда выаолнения запоминает, что переменная a лежит вон там внутри "массива" кучи. При чтении оттуда данных все происходит за одну асскмблерную операцию, как если бы вы читали из int.

    Пишущим на си++ из-за концепции указателей это все должно быть понятнее. При выделении данных на куче просто какой-то указатель указывает куда-то в кучу и все.
    Ответ написан
    Комментировать
  • Чем отличаются структуры данных от колекции?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    Коллекция - это вид структур данных, используемый для хранения множества однотиптых элементов.

    Но не любая структура данных - коллекция. Например, граф, хранимый в виде списков смежности - не является коллецией. Еще примеры: cуффиксное дерево, дерево отрезков - тоже не являются коллекциями.
    Ответ написан
  • Как добавить элемент из файла в ветвящееся дерево?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Самый простой вариант - поддерживать вектор из указателей на самые правые узлы во всех доступных уровнях.

    Сначала читаете первую строку и создаете из нее корень дерева.

    А потом в цикле читайте строку и считайте, сколько там пробелов. Их должно быть не больше, чем количество доступных уровней (т.е. элементов в векторе).
    Добавляете текущую строку к узлу, который на уровне равном количеству пробелов (-1, потому что индексация с 0). Укорачиваете вектор до этого узла (все с уровенем ниже та вершина, к которой добавили ребенка - уже недоступно). Добавляете в вектор указатель на только что добавленную вершину.

    В вашем примере.

    Прочитали иван, сделали корень дерева. Вектор будет {"иван"} (указатель на вершину-корень дерева).

    Прочитали алексей. 1 проблел - все хорошо, в векторое 1 элемент, значит может быть до 1 пробела. Добавили "алексей" к вершине "иван". Вектор {"иван", "алексей"}

    Прочитали игорь. Вектор стал {"иван", "алексей", "игорь"}.

    Прочитали андрей. 1 пробел. Может быть до трех пробелов, ведь в векторе 3 элемента. Добавили "андрей" к узлу из вектора по индексу 0 (-1 к количеству пробелов). обрезали вектор до длины 1 и добавили туда новую вершину. Вектор стал {"иван", "андрей"}

    И т.д.

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