Задача от поправителен изпит по САА – 25.06.2012

Днес се проведе поправителният изпит по Синтез и анализ на алгоритми (САА) и в тази публикация ще разгледаме падналата се задача.

Даден е двумерен масив a с n елемента.
 а) Да се намери сумата от елементите на триъгълниците, заключени между второстепенния диагонал и първия и последен ред и стълб;
 б) да се намери най-големият общ делител на сумите от а)

Най-първо ще отбележим, че масивът ни е квадратен – само и единствено в този случай диагоналът разделя матрицата ни на два триъгълника. Вторичният диагонал започва от най-горния най-десен елемент и върви надолу и наляво, докато не достигне най-лявата колона или най-долния ред на матрицата. Т.е. нашият масив разполага с корен от n колони и също толкова редове.

Машата матрица – откриване на елементите, отговарящи на условиетоПодусловие а ще изпълним с два вложени един в друг for цикъла, с помощта на които ще обходим масива. За всеки елемент, разположен между първите и последните редове и колони ще определим от коя страна на второстепенния диагонал се намира (или дали не е част от самия диагонал) и ще прибавим стойността му към съответната сума.

Условието един елемент да се намира в рамките на вторичния диагонал е неговите координати да бъдат (ред, общ брой колони-ред).

Нашият цикъл ще добие следния вид:

    for(i=1; i<COLS-1; i++){
        for(j=1; j<COLS-1; j++){
            if(j < COLS-1-i) left += a[i][j];
            else if(j >= COLS-i) right += a[i][j];
        }
    }

Тук COLS е броят колони (а също и редове), а left и right сумите, натрупани съответно в левия и десния триъгълник. Първоначалните стойности на i и на j са единици, за да пропуснем първите ред и колона, а финалните са с едно по-малки от броя редове и колони минус едно – за да пропуснем последните.

Важно е и да не изпуснем да извадим единица в първото условно разклонение if – иначе програмата ще събере и елементите, изграждащи самия диагонал.

Сега за условие б – да намерим най-големия общ делител на двете суми. Най-голям общ делител наричаме най-голямото число, което дели две цели числа без остатък. Това е една стандартна задача, решена от древногръцкия математик Евклид преди около 2400 години. Забележително е, че решенито му се ползва и до днес!

Евклидовият алгоритъм се базира на наблюдението, че най-големият общ делител не се променя, ако от по-голямото от двете числа извадим по-малкото.

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

„Атинската школа“ на Рафаело

Евклид в „Атинската школа“ на Рафаело

int gcd(int a, int b){
    if(!b) return a;
    return gcd(b, a%b);
}

Както вече казахме, най-големият общ делител не се променя, когато от по-голямото число извадим по-малкото. Следователно функцията вика сама себе си, изпращайки по-малкото число като първи аргумент и остатъка от делението на двете като втори. Ако b не е нула, цикълът се повтаря.

Поради естеството на действието „деление по модул“, методът ще сработи, независимо дали при извикването му стойността на a е по-голяма от тази на b или точно обратното.

Ето го и цялото решение:

#include <iostream>
#define COLS 5

using namespace std;

int gcd(int a, int b){ // Евклид
	if(!b) return a;
	return gcd(b, a%b);
}

int main() {
	int a[][COLS] = { // Примерен масив
			  { 4, 7,  9, -1, 0 },
			  { 2, 0,  2,  5, 6 },
			  { 9, 7, -4,  9, 11 },
			  { 7, 7,  0, -3, 4, },
			  { 9, 4,  3,  0, 5 }
			},
	left = 0, right = 0, i, j;

	for(i=1; i<COLS-1; i++){
		for(j=1; j<COLS-1; j++){
			if(j < COLS-1-i) left += a[i][j];
			else if(j >= COLS-i) right += a[i][j];
		}
	}

	cout << "Сумата в левия триъгълник е " << left << endl
	<< "В десния пък е " << right << endl
	<< "Най-големият общ делител е " << gcd(left, right) << endl;

	return 0;
}

Може да са ви интересни и двете задачи от редовната изпитна сесия.

DIY: Приложение за съкращаване на URL

Goo.gl

