КАТЕГОРИИ: Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748) |
Case n of
Begin End. P(n) Begin End Begin Begin Begin End. Begin Begin Begin x:= 0; ... E(M);... Write(x); {На экран будет выведен 0} x:= 1 end; Параметры (параметры-значения) рекурсивной процедуры (функции) формируют свои новые значения в момент рекурсивного вызова. Эти значения передаются в новую копию рекурсивной процедуры. При возврате из рекурсии параметры-значения восстанавливают свои прежние значения. Параметры - переменные по существу передают в новую копию рекурсивной процедуры свои имена. Поэтому при выходе из рекурсии их значения определяются в той копии, из которой произошел выход. Пример 5. Перестановки. Сгенерировать (распечатать) все перестановки элементов конечной последовательности, состоящей из первых N букв латинского алфавита и найти их количество. Решение Сведем задачу к нескольким подзадачам, более простым, чем исходная. Пусть S = [ s1, s2,..., sn ] - конечный набор символов. Через P(S) обозначим множество всех перестановок S, a через P(S,i) - множество всех перестановок, в которых на последнем месте стоит элемент si. Тогда P(S) = P(S,n) È P(S, n-1) È... È P(S,1) Элемент множества P(S, i) имеет вид [sj2,..., sjn, si], где j2,..., jn - всевозможные перестановки индексов, не равных i. Поэтому P(S, i) = (P(S \ si), si) и P(S)=(P(S \ s1),s1)È...È (P(S \ sn), sn). Полученное соотношение выражает множество P(S) через множества перестановок наборов из (n-1) символа. Дополнив это соотношение определением P(S) на одноэлементном множестве, получим: 1. P({s}) = {s} 2. P(S) = (P(S\s1), s1) È... È (P(S\sn), sn)
Уточним алгоритм, опираясь на представление S в виде массива S[1..n] of char. Во первых, определим параметры процедуры P: k - количество элементов в наборе символов; S - набор переставляемых символов. Алгоритм начинает работу на исходном наборе и генерирует все его перестановки, оставляющие на месте элемент s[k]. Если множество перестановок, в которых на последнем месте стоит s[j], уже порождено, меняем местами s[j-1] и s[k], выводим на печать полученный набор и применяем рекурсивно алгоритм к этому набору. Параметр k управляет рекурсивными вычислениями: цепочка вызовов процедуры P обрывается при k=1. Program Permutations; {Все перестановки множества S} Const N = 10; Type Sequence = array [1..N] of Char; Var S: Sequence; Ch: Char; Proсedure WriteSequence (Var A: Sequence); {вывод на печать перестановки массива} Var i: Integer; {Блок вывода массива А на экран} End; Procedure P(k: Integer; Q: Sequence); Var j: integer; Buf: Char; {Count:= Count + 1;} if k<>1 then P(k-1, Q);{ Рекурсивный вызов Р} {Все перестановки с элем. S[k] на последнем месте} For j:= k - 1 downto 1 do begin Buf:= Q[j]; Q[j]:= Q[k]; Q[k]:= Buf; {Меняем местами S[j] и S[k]} WriteSequence(Q); {Печать очередной перестановки} P(k - 1, Q) {Рекурсивный вызов Р} end {Все перестановки с S[j] на последнем месте} End; Begin {Раздел операторов программы} For Сh:= ‘a’ to Chr(Ord(‘a’)+N) do S[Ch]:= Ch; {Генерация исходного массива S} WriteSequence(S); {Печать исходной перестановки } Count:= 0; {Счетчик количества вызовов процедуры P} P(N, S); {Вызов P из основной программы} Write(‘ Count = ’, Count)
Рассмотрим другой вариант этого алгоритма. Используем S как глобальную переменную. Тогда при вызове P S будет изменяться. Следовательно, при выходе из рекурсии массив S нужно восстанавливать, используя обратную перестановку! Program All_permutations; Const n = 4; Type Sequence = array[1..n] of char; Var S: Sequence; Buf: char; i: Integer; Procedure WriteSequence; For i:= 1 to n do Write(S[i]); Writeln end; Procedure P(k: Integer); Var j: integer; Procedure Swap(i, j: Integer); Buf:= S[j]; S[j]:= S[i]; S[i]:= Buf end; if k > 1 then P(k - 1); For j:= k - 1 downto 1 do begin Swap(j, k); {Прямая перестановка} WriteSequence; P(k - 1); Swap(k, j) {Обратная перестановка} End; {Генерация исходного набора S} WriteSequence;
Важнейшую роль в реализации рекурсивных алгоритмов играет контроль выхода из рекурсии (ее окончания). Если не предусмотреть выхода из рекурсии, цепочка рекурсивных вызовов станет (теоретически) бесконечной. На практике это приведет к переполнению той части памяти компьютера, в которой рекурсия реализуется. Рекурсивный алгоритм всегда имеет так или иначе реализованное условное ветвление, одна или несколько ветвей которого - нерекурсивные действия, а другая (другие) - рекурсивный вызов. Логическое значение условия, управляющего этим ветвлением, зависит от параметров рекурсии.
При определенных значениях параметров алгоритм не вызывает новую рекурсивную копию, а выполняет нерекурсивные действия, тем самым обрывая цепочку рекурсивных вызовов. Эти нерекурсивные действия будем называть базисом рекурсии. Вторую ветвь естественно назвать шагом рекурсии, а условие - условием рекурсии.
В примере 2 это ветвление имеет вид If n = 1 {условие рекурсии} then F:= 1 {базис рекурсии} else F:= F(n - 1) * n {шаг рекурсии} В примере 3 это выглядит так: If x <> y { условие рекурсии } Then If x < y { шаг рекурсии } then GCD:= GCD(x, y - x) else GCD:= GCD(x - y, y) Else GCD:= x { базис рекурсии } В примере 3 ветвление реализовано оператором Case. В примере 4 выбор управляется условием k > 1, а ветвление реализовано с помощью арифметического цикла, который не исполнится, если k <= 1. Базис рекурсии вырожден: при выходе из рекурсии никакие действия не выполняются. Важную роль в понимании процесса исполнения рекурсивного алгоритма играет представление о том, как и в каком порядке осуществляются рекурсивные вызовы. Первоначальное представление об этом механизме мы получили, рассмотрев рекурсивный алгоритм вычисления N! Полная картина рекурсивных вызовов представляется в виде цепочки рекурсивных копий длины N. Аналогично будет выглядеть полная картинка рекурсивных вызовов в примере 2. Нарисуем ее при исходных значениях параметров x = 26, y = 16. { НОД(26, 16) }
Линейность картинки рекурсивных вызовов обусловлена тем, что в соответствующей рекурсивной функции возможен только один рекурсивный вызов. Если в рекурсивной функции (процедуре) возможны два и более вызовов, осуществляемых последовательно, полная картинка рекурсии будет ветвящейся. Пример 6. Последовательность Фибоначчи. Требуется построить алгоритм вычисления n-того числа последовательности F(n), определенной рекурсивными соотношениями: F(0) = F(1) = 1; F(n+2) = F(n+1) + F(n). Переведя определение F(n) на Паскаль, получим: Function F(n: Integer): Integer; 0,1: F:= 1 else F:=F(n-1)+F(n-2) {Два последовательных вызова}
Дата добавления: 2014-01-06; Просмотров: 267; Нарушение авторских прав?; Мы поможем в написании вашей работы! Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет |