Опубликован: 02.02.2011 | Доступ: свободный | Студентов: 3339 / 950 | Оценка: 4.43 / 3.57 | Длительность: 33:06:00
Специальности: Программист
Лекция 4:

Рекурсивные функции

< Лекция 3 || Лекция 4: 123 || Лекция 5 >

Пример 4. Задача о Ханойских башнях

Ханойская башня является одной из популярных головоломок XIX века. Даны три стержня, на один из которых нанизаны n колец, причем кольца отличаются размером и лежат меньшее на большем. Задача состоит в том, чтобы перенести пирамиду из n колец за наименьшее число ходов с одного стержня на другой. За один раз разрешается переносить только одно кольцо, причём нельзя класть большее кольцо на меньшее.

Существует древнеиндийская легенда, согласно которой в городе Бенаресе под куполом главного храма, в том месте, где находится центр Земли, на бронзовой площадке стоят три алмазных стержня. В день сотворения мира на один из этих стержней было надето 64 кольца. Бог поручил жрецам перенести кольца с одного стержня на другой, используя третий в качестве вспомогательного. Жрецы обязаны соблюдать условия:

  1. переносить за один раз только одно кольцо;
  2. кольцо можно класть только на кольцо большего размера или на пустой стержень.

Согласно легенде, когда, соблюдая все условия, жрецы перенесут все 64 кольца, наступит конец света. Для 64 колец это 18 446 744 073 709 551 615 перекладываний, и, если учесть скорость одно перекладывание в секунду, получится около 584 542 046 091 лет, то есть апокалипсис наступит нескоро.

На рисунке ( рис. 3.2) изображена ситуация, иллюстрирующая перекладывание 7 колец со стержня А на В через вспомогательный С.

Ситуация при переносе семи колец

Рис. 3.2. Ситуация при переносе семи колец

Кольцо со стержня А можно перенести на стержень В или С, кольцо со стержня В можно перенести на стержень С, однако, нельзя перенести его на стержень А.

Задача состоит в том, чтобы определить последовательность минимальной длины переноса колец. Решением задачи будем считать последовательность допустимых переносов, каждый из которых имеет вид: A->B, A->C, B->A, B->C, C->A, C->B. Если кольцо всего одно, то задача решается за один перенос A->В. Для перемещения двух колец требуется выполнить три действия: A->C, A->В, C->B. Решение задачи для трех колец содержит семь действий, для четырех – 15.

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

Параметризация. Функция имеет четыре параметра, первый параметр – число переносимых колец, второй параметр – стержень, на который первоначально нанизаны кольца. Третий параметр функции – стержень, на который требуется перенести кольца, и, наконец, четвертый параметр – стержень, который разрешено использовать в качестве вспомогательного.

База рекурсии. Перенос одного стержня.

Декомпозиция. Последовательность переноса колец изображена на рисунке ( рис. 3.3).

Схема решения задачи о Ханойских башнях для четырех колец

Рис. 3.3. Схема решения задачи о Ханойских башнях для четырех колец

Чтобы перенести n колец со стержня A на стержень B, используя стержень C в качестве вспомогательного, можно поступить следующим образом:

  • перенести n –1 кольцо со стержня A на C, используя стержень B в качестве вспомогательного стержня;
  • перенести последнее кольцо со стержня A на стержень B ;
  • перенести n –1 кольцо со стержня C на B, используя стержень A в качестве вспомогательного стержня.

При переносе n –1 кольца можно воспользоваться тем же алгоритмом, т.к. на нижнее кольцо с самым большим диаметром можно просто не обращать внимания. Перенос одного кольца в программе выражается в том, что выводится соответствующий ход.

//Ханойские башни
#include "stdafx.h"
#include <iostream>
using namespace std;
int hanoj(int n, char A, char B, char C); 
//Объявление функции перемещения колец с A на C через B

int _tmain(int argc, _TCHAR* argv[]){
  char x='A',y='B',z='C';
  int k,h;
  printf("Задача о Ханойских башнях");
  printf("\nВведите количество колец: "); 
  scanf("%d",&k); 
  h=hanoj(k,x,z,y);
  printf("\nКоличество перекладываний равно %d",h);
  system("pause");
  return 0;
}

//Описание функции перемещения колец с A на C через B
int hanoj(int n, char A, char B, char C){  
  int num;
  if (n == 1) {printf("\n   %c -> %c", A, C); num = 1;}
  else {
    num=hanoj(n-1, A, C, B);
    printf("\n   %c -> %c", A, C);
    num++;
    num+=hanoj(n-1, B, A, C);
    }
  return num;
}

Ключевые термины

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

Декомпозиция – это выражение общего случая через более простые подзадачи с измененными параметрами.

Косвенная (взаимная) рекурсия – это последовательность взаимных вызовов нескольких функций, организованная в виде циклического замыкания на тело первоначальной функции, но с иным набором параметров.

Параметризация – это выделение из постановки задачи параметров, которые используются для описания условия задачи и решения.

Прямая рекурсия – это непосредственное обращение рекурсивной функции к себе, но с иным набором входных данных.

Рекурсивная триада – это этапы решения задач рекурсивным методом.

Рекурсивная функция – это функция, которая в своем теле содержит обращение к самой себе с измененным набором параметров.

Рекурсивный алгоритм – это алгоритм, в определении которого содержится прямой или косвенный вызов этого же алгоритма.

Рекурсивный стек – это область памяти, предназначенная для хранения всех промежуточных значений локальных переменных при каждом следующем рекурсивном обращении.

Рекурсия в программировании – это пошаговое разбиение задачи на подзадачи, подобные исходной.

Рекурсия в широком смысле – это определение объекта посредством ссылки на себя.

Краткие итоги

  1. Свойством рекурсивности характеризуются объекты окружающего мира, обладающие самоподобием.
  2. Рекурсия в широком смысле характеризуется определением объекта посредством ссылки на себя.
  3. Рекурсивные функции содержат в своем теле обращение к самим себе с измененным набором параметров. При этом обращение к себе может быть организовано через цепочку взаимных обращений функций.
  4. Решение задач рекурсивными способами проводится посредством разработки рекурсивной триады.
  5. Целесообразность применения рекурсии в программировании обусловлена спецификой задач, в постановке которых явно или опосредовано указывается на возможность сведения задачи к подзадачам, аналогичным самой задаче.
  6. Область памяти, предназначенная для хранения всех промежуточных значений локальных переменных при каждом следующем рекурсивном обращении, образует рекурсивный стек.
  7. Рекурсивные методы решения задач нашли широкое применение в процедурном программировании.
< Лекция 3 || Лекция 4: 123 || Лекция 5 >
Денис Курбатов
Денис Курбатов
Владислав Нагорный
Владислав Нагорный

Подскажите, пожалуйста, планируете ли вы возобновление программ высшего образования? Если да, есть ли какие-то примерные сроки?

Спасибо!