Goo.gl е съкращаващата услуга на Google, достъпна само за регистрирани потребители на търсачката.

Има много причини да ползвате т.нар. съкратител на адреси, особено ако сте потребител на сайтове, където дължината на съобщенията, които изпращате, е ограничена. Или когато нямате възможност да форматирате своя коментар – дългите връзки разпокъсват написаното и идеите ви се губят. Или пък когато пишете SMS-и. Да не пропускаме и връзките с кирилски букви в тях – те често се конвертират към нечетимия за обикновения човек URI по учебник (по правило съдържащ само символите от ASCII, всеки друг знак се кодира). В резултат връзките са сякаш безкрайно дълги и абсолютно непрактични за споделяне.

Ето тук се намесват съкратителите – с помощта на кратък домейн от рода на is.gd или bit.ly и скрипт се създава кратка връзка от рода на bit.ly/NkMxvo, която пренасочва всеки любопитен посетител към желания от вас по-дълъг адрес.

Освен че е кратка, тази връзка често ви носи и допълнителни облаги, например възможността да следите статистики за нейното ползване – кой, кога и как е попаднал на нея. Всичко това зависи от вашия избор на услуга за скъсяване.

Не всички съкратители са направени да работят изцяло за вас – Туитър например ползва своя съкратител t.co за капсулиране на всеки един адрес, публикуван в съобщение из сайта с цел контрол над външното съдържание. Много медии, сред които National Geographic и New York Times също разполагат с подобни услуги, към които прибягват при споделяне на свое съдържание в интернет. Всъщност цели правителства са полудели по тази идея – руското например ползва krln.ru.

Е, в тази статия ще си създадем наш собствен скъсител на адреси.

На първо място ще отбележа, че знам, че съществуват няколко подобни проекта с отворен код, при това най-вероятно някои от тях са много добри. Не съм ги гледал. Всичко описано по-долу си е моя измислица и в качеството си на такава не претендира да е най-добрата измислица!

Задача

Имате дълга връзка, която желаете да скъсите. Чрез бланка посочвате въпросната връзка и по желание избирате кратко съчетание от букви, което да ѝ отговаря. Ако не посочите такова, системата сама ви избира.

Можете да следите последните връзки или да научите подробности за точно определен кратък адрес. Можете да следите кой и кога е използвал някоя препратка.

Веднъж съкратили каквото искате, споделяте краткия адрес с приятели,  колеги или непознати и когато те го посетят, системата ги отвежда на оригиналната локация, като си отбелязва откъде са се взели, какъв браузър и операционна система ползват, какъв е техният IP адрес и кога са дошли.

От какво се нуждаем?

На първо място – от каратък и лесен за запомняне домейн, който ще ползваме за нашия скъсител. Аз си имам bozhur.in – не е толкова кратък, но пък представлява т.нар. domain hack или с други думи комбинация от домейни от първо и второ ниво, подбрана така, че да изписва определена дума – в случая името Божурин. За примера ще ползваме By.Bozhur.in.

Останалото е като че ли по-лесно за осигуряване – трябва ни хостинг с поддръжка на PHP и MySQL.

Нека приемем, че всичко е налице и да пристъпим към действие!

Базата данни

Като начало ще си направим база от данни, където ще съхраняваме нашите данни.

Релационна диаграма за нашата база от данниНеобходими са ни само две таблици – „Връзки“, където ще записваме коя кратка връзка на кой URL отговаря. От любопитство ще си отбелязваме и датата на създаването ѝ – нищо чудно в последствие да ни е нужна при изготвяне на статистиките.

В „Посещения“ пазим куп любопитни работи – кой, кога и откъде идва. Таблицата ще свържем с „Връзки“ посредством външен ключ.

Сега да създадем и самите таблици.

Таблица „Връзки“:

CREATE TABLE `links` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `link` varchar(255) NOT NULL UNIQUE,
  `url` varchar(255) NOT NULL,
  `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE utf8_bin;

… и таблица „Посещения“:

CREATE TABLE `visits` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `vip` varchar(16) DEFAULT NULL,
  `referer` varchar(255) DEFAULT NULL,
  `agent` varchar(255) DEFAULT NULL,
  `lid` int(10) unsigned DEFAULT NULL,
  `when` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY `lid` REFERENCES links(`id`)
  ON DELETE CASCADE
 ) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE utf8_bin;

След като сме готови с базата от данни, е време да напишем и малко код…

Програмна реализация

By.Bozhur.in – файловеЦялото действие ще е описано в рамките на три файла с общ размер около 7kB.

Накратко, index.php ще получава заявките от страна на потребителя, ще прави проверка в базата данни на какъв адрес отговаря подадената кратка връзка, ще пренасочва потребителя в тази посока и ще си записва данни за него в таблица „Посещения“.

Manage.php ще се грижи за административните функции – него ще ползваме, когато искаме да създадем нова препратка, да изтрием предишна или да преглеждаме събраните данни.

И най-накрая, link.php съдържа описанието на цялата тази функционалност, която първите два файла реализират. Именно този link.php ще напишем първо.

link.php

<?php
	/* By Bozhurin Quick Link Script (by.bozhur.in)
	 * 2012 © Ivaylo Bozhurin (dev at bozhur dot in)
	*/

class Linker {

	// Конструктор за клас Linker
	function Linker() {
		// Подразбираща се часова зона
		date_default_timezone_set('Europe/Helsinki');

		// Данни за връзка с базата данни – хост, потребителско име, парола
		$this->connexion = mysql_connect("localhost", "LinkDB", "PASSWORD") or die(mysql_error());
		mysql_query("SET CHARACTER SET utf8");
		mysql_query("SET NAMES utf8");
		mysql_query("SET SQL_SAFE_UPDATES=1");

		// Избираме базата данни
		mysql_select_db("LinkDB", $this->connexion) or die(mysql_error());
	}

	// Връща информация за конкретна кратка връзка
	function Get($link) {
		$q = "SELECT * FROM by_links WHERE link='" .mysql_real_escape_string($link). "' LIMIT 1";
		$res = mysql_query($q, $this->connexion);
		return mysql_fetch_array($res, MYSQL_ASSOC);
	}

	// Проверява дали предоставеното кратко име е заето (съществува ли в БД)
	function There($link) {
		$q = "SELECT id FROM by_links WHERE link='".mysql_real_escape_string($link)."'";
		$res = mysql_query($q, $this->connexion);
		return mysql_num_rows($res);
	}

	// Връща данните за определен брой кратки връзки
	function GetLinks($limit) {
		$q = "SELECT * FROM by_links WHERE 1 ORDER BY created DESC LIMIT $limit";
		$res = mysql_query($q, $this->connexion);
		for($i=0; $a[$i] = mysql_fetch_array($res, MYSQL_ASSOC); $i++); // Любимият ми for-цикъл!!!
		return $a;
	}		

	/* Вмъква информация за нова кратка връзка.
	 * За вход приема съкращаван адрес ($url) и
	 * кратка връзка ($link)
	 */
	function Insert($url, $link, $ip) {
		$q = "INSERT INTO by_links(url, link, ip) VALUES ('".
			 mysql_real_escape_string($url) ."', '".
			 mysql_real_escape_string($link) ."', '".
			 mysql_real_escape_string($ip) ."')";

		$res = mysql_query($q, $this->connexion);
		return mysql_affected_rows($res);
	}

	// Изтрива данни за връзка от БД
	function Delete($link) {
		$q = "DELETE FROM by_links WHERE link='" .mysql_real_escape_string($link). "'";
		$res = mysql_query($q, $this->connexion);
		return mysql_affected_rows($res);
	}		

	// Въвежда данни в таблица „Посещения“
	function AddStats($lid, $vip, $ref, $agent) {
		$q = "INSERT INTO by_visits(lid, vip, referer, agent) VALUES (".
			 mysql_real_escape_string($lid) . ", '" .
			 mysql_real_escape_string($vip) . "', '" .
			 mysql_real_escape_string($ref) . "', '" .
			 mysql_real_escape_string($agent) . "')";
		$res = mysql_query($q, $this->connexion);
		return mysql_affected_rows($res);
	}
}
$linker = new Linker;

?>

Вече имаме всичко необходимо, за да пишем и четем от базата данни. Време е да направим страницата за администрация.

manage.php

Този файл ще се състои от две части – PHP скрипт, описващ въвеждането на данни, изпратени от бланка с метод post и XHTML описание на самата страница, включително бланката.

Нека първо направим скриптовата част. За нуждите на нашия пример тя ще реализира три неща – добавяне на нова препратка, премахване на съществуваща и извеждане на кратки данни за такава.

<?php
	include_once 'link.php'; 

	// Добавяне на препратка
	if($_POST["act"] == "add"){
		/* Ако потребителят представи желана кратка връзка и тя не е заета
		 * я вписваме в БД.
		*/
		if(($_POST["link"] != "") && (!$linker->There($_POST["link"]))){
			$linker->Insert($_POST["url"], $_POST["link"]);
		}

		// Иначе скриптът генерира случайна и вписва нея в БД.
		else {
			for(; $linker->There($_POST["link"] = substr(md5(uniqid()), 0, 5)); );
			$linker->Insert($_POST["url"], $_POST["link"]);
		}
	}

	// Премахване на препратка
	if(isset($_GET["del"])) {
		$linker->Delete($_GET["del"]);
	}

	// Шпиониране за препратка
	if($_POST["act"] == "spy"){
		$data = $linker->Get($_POST["link"]);

		if($data["id"] == "") $error = "Въвели сте несъществуваща връзка.<br />Ако искате, можете да си създадете такава.";
	}
?>

Да разпишем сега и външния облик. Освен бланки, там ще се съдържа и малко PHP код, използван да покажем последните трийсет препратки, както и информация за конкретна връзка, когато такава е поискана.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

	<head>

		<meta http-equiv="content-type" content="text/html;charset=utf-8" />
		<meta http-equiv="Content-Style-Type" content="text/css" />

		<meta name="Author" content="Ивайло Божурин" />
		<meta name="copyright" content="Copyright 2012" /> 		

		<title>By Bozhurin > Управление</title>
	</head>
	<body>
		<div style="float: left; margin: 10px;">
		<div style="width: 360px; border-radius: 6px; padding: 0px 6px 6px 6px;
		            text-align: center; font-size: 16px; background: #92AECA; color: #292995;">

			<form action="manage" method="post">

				<h1>Създай нова връзка</h1>
				<input type="text" name="url" size="32" value="http://"
				       style="background: #9EC1E2; border-radius: 2px; border: 1px solid #5C88B2;" />
				<br />

				<h2>Пряк път по желание</h2>
				http://<strong>by.bozhur.in/</strong>
				<input type="text" name="link" size="5"
				       style="background: #9EC1E2; border-radius: 2px; border: 1px solid #5C88B2;" />
				<br /><br />

				<input type="hidden" name="act" value="add" />
				<input type="submit" value="Добави" />

			</form>
		</div>

		<div style="width: 360px; border-radius: 6px; padding: 0px 6px 6px 6px; 

		            text-align: center; font-size: 16px; background: #92AECA; color: #292995;">

			<form action="manage" method="post">

				<h1>Разузнай съществуваща</h1>

				http://<strong>by.bozhur.in/</strong>
				<input type="text" name="link" size="5" style="background: #9EC1E2;
				             border-radius: 2px; border: 1px solid #5C88B2;" />

				<input type="hidden" name="act" value="spy" />
				<input type="submit" value="Търси" />

			</form>
		</div>
		</div>

		<div style="float: left; margin: 10px;">
		<div style="width: 360px; border-radius: 6px; padding: 0px 6px 6px 6px;
		            text-align: center; font-size: 16px; background: #92AECA; color: #292995;">

			<h1>Списък адреси</h1>

			<ul style="text-align: left">

		<?
		  // Извеждаме последните трийсет създадени адреса
		   $list = $linker->GetLinks(30);

			foreach($list as $l) {
				if($l["id"] != "")
					echo '<li><a href="'.$l["url"].'">'.str_replace("http://", "", $l["url"]).
					     '</a> (<a href="http://by.bozhur.in/'.$l["link"].'">'
					     .$l["link"].'</a>) '.
						 '<a href="http://by.bozhur.in/manage&amp;del='.$l["link"].
						 '"><img src="http://media.tsarstva.bg/icons/delete.png" '.
						 'style="vertical-align: middle" title="Изтрий" alt="Изтрий" border="0" /></a></li>';
			}
		?>

			</ul>
		</div>

		<? // Ако потребителят е поискал справка за адрес...
			if(isset($data)) {
		?>

		<div style="width: 360px; border-radius: 6px; padding: 0px 6px 6px 6px;
		           text-align: center; font-size: 16px; background: #92AECA; color: #292995;">

			<h1>Данни за адрес</h1>

		<? if($error) echo $error;
			else echo '<a href="'.$data["url"].'">'.str_replace("http://", "", $data["url"]).
			          '</a> (<a href="http://by.bozhur.in/'.$data["link"].'">'.$data["link"].'</a>)<br />';
		?>

		<? } ?>

		</div>
	</body>
</html>

CSS стиловете в горния код могат да са малко объркващи при четене, но пък страницата ни няма да изглежда съвсем ужасно. Ето я и нея:

Външен вид на страницата за управление

Вече сме на финалната права. Ще завършим системата с направата на index.php.

index.php

<?php
	if($_GET["go"] != "") {
		if($_GET["go"] == "manage") {
			include_once 'manage.php';
			exit;
		}
		include_once 'link.php';
		$l = $linker->Get($_GET["go"]);

		if($l["id"] != "") {
			$linker->AddStats($l["id"], $_SERVER['REMOTE_ADDR'], $_SERVER['HTTP_REFERER'], $_SERVER['HTTP_USER_AGENT']);
			header("Location: ".$l["url"]."");
		}
		else {
			header("Location: http://tsarstva.bg/sh/372/url-shortener/#несъществуващ");
		}
	}
	else header("Location: http://tsarstva.bg/sh/372/url-shortener/");
?>

Ако я оставим в този вид, системата би работила с адреси от типа на by.bozhur.in/?go=blog. Ние обаче целим възможно най-кратки връзки, затова ни остава едно последно действие – да си направим похдодящ .htaccess файл, който да да взима всичко след последната наклонена черта и да го пренаписва по подходящ за нас начин.

Както можете да видите от индексния файл, нямаме намерение да осигуряваме директен достъп до manage.php. Напротив, дори ще го направим невъзможен. Вместо това ще го включваме, подавайки „manage“ като входна стойност на go (by.bozhur.in/manage).

.htaccess

Options +FollowSymLinks
RewriteEngine On

RewriteCond %{REQUEST_URI} !index
RewriteRule (.*) index.php?go=$1

 Това е всичко!

Разбира се, в този вид има още много неща, които бихме могли да подобрим. Най-малкото, към момента никъде не правим статистика. Но това вече остава за домашно. Важното е, че имаме една скромна, но все пак ефективна система, която да ни отърве от дългите хипервръзки и да улесни нашето онлайн общуване.

Задачи от изпит по САА – 31.05.2012

Накратко ще представя условията и решенията на задачите от изпита по Синтез и анализ на алгоритми (САА), проведен в ТУ на 31-ви май в рамките на редовната изпитна сесия.

Задача 1: Даден е двумерен масив, състоящ се от 25 елемента. Нека в друг масив се запишат елементите по двата диагонала, основен и вторичен, подредени в обратен ред.

Решение:

#include <iostream>
#define SIZE 5
using namespace std;

int main() {
	int arr[][SIZE] = {
		{ 1,  4,  0, -1, 6 },
		{ 9,  2, -5,  7, 6 },
		{ 0, -9,  8,  1, 9 },
		{ 4,  9, -7,  4, 0 },
		{ 3,  8, -2,  4, 5 }
	};

	int result[ SIZE*2 ], curr, i, index;

	for(i=0; i < SIZE*2; i++) {
		if(i < SIZE) {
			result[i] = arr[i][i]; // Главен диагонал
		}
		else {
			result[i] = arr[SIZE-1-(i-SIZE)][i-SIZE]; // Вторичен
		}

		index = i;
		curr = result[i];

		while((index > 0) && (result[index-1] < curr)) {
			result[index] = result[index-1];
			index--;
		}
		result[index] = curr;
	}
	return 0;
}

Ето и обяснението (в скоби са дадени номерата на редовете, където се извършват съответните действия). Приемаме, че частта, в която се декларират променливите и двумерният масив се инициализира с примерни стойности не се нуждае от обяснение.

За всеки елемент от едномерния масив (17) извличаме стойност от един от диагоналите на двумерния масив и я вписваме. Започваме от главния диагонал (19) и продължаваме с вторичния (22). Разграничаването от кой диагонал кога трябва да четем правим лесно, защото знаем, че половината елементи на едномерния масив съдържат числата от единия диагонал, а другата – от втория.

Веднъж въвели стойността от диагонала, отбелязваме същата и числото, съхранявано в i, съответно в променливите curr (26) и index (25). Ще ги използваме при сортировката.

За подреждането бихме могли да се доверим на всеки прост алгоритъм за сортиране, а аз в конкретния случай реших да ползвам сортиране чрез вмъкване.

Примерът за този алгоритъм, който дава книгата „Основи на алгоритмите“ от Саймън Харис и Джеймс Рос, е за играчи на карти. Държите няколко карти и при раздаване получавате още. Най-вероятно ще ги сортирате именно чрез вмъкване – ще прецените къде се падат измежду другите по боя и стойност и ще ги пъхнете именно там, измествайки останалите надясно – това е и нашият принцип.

Това нещо става в рамките на четири реда (28-32) и изглежда по следния начин:

  1. Предната стойност result[index-1] по-малка ли е от текущата curr и index по-голямо ли е от нула? (28)
  2. Ако да, премести стойността на result[index-1] в result[index] (29) и намали стойността на index с единица (30).
  3. Впиши текущата стойност curr в result на позиция index.

И това е всичко!

Задача 2: Имаме следната рекурсивна редица: х = 5х+7; x0 = 1.
Да се намери средноаритметичното на сумата на елементите, намиращи се между първия по-голям от 1000 и последния по-малък от 10000 елемент от редицата.

I решение – без рекурсия:

#include <iostream>
using namespace std;

int main() {
	int x = 1, // по условие x(0) = 1
		br = 0, sum = 0;

	while((x = 5*x + 7) < 10000) {
		if(x > 1000) {
			sum += x;
			br++;
		}
	}

	if(br)
		cout << "Средноаритметичната стойност е " << sum/br << endl;
	return 0;
}

Подходът тук е много прост. Инициализираме x със стойност 1 и в цикъл с предусловие while за всяка стъпка го преизчисляваме на база предходната му стойност, докато не достигне 10000.

Ако x е по-голямо от 1000 го добавяме към общия сбор и увеличаваме брояча br, който показва колко са елементите, отговарящи на нашите условия.

Накрая извеждаме средната стойност, като предварително сме се защитили от евентуално деление на нула.

II решение – с рекурсия

#include <iostream>
using namespace std;

int x(int n){
	if(!n) return 1;
	return 5*x(n-1) + 7;
}

int main() {
	int element, sum = 0, i = 0, br = 0;

	for(i=0; (element = x(i)) < 10000; i++) {
		if(element > 1000) {
			sum += element;
			br++;
		}
	}
	if(br) cout << "Средноаритметичната стойност е " << sum/br << endl;

	return 0;
}

Нека решим задачата по друг начин.

x(int n) е рекурсивна функция, която в общия случай връща 5x+7, където x е обръщение на функцията сама към себе си, при което стойността на аргумента n е понижена с едно.

В момента, в който n достигне стойност 0, функцията ще върне единица – или стойността на x0 по условие.

В главната функция викаме x за всяко i в рамките на цикъл for, докато резултатът от действието ни удовлотверява, т.е. докато стойността на x е по-малка от 10000. Оттук насетне подходът е същият, като при първото решение.

Какъв е недостатъкът на това решение? За всяко увеличаване на i ще изчислим повторно и x(i-1), и x(i-2), и x(i-n), чак до x(0). В нашия случай i няма да достигне стойност, надвишаваща 5, което значи 28 позовавания на рекурсивната функция.

Представете си какво ще се случи обаче при работа с по-големи стойности…

Може да ви е интересна и задачата от поправителната изпитна сесия.