Шаг 108.
Рекурсия на Python.
Множественная рекурсия I: "разделяй и властвуй". Задача очертания (окончание)

    На этом шаге мы рассмотрим алгоритм слияния контуров.

    Задача объединения контуров - новая отдельная вычислительная задача. В то время как большинство решений в различных источниках является итерационными, мы предложим линейно-рекурсивный метод. Его входы - два сортированных списка кортежей, представляющих собой контур. Кроме того, поскольку кортеж задаёт изменение высоты контура, метод, прежде чем изменить высоту, должен иметь доступ к предыдущей высоте. Более того, поскольку предлагаемый алгоритм обрабатывает список с первого кортежа (до исчерпания списка), последовательно удаляя обработанные кортежи, предыдущих высот не будет во входных списках. Таким образом, метод требует двух дополнительных параметров, скажем p1 и p2, для сохранения предыдущих высот контура. Естественно, оба этих параметра должны получить начальное значение 0 при первом вызове метода из основной функции (см. строку 9 примера 6.14).

    Размер задачи зависит от длин входных списков контуров, скажем n1 и n2. Можно считать, что начальное условие выполняется, когда один из списков пуст, и метод в таком случае должен тривиально вернуть второй список. В примере 6.15 приведён код, где начальные условия описаны в строках 2-5.


Пример 6.15. Рекурсивный метод объединения контуров
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def merge_skylines(sky1, sky2, p1, p2):
    if sky1 == []:
        return sky2
    elif sky2 == []:
        return sky1
    else:
        x1 = sky1[0][0]
        x2 = sky2[0][0]
        h1 = sky1[0][1]
        h2 = sky2[0][1]

        if x1 == x2:
            h = max(p1, p2)
            new_h = max(h1, h2)
            if h == new_h:
                return merge_skylines(sky1[1:], sky2[1:],
                                      h1, h2)
            else:
                return ([(x1, new_h)]
                        + merge_skylines(sky1[1:], sky2[1:],
                                         h1, h2))

        elif x1 < x2:
            if h1 > p2:
                return ([(x1, h1)]
                        + merge_skylines(sky1[1:], sky2,
                                         h1, p2))
            elif p1 > p2:
                return ([(x1, p2)]
                        + merge_skylines(sky1[1:], sky2,
                                         h1, p2))
            else:
                return merge_skylines(sky1[1:], sky2,
                                      h1, p2)

        else:
            if h2 > p1:
                return ([(x2, h2)]
                        + merge_skylines(sky1, sky2[1:],
                                         p1, h2))
            elif p2 > p1:
                return ([(x2, p1)]
                        + merge_skylines(sky1, sky2[1:],
                                          p1, h2))
            else:
                return merge_skylines(sky1, sky2[1:], p1, h2)    
Архив с полным текстом приложения можно взять здесь.

    Ключом для определения подходящей декомпозиции является то, что результат объединяющей функции - это новое очертание (контур), кортежи которого отсортированы в порядке возрастания значения х. Таким образом, алгоритм анализирует первые кортежи каждого списка и обрабатывает тот, что имеет меньшее значение х (или оба, если их значения х равны). Значит, в рекурсивных условиях, помимо первых кортежей контуров 1, h1) и 2, h2), нам нужны ещё и их предыдущие высоты p1 и p2.

    Давайте сначала рассмотрим случай, когда х1 = х2 = х. Так как кортежи маркируют изменения в горизонте, нам, возможно, придётся включать в решение кортеж с наибольшей высотой. Например, на рисунке 1(a) точка (х, h2) должна быть включена в конечный горизонт.


Рис.1. Случаи объединения контуров, когда их высота меняется в одной и той же точке x

    Далее рекурсивный вызов будет использовать хвосты обоих входных списков без (х, h1) и (х, h2), так как возможные изменения в точке х уже обработаны правильно. Это делается в строках 19-21. Наконец, бывают ситуации, когда новый кортеж не включается в решение. Это происходит, когда новая наибольшая высота совпадает с предыдущей наибольшей высотой контура (то есть когда max(h1, h2) = max(p1, p2)), так как в точке х высота не меняется. Рисунок 1(b) иллюстрирует этот случай, где точка (x, h2) не должна включаться в конечный контур (см. строки 16 и 17).

    Теперь проанализируем возможные сценарии, когда значения x первых кортежей горизонтов не равны. Без потери общности допустим, что х1 < х2. В этом случае алгоритм должен решить, включать ли кортеж 1, h1), или 1, p2), или ни тот, ни другой, как показано на рисунке 2.


Рис.2. Возможные варианты слияния контуров при x1 < x2

    Если h1 > p2, то в точке х1 первый контур выше второго, и поэтому в объединение должен быть включён кортеж 1, h1) (см. строки 25-27). Если же h1 < p2, то алгоритм должен проверить условие p1 > p2. Если условие истинно, то метод включает кортеж 1, p2) (см. строки 29-31). Обратите внимание, что при h1 < р2 создаётся новый кортеж, которого нет во входных списках контуров. Случаи р2 ≥ h1 и р2 ≥ р1 означают, что объединённый контур не меняется в x1. В итоге, обработав первый кортеж первого контура (x1, h1), метод отбрасывает его при вызове самого себя в соответствующих рекурсивных условиях. Кроме этого, аргументами, задающими предыдущие высоты горизонтов, будут h1 и р2 (см. строки 27, 31 и 34). Остальная часть кода в строках 36-46 похожа на код в строках 23-34 и обрабатывает случай x2 < x1.

    На следующем шаге мы приведем решения нескольких задач.




Предыдущий шаг Содержание Следующий шаг