580 likes | 771 Views
Рекурсия. Рекурсивные алгоритмы. Рекурсивные типы данных. Рекурсивные алгоритмы. Определение и примеры. Объект называется рекурсивным , если он содержит сам себя или определен с помощью самого себя. < Имя > ::= < Буква >|< Имя >< Буква >|< Имя >< Цифра > Дерево это О – пустое дерево
E N D
Рекурсия Рекурсивные алгоритмы. Рекурсивные типы данных.
Определение и примеры • Объект называется рекурсивным, если он содержит сам себя или определен с помощью самого себя. • <Имя>::=<Буква>|<Имя><Буква>|<Имя><Цифра> • Дерево это • О – пустое дерево • Если T1и T2 – деревья, тотоже есть дерево. • Факториал:
Формальное представление • Пусть: P – рекурсивная процедура; Si – базовые операторы, не содержащие P; P – композиция операторов; • Тогда: P = P[Si,P] • Средством создания рекурсивных программ является описание функций (процедур, под-программ), т.к. оно позволяет присвоить оператору имя, с помощью которого он может быть вызван.
Классификация Прямо рекурсивная Косвенно рекурсивная P = P[Si,Q] Q = Q[Si,P] • P = P[Si,P] Набор локальных объектов, определенных внутри рекурсивной функции, создается заново при каждом вызове такой функции. Идентификаторы всегда ссылаются на множество переменных, созданное последним.
N.B. • Проблема окончания работы. P = P[Si,if!B then P] Наиболее надежный способ окончания работы – связать с Р параметр n и рекурсивно вызывать Р с параметром n-1: P(n) = P[Si,if n>0 then P(n-1)] • Глубина рекурсии. Следует убедиться, что она не только конечна, но и достаточно мала.
Поиск пути в лабиринте Пример рекурсивной программы
Вид файла данных Первая строка: N – количество сток M – количество символовв строке Вторая строка и далее: N строк, со словами из алфавита {#,.,E} . – проходимая клетка # - непроходимая клетка Е – конечная клетка • 20 24 • ........................# • ..................#...#. • .................#...#.. • ................#...#... • ...............#...#.... • ..............#...#..... • .............#...#...... • ............#...#....... • ...........#...#........ • ..........#...#......... • .........#...#.......... • ........#...#........... • .......#...#............ • ......#...#............. • .....#...#.............. • ....#...#............... • ...#...#................ • ..#...#................. • .#...#.................E • #........................
Файлы заголовков и глобальные переменные #include <iostream.h> #include <stdio.h> int N,M,F=0,X,Y; char** A; int** B;
Создание динамических массивов void array(int n,int m) { A = new char*[n];// план лабиринта B = new int*[n];// ранг полей лабиринта while (n--) { A[n] = new char[m]; B[n] = new int [m]; } }
Рекурсивное построение пути void next(intn,intm,int l) { if( n<0 || m<0 || n>=N || m>=M) return; if( A[n][m]=='.' && (!B[n][m] || l<B[n][m]) ) { B[n][m]=l; next(n-1,m,l+1); next(n,m+1,l+1); next(n+1,m,l+1); next(n,m-1,l+1); } else if( A[n][m]=='E' && (!B[n][m] || l<B[n][m]) ) { B[n][m]=l; F++; X=n; Y=m; } return; }
Основная программа void main(intargv, char** argn){ FILE *pole; int n=0,m; if ((pole = fopen(argn[1], "r")) == NULL){ fprintf(stderr, "Cannot open input file .\n"); return;} fscanf(pole,"%d %d",&N,&M); array(N,M); while (!feof(pole)) fscanf(pole,"%s",A[n++]); fclose(pole); if(n!=N) { fprintf(stderr,"Error in data file\n"); return; }
Основная программа for(n=0;n<N;n++) for(m=0;m<M;m++) B[n][m]=0; next(0,0,1); if(!F) cout << "No way\n "; elseprint_way(X,Y,B[X][Y]); for(n=0;n<N;n++) cout << A[n] << endl; } Демонстрация
Печать найденного пути void print_way(intn,intm,int l) { cout << n << ' ' << m << endl; A[n][m]=‘*'; if(!--l) return; if(n && B[n-1][m]==l) n--; else if(n+1<N && B[n+1][m]==l) n++; else if(m && B[n][m-1]==l) m--; else m++; print_way(n,m,l); }
Или о том, что рекурсию следует использовать весьма осмотрительно… Числа фибоначчи
Вычисление чисел Фибоначчи • Итеративный long F(int n) { long a=1,b=1; do{a+=b;b=a-b;} while(--n>2); return a; } • Рекурсивный long F(int n){ return n<3?1:F(n-1)+F(n-2); } void main() {int n; cout << "Input n="; cin >> n; start = clock(); cout << " F(" << n << ")= " << F(n) << '\t'; end = clock(); cout << “Time was: " << (end - start) / CLK_TCK << " s\n";} F(40)= 102334155Time was: 0 s F(45)= 1134903170The time was: 0 s F(40)= 102334155 Time was: 5.10989 s F(45)= 1134903170Time was: 55.714286 s
Или о том, когда рекурсию не следует использовать вообще… Функция Аккермана
Функция Аккермана #include <iostream.h> int A(intm,int n) {return !m?n+1: !n?A(m-1,1): A(m-1,A(m,n-1)); } void main() {intn,m; cout << "Input m="; cin >> m;cout << "Input n="; cin >> n; cout << "A(" << m << ',' << n << «)= " << A(m,n) << endl;}
Дерево вычисления функции А(2,2) А(2,2)=7 Количество вершин дерева - 27 Демонстрация
Линейные наборы данных Древовидные структуры Динамические информационные структуры
Указатели • Динамическое распределение памяти – выделение памяти для отдельных компонент в тот момент, во время выполнения программы, когда они появляются. • Компилятор во время трансляции выделяет фиксированный объём памяти для хранения адреса (указатель) динамически размеща-емой компоненты.
ЛИНЕЙНЫЕ НАБОРЫ ДАННЫХ • Каждый элемент содержит только одну ссылку, связывающую его со следующим элементом списка: template <class T> struct Item {T n;Item* next; }; • Указатель на список: Item<Myclass> *top; • <ЛНД> ::= Ø| Item | Item <ЛНД>
СПИСОК, ОЧЕРЕДЬ, СТЕК • Очередь FIFO – First In First Out • Стек LIFO – Last In First Out • Список Casual In Casual Out
Организация стека на основе массива #include <iostream.h> #include <stdlib.h> void ERR(char c) { switch (c) { case 'f': cout << "Steak is Full!\n"; break; case 'e': cout << "Steak is Empty\n"; break; default: cerr << "E R R O R case !\n";} exit(1); }
Описание шаблона типа данных template <class T> class steak { int N, sp; T *a; public: steak(int size) { a=new T[N=size]; sp=0; } int full() { return sp==N-1; } int empty() { return !sp;} void Push(T b) { if( full() ) ERR('f'); a[sp++]=b; } void Push(T* b) { while(*b) Push(*b++); } T Pop(void) { if( empty() ) ERR('e'); return a[--sp]; } ~steak() { delete a; }};
Организация очереди на основе массива template <class T> class queue { intN,qp,k; T *a; public: queue(int R) { a=new T[N=R]; qp=N-5; k=0; } int full() { return k==N; } int empty() { return !k;} void Put(T b) { if( full() ) ERR('f'); k++; a[qp++]=b; if(qp==N) qp=0; } void Put(T* b) { while(*b) Put(*b++); } T Get(void) { if( empty() ) ERR('e'); return a[qp+(qp<k?N:0)-k--]; } ~queue() { delete a; } };
Пример void main() { steak<char> A(100); steak<int> B(50); queue<char> C(15); queue<int> D(15); char *a="Hello world!"; int b[]={1,2,3,4,5,6,7,8,9,0}; A.Push(a); B.Push(b); C.Put(a); D.Put(b); while(!A.empty()) cout <<A.Pop(); cout << endl; while(!B.empty()) cout <<B.Pop(); cout << endl; while(!C.empty()) cout <<C.Get(); cout << endl; while(!D.empty()) cout <<D.Get(); cout << endl;}
Организация стека на основе использования указателей template <class T> struct Item { T n; Item* next; }; template <class T> class steak { Item<T> *Top; public: steak() { Top=NULL; } void push(T k) { Item<T> *ptr=new Item<T>; ptr->n=k;ptr->next=Top; Top=ptr; } T pop() { if(!Top) { cerr << "Steak is empty\n"; exit(1); } Item<T> k=*Top; delete (Top); Top=k.next; return k.n;} int empty() { return Top==NULL?1:0; }};
Организация очереди на основе использования указателей • Top template <class T> class queue { Item<T> *Top,*Bot; public: queue() { Top=Bot=NULL; } void push(T k) { if(Bot!=NULL) { Bot->next=new Item<T>; Bot=Bot->next; } else Top=Bot=new Item<T>; Bot->next=NULL; Bot->n=k; } T pop() { if(Top==NULL) {cerr << "Steak is empty\n"; exit(1); } Item<T> k=*Top; delete (Top); Top=k.next; if(Top==NULL) Bot=NULL; return k.n;} int empty() { return Top==NULL?1:0; } }; • 1 • 2 • 3 • Bot • 3 • Bot
Включение в списокнового элемента • После текущего Item<…> *q = new Item<…>; q->n = … q->next = p->next; p->next = q; • Перед текущим Item<…> *g = new Item <…>; *g = *p; p->next = g; p->n = …
Исключение элемента из списка • Следующего за текущим p->next=p->next->next; • Текущего *p=*p->next;
Организация списка template <class T> class list { Item<T> *Top; public: list() { Top=NULL; } Item<T>* find(Item<T>* &F, T k) { if(Top==NULL) return (F=NULL); Item<T> *ptr=F=Top; if(Top->n==k) return NULL; while((F=ptr->next)!=NULL) { if(F->n==k) break; ptr=F; } return ptr; } void put(T k,T n = 0) { Item<T> *b,*c; b=find(c,n); if(b==NULL) { Top=new Item<T>; Top->n=k; Top->next=c; } else { b->next=new Item<T>; b->next->n=k; b->next->next=c;}} void del(T k) { Item<T> *b,*c; b=find(c,k); if(c==NULL) return; if(b==NULL) { Top=Top->next; delete (c); } else { b->next=c->next; delete(c); }} void print() { Item<T> *ptr=Top; while(ptr) { cout << ptr->n << '\t'; ptr=ptr->next; }cout << '\n'; } };
Пример void main() { steak<char> A; queue<char> B; list<int> C; char* a="Hello world!\n"; while(*a) { B.push(*a); A.push(*a++); } while(!A.empty()) cout << A.pop(); cout << endl; while(!B.empty()) cout << B.pop(); cout << endl; C.put(0); for(inti=1;i<4;i++) { C.put(i); C.put(i+3,100); } C.del(6); C.del(1); C.del(0); C.print();}
Древовидные структуры • Дерево ::= Ø|Item ¤ *Item ¤ … ¤ *Item • Список – вырожденное дерево. • Терминология: • А – корень дерева; • С предок G или G потомок C; • Корень на уровне 1; • Максимальный уровень – это высота или глубина дерева • Число ребер, которые нужно пройти от корня до узла x, называют длиной пути к x.
Древовидные структуры Корень имеет длину пути 1, его непосредственные потомки – 2, узлы на уровне i имеют длину пути i. • Элементы D,E,F,G,H – терминальные узлы. Не терминальные элементы называются внутренними узлами. • Число непосредственных потомков внутреннего узла называют его степенью. • Степень дерева – максимальная степень среди всех его узлов.
Бинарные деревья Struct Item {int n; Item*left, *right;}; • Построение дерева с n узлами минимальной высоты. • Взять один узел в качестве корня дерева; • Построить левое поддерево с nl=n/2 узлами; • Построить правое поддерево с nr=n-nl-1 узлами. • Item* tree(int n) { Item *ptr; intnl=n/2, nr=n-nl-1; if(!n) return null; ptr=new(Item); ptr->n=rand(); ptr->left=tree(nl); ptr->right=tree(nr); return ptr; }
Бинарное дерево • Печать бинарного дерева void printtree(Item* ptr,int n=0) { if(!ptr) return; printtree(ptr->right,n+1); for(inti=0;i<n;i++) cout << '\t'; cout << ptr->n << endl; printtree(ptr->left,n+1); }
Бинарное дерево • Обход дерева: • Сверху вниз: T,L,R • Слева направо: L,T,R • Снизу вверх: L,R,T • Дерево поиска: NL<NT<NR Здесь NL – все ключи левого поддерева, NR – все ключи правого поддерева • T • L • R
(a+b/c)*(d-e*f) TLR: *+a/bc-d*ef – префиксная запись LTR: a+b/c*d-e*f – инфиксная запись LRT: abc/+def*-* - постфисная запись
Бинарное дерево • Поик по ключу Item* loc(Item* ptr, int key) { while( ptr ) if(ptr->n==key) return ptr; else if(ptr->n>key) ptr=ptr->left; else ptr=ptr->right; return NULL; } NB: Чем меньше высота дерева – тем быстрее поиск.
Бинарное дерево • Идеально сбалансированное дерево – это дерево, у которого для каждого его узла количества узлов в левом и правом поддеревьях различаются не более чем на 1.
Идеально сбалансированное дерево
Бинарное дерево • Идеально сбалансированное дерево – это дерево, у которого для каждого его узла количества узлов в левом и правом поддеревьях различаются не более чем на 1. • Сбалансированное дерево – это дерево, у которого для каждого его узла высоты левого и правого поддеревьев различаются не более чем на 1.Другое их название - АВЛ-деревья(по фамилиям их изобретателей Адельсон-Вельский и Ландис). hИСД ≤h≤hДФ
Теорема Адельсон-Вельского и Ландиса log(n+1) ≤ h(n) < 1,4404 * log(n+2) – 0,328 Построение АВЛ дерева высоты h с минимальным количеством вершин T0 – пустое дерево T1 – дерево, состоящее из одной вершины T2 = T1 ¤ T0
Деревья Фибоначчи Nh= Nh-1+ Nh-2+1; N0=0; N1=1 T4 T3 T2
Включение левого корня левого поддерева
Включение правого корня левого поддерева
Построение АВЛ дерева #include <iostream.h> int h=0; struct Item { int n; char b; Item *left, *right; }; void printtree(Item *ptr, inti=0) { if(ptr==NULL) return; printtree(ptr->right,i+1); for(int k=0;k<i;k++) cout << '\t'; cout << ptr->n << '(' << int(ptr->b) << ')' << endl; printtree(ptr->left,i+1); }