Рекурсивный союз: как он работает на самом деле?
В настоящее время я беру курс Scala на Coursera в свободное от работы время, пытаясь наконец попробовать функциональное программирование. В настоящее время я работаю над заданием, где мы должны "рассчитать" объединение двух наборов, содержащих некоторый объект. Я намеренно упускаю детали, поскольку это не очень важно для того, что я пытаюсь задать здесь. Однако имеет значение то, что множества определяются как двоичные деревья, причем каждый node содержит элемент и два поддеревья.
Это так; пример union
в лекции выглядит следующим образом:
def union(other:BTSet) :BTSet = ((left union right) union other) incl element
Question1: Совершенно откровенно, даже прочитав соответствующие FAQ и другие темы форума, я до сих пор не понимаю, как и почему эта функция работает. В реализации объединения абсолютно нет "действия", кроме добавления (вызов incl
) элемента в голове node, он просто называет себя снова и снова. Я был бы очень признателен за некоторые объяснения...
Question2: В форуме курса есть много сообщений, в которых говорится, что это решение неэффективно и что оно недостаточно. Видя, как я не понимаю, как это работает, я не совсем понимаю, почему это недостаточно.
ПОЖАЛУЙСТА, ОБРАТИТЕ ВНИМАНИЕ, что я никоим образом не требую спойлера для решения задания. Я более чем готов "выполнять работу за класс", но я просто не понимаю, что я должен здесь делать. Я не верю, что инструкции и рекомендации, приведенные в курсе, достаточны для того, чтобы обернуться вокруг причуд функционального программирования, поэтому я приветствую любые комментарии/ответы, которые касаются того, как правильно думать, а не как правильно кодировать.
Ответы
Ответ 1
A
/ \ union D
B C
((B union C) union D) incl A
^^^^^^^^^......................................assume it works
( B )
( \ union D ) incl A
( C )
(((0 union C) union D) incl B) incl A
^^^^^^^^^.....................................just C
(((C union D) incl B) incl A
^^^^^^^^^.....................................expand
((((0 union 0) union D) incl C) incl B) incl A
^^^^^^^^^....................................just 0
(((0 union D) incl C) incl B) incl A
^^^^^^^^^.....................................just D
((D incl C) incl B) incl A
^^^^^^^^^^^^^^^^^^^^^^^^^^.......................all incl now
Просто запишите его шаг за шагом. Теперь вы видите, что объединение сводится к кучке включенных операторов, применяемых к правым аргументам.
Ответ 2
Я понимаю, что incl
вставляет элемент в существующий набор? Если да, то там, где происходит вся настоящая работа.
Определение объединения - это множество, включающее все в любом наборе ввода. Если два набора сохранены как двоичные деревья, если вы берете союзы первого набора с ветвями второго, единственным элементом в том, что может отсутствовать результат, является элемент в корневом каталоге node второго дерева, поэтому, если вы вставляете этот элемент, у вас есть объединение обоих наборов ввода.
Это просто очень неэффективный способ вставки каждого элемента из обоих наборов в новый набор, который начинается пустым. Предположительно дубликаты отбрасываются incl
, поэтому результатом является объединение двух входов.
Возможно, это помогло бы игнорировать древовидную структуру на данный момент; это не очень важно для основного алгоритма. Скажем, у нас есть абстрактные математические множества. Учитывая набор входных данных с неизвестными элементами, мы можем сделать две вещи:
- Добавьте к нему элемент (который ничего не делает, если элемент уже присутствует)
- Проверьте, не является ли набор непустым и, если это так, разложите его на один элемент и два непересекающихся подмножества.
Чтобы взять объединение двух множеств {1,2} и {2,3}, начнем с разложения первого множества на элемент 1 и подмножества {} и {2}. Мы рекурсивно используем объединение {}, {2} и {2,3} с использованием того же процесса, затем вставляем 1 в результат.
На каждом шаге проблема сводится от одной операции объединения к двум совместным операциям на меньших входах; стандартный алгоритм разделения и покоя. При достижении объединения одноточечного множества {x} и пустого множества {} объединение тривиально {x}, которое затем возвращается обратно в цепочку.
Древовидная структура используется только для того, чтобы разрешить анализ/декомпозицию case на более мелкие наборы и сделать вставку более эффективной. То же самое можно сделать с использованием других структур данных, таких как списки, разделенные пополам для декомпозиции и с вставкой, выполненной с помощью исчерпывающей проверки на уникальность. Чтобы эффективно использовать профсоюз, требуется алгоритм, который немного более умен и использует структуру, используемую для хранения элементов.
Ответ 3
Поэтому, основываясь на всех ответах выше, я думаю, что настоящая рабочая лошадка incl
, а рекурсивный способ вызова union
- это просто пройти через все элементы в наборах.
Я придумал следующую реализацию объединения, это лучше?
def union(other:BTSet) :BTSet = right union (left union (other incl element))
Ответ 4
2
/ \ union 4
1 3
((1 union 3) union 4) incl 2
^^^^^^^^^......................................assume it works
(((E union E) union 3 incl 1) union 4) incl 2
^^^^^^^^^.....................................still E
(E union E) union 3 incl 1 = E union 3 incl 1 = 3 incl 1
Следующее поддерево должно быть 3, включая 1
( 3 )
( \ union D ) incl 2
( 1 )
(((1 union E) union 4) incl 3) incl 2
^^^^^^^^^.......................................expand
(((( (E union E) union E) incl 1) union 4) incl 3) incl 2
^^^^^^^^^^^^^^^^^^^^^^^^^^..................still 1
((1 union 4) incl 3) incl 2
^^^^^^^^......................................continue
((((E union E) union 4) incl 1) incl 3) incl 2
^^^^^^^^^^^^^^^^^^^^^^^^^^^^..........expand 1 union 4
((4 incl 1) incl 3) incl 2
^^^^^^^^^^^^^^^^^^^^^^^^^............Final union result
Спасибо @Rex Керр вычеркивает шаги. Я заменяю второй шаг фактическим этапом выполнения, который может дать более четкое описание функции Scala union
.
Ответ 5
Вы не можете понять рекурсивные алгоритмы, если не посмотрите на базовый случай. На самом деле, зачастую ключ к пониманию заключается в понимании базового случая. Так как базовый случай не показан (возможно, потому, что вы его не заметили, в первую очередь) отсутствует понимание.
Ответ 6
Я делаю тот же курс, и приведенная выше реализация union
оказалась крайне неэффективной.
Я придумал следующее не очень функциональное решение для создания объединения наборов бинарных деревьев, которое является более эффективным:
def union(that: BTSet): BTSet = {
var result:BTSet = this
that.foreach(element => result = result.incl(element))
result
}