Ремонт принтеров, сканнеров, факсов и остальной офисной техники


назад Оглавление вперед




[150]

Поскольку нет никакого априорного ограничения на число вложенных рекурсивных вызовов, нам может понадобиться хранить произвольное число значений регистров. Значения эти требуется восстанавливать в порядке, обратном порядку их сохранения, поскольку в гнезде рекурсий последняя начатая подзадача должна завершаться первой. Поэтому требуется использовать для сохранения значений регистров стек (stack), или структуру данных вида «последним вошел, первым вышел». Можно расширить язык регистровых машин и добавить в него стек, если ввести два новых вида команд: значения заносятся на стек командой save и снимаются со стека при помощи команды restore. После того, как последовательность значений сохранена на стеке, последовательность команд restore восстановит их в обратном порядке.3

С помощью стека можно использовать для всех подзадач-факториалов единую копию путей данных факториальной машины. Имеется подобная проблема и при использовании последовательности команд контроллера, который управляет путями данных. Чтобы запустить новое вычисление факториала, контроллер не может просто перейти в начало последовательности, как в итеративном процессе, поскольку после решения подзадачи поиска (n - 1)! машине требуется еще домножить результат на n. Контроллер должен остановить вычисление n!, решить подзадачу поиска (n - 1)! и затем продолжить вычисление n!. Такой взгляд на вычисление факториала приводит к использованию механизма подпрограмм из раздела 5.1.3, при котором контроллер с помощью регистра continue переходит к той части последовательности команд, которая решает подзадачу, а затем продолжает с того места, где он остановился в главной задаче. Мы можем таким образом написать факториальную подпрограмму, которая возвращается к точке входа, сохраненной в регистре continue. При каждом вызове подпрограммы мы сохраняем и восстанавливаем регистр continue подобно регистру n, поскольку все «уровни» вычисления факториала используют один и тот же регистр continue. Так что факториальная подпрограмма должна записать в continue новое значение, когда она вызывает сама себя для решения подзадачи, но для возврата в место, откуда она была вызвана для решения подзадачи, ей потребуется старое значение continue.

На рисунке 5.11 показаны пути данных и контроллер машины, реализующей рекурсивную процедуру factorial. В этой машине имеются стек и три регистра с именами n, val и continue. Чтобы упростить диаграмму путей данных, мы не стали давать имена кнопкам присваивания регистров, и поименовали только кнопки работы со стеком - sc и sn для сохранения регистров, rc и rn для их восстановления. В начале работы мы кладем в регистр n число, факториал которого желаем вычислить, и запускаем машину. Когда машина достигает состояния fact-done, вычисление закончено и результат находится в регистре val. В последовательности команд контроллера n и continue сохраняются перед каждым рекурсивным вызовом и восстанавливаются при возврате из этого вызова. Возврат из вызова происходит путем перехода к месту, хранящемуся в continue. В начале работы машины continue получает такое значение, что последний возврат переходит в fact-done. Регистр val, где хранится результат вычисления факториала, не сохраняется перед рекур-

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

3В разделе 5.3 мы увидим, как стек можно реализовать на основе более элементарных операций.


сивным вызовом, поскольку после возврата из подпрограммы его старое содержимое не нужно. Используется только новое значение val, то есть результат подвычисления.

Несмотря на то, что в принципе вычисление факториала требует бесконечной машины, машина на рисунке 5.11 конечна, за исключением стека, который потенциально неограничен. Однако любая конкретная физическая реализация стека будет иметь конечный размер и таким образом будет ограничивать возможную глубину рекурсивных вызовов, которые машина сможет делать. Такая реализация факториала иллюстрирует общую стратегию реализации рекурсивных алгоритмов в виде обыкновенных регистровых машин, дополненных стеком. Когда нам требуется решить рекурсивную подзадачу, мы сохраняем на стеке регистры, текущее значение которых потребуется после решения этой подзадачи, решаем ее, затем восстанавливаем сохраненные регистры и продолжаем выполнение главной задачи. Регистр continue следует сохранять всегда. Нужно ли сохранять другие регистры, зависит от конкретной машины, поскольку не все рекурсивные вычисления нуждаются в исходных значениях регистров во время решения подзадачи (см. упражнение 5.4).

Двойная рекурсия

Рассмотрим более сложный рекурсивный процесс - древовидную рекурсию при вычислении чисел Фибоначчи, описанную в разделе 1.2.2:

(define (fib n)

(if (< n 2)

n

(+ (fib (- n 1)) (fib (- n 2)))))

Как и в случае с факториалом, рекурсивное вычисление чисел Фибоначчи можно реализовать в виде регистровой машины с регистрами n, val и continue. Машина более сложна, чем факториальная, поскольку в последовательности команд контроллера здесь два места, где нам нужно произвести рекурсивный вызов - один раз для вычисления Fib(n-1), а другой для вычисления Fib(n-2). При подготовке к этим вызовам мы сохраняем регистры, чье значение нам потребуется позже, устанавливаем в регистр n число, Fib от которого нам требуется вычислить (n - 1 или n - 2), и присваиваем регистру continue точку входа в главной последовательности, куда нужно вернуться (соответственно, afterfib-n-1 или afterfib-n-2). Затем мы переходим к метке fib-loop. При возврате из рекурсивного вызова ответ содержится в val. На рисунке 5.12 показана последовательность команд контроллера для этой машины.


(controller

(assign continue (label fact-done)) ; установить адрес

; окончательного возврата fact-loop

(test (op =) (reg n) (const 1)) (branch (label base-case))

;; Подготовиться к рекурсивному вызову, сохраняя n и continue.

;; Установить continue так, что вычисление продолжится

;; с after-fact после возврата из подпрограммы. (save continue) (save n)

(assign n (op -) (reg n) (const 1)) (assign continue (label after-fact)) (goto (label fact-loop)) after-fact (restore n) (restore continue)

(assign val (op *) (reg n) (reg val)) ; теперь val содержит n(n - 1)! (goto (reg continue)); возврат в вызывающую программу

base-case

(assign val (const 1)); базовый случай: 1! = 1

(goto (reg continue)); возврат в вызывающую программу

fact-done)

Рис. 5.11: Рекурсивная факториальная машина.



[стр.Начало] [стр.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] [стр.47] [стр.48] [стр.49] [стр.50] [стр.51] [стр.52] [стр.53] [стр.54] [стр.55] [стр.56] [стр.57] [стр.58] [стр.59] [стр.60] [стр.61] [стр.62] [стр.63] [стр.64] [стр.65] [стр.66] [стр.67] [стр.68] [стр.69] [стр.70] [стр.71] [стр.72] [стр.73] [стр.74] [стр.75] [стр.76] [стр.77] [стр.78] [стр.79] [стр.80] [стр.81] [стр.82] [стр.83] [стр.84] [стр.85] [стр.86] [стр.87] [стр.88] [стр.89] [стр.90] [стр.91] [стр.92] [стр.93] [стр.94] [стр.95] [стр.96] [стр.97] [стр.98] [стр.99] [стр.100] [стр.101] [стр.102] [стр.103] [стр.104] [стр.105] [стр.106] [стр.107] [стр.108] [стр.109] [стр.110] [стр.111] [стр.112] [стр.113] [стр.114] [стр.115] [стр.116] [стр.117] [стр.118] [стр.119] [стр.120] [стр.121] [стр.122] [стр.123] [стр.124] [стр.125] [стр.126] [стр.127] [стр.128] [стр.129] [стр.130] [стр.131] [стр.132] [стр.133] [стр.134] [стр.135] [стр.136] [стр.137] [стр.138] [стр.139] [стр.140] [стр.141] [стр.142] [стр.143] [стр.144] [стр.145] [стр.146] [стр.147] [стр.148] [стр.149] [стр.150] [стр.151] [стр.152] [стр.153] [стр.154] [стр.155] [стр.156] [стр.157] [стр.158] [стр.159] [стр.160] [стр.161] [стр.162] [стр.163] [стр.164] [стр.165] [стр.166] [стр.167] [стр.168] [стр.169] [стр.170] [стр.171] [стр.172] [стр.173] [стр.174] [стр.175] [стр.176] [стр.177] [стр.178] [стр.179] [стр.180] [стр.181] [стр.182] [стр.183] [стр.184] [стр.185] [стр.186] [стр.187] [стр.188] [стр.189] [стр.190] [стр.191] [стр.192] [стр.193] [стр.194] [стр.195] [стр.196]