Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Метод_материалы / Учебники / Программирование_С

.pdf
Скачиваний:
66
Добавлен:
16.03.2016
Размер:
2.31 Mб
Скачать

# define FALSE 0

struct NODE {

data;

NODE * l_heir, * r_heir;

};

typedef NODE * TREE;

TREE IniTree ( TREE pt_tree );

TREE AddLeaveToTree( TREE pt_tree, int new_el );

int FoundInTree( TREE pt_tree, int el );

main()

{

TREE tree; int tree_el;

clrscr();

tree = IniTree( tree );

printf( " Введите целые числа, завершение — 0: \n");

do

{

scanf("%d", &tree_el);

tree = AddLeaveToTree(tree, tree_el);

}

while ( tree_el != 0 );

printf("\Введите целые числа для поиска, завершение — 0:\n");

do

{

scanf("%d", &tree_el);

if ( FoundInTree( tree,tree_el ))

printf("Элемент %d найден в дереве \n",tree_el); else

printf("Элемент %d не найден в дереве \n",tree_el);

}

161

while ( tree_el != 0 );

return 0;

}

TREE IniTree ( TREE pt_tree )

{

pt_tree = NULL; return pt_tree;

}

TREE AddLeaveToTree( TREE pt_tree, int new_el )

{

TREE pt_new, father, son;

pt_new = new NODE; pt_new–>data = new_el;

pt_new–>r_heir = NULL; pt_new–>l_heir = NULL;

if (pt_tree != NULL)

{

father = pt_tree; son = pt_tree;

while ((son != NULL) && (new_el != father–>data))

{

father = son;

if (new_el < father–>data) son = father–>l_heir;

else

son = father–>r_heir;

};

if (new_el != father–>data)

{

if (new_el < father–>data) father–>l_heir = pt_new;

162

else

father–>r_heir = pt_new;

}

else

delete pt_new;

}

else

{

pt_tree = pt_new;

}

return pt_tree;

}

int FoundInTree( TREE pt_tree, int el )

{

int result;

result = FALSE;

while ((pt_tree != NULL) && (result == FALSE)) if (pt_tree–>data == el)

result = TRUE; else

if (el < pt_tree–>data) pt_tree = pt_tree–>l_heir;

else

pt_tree = pt_tree–>r_heir;

return result;

}

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

2.2.13.1. Задачи на деревья бинарного поиска

2.2.13.1.1. Удвоение элементов двоичного дерева, больших заданного Реализовать двоичное дерево, тип данных — целые числа. Прикладная задача:

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

2.2.13.1.2. Удаление символа для элементов двоичного дерева, больших по длине заданной

Реализовать двоичное дерево, тип данных — строки символов. Прикладная задача:

163

исходное дерево формируется из входного потока, затем программа удаляет последний символ для всех элементов, больших по длине заданной.

164

3.ПРИКЛАДНЫЕ АЛГОРИТМЫ НА СТРУКТУРАХ ДАННЫХ

3.1.Основные прикладные алгоритмы

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

3.1.1. Рекурсия

Очень часто в программировании возникает ситуация, когда необходимо провести повторяющиеся действия. До сих пор мы в этих случаях пользовались циклическими конструкциями. Повторение некоторой группы вычислений на основе полученных в предыдущем шаге результатов называют итерационным процессом (это нестрого!). Иначе говоря, до сих пор мы использовали циклы для реализации алгоритмов, заданных через итерационное определение.

Итерационное определение задает некий объект через повторение неких действий над другими объектами. Итерационное определение факториала, например, выглядит так:

Итерационное определение

n! = 1

n = 0

 

 

 

n! = n * (n – 1) * (n – 2) * ... * 1, n > 0

//

суть

итерационного

определения Например,

5! = 5 * 4 * 3 * 2 * 1

Для программной реализации этого определения потребуется простой цикл, перемножающий числа от n до 1.

Рекурсивное определение

n! = 1

n = 0

 

n! = n * (n – 1)!, n > 0

// суть рекурсивного определения

5! = 5 * 4!

4! = 4 * 3!

165

3! = 3 * 2! 2! = 2 * 1!

1! = 1 * 0!

Как видно из этого примера, мы должны спуститься вниз по цепочке рекурсий — на каждом шаге определяя следующий экземпляр объекта через более простой экземпляр. На определенном шаге срабатывает так называемое условие отсечки, заставляющее прекратить движение «вниз» и начать путешествие в обратную сторону; в нашем случае условие отсечки — это достижение ситуации 0!, которая определена в описании алгоритма. Зная значение 0!, можно получить значение 1!, далее, шагнув вверх по цепочке — значение 2! и так далее.

Ниже приводится реализация двух этих определений на языке C.

Итерационное определение:

int FactIter( int n, int * nfact )

{

int ncurr;

if ( n < 0 )

return FALSE; // возвращение ошибки else

{

*nfact = 1; ncurr = n;

while ( ncurr > 0 )

{

*nfact = (*nfact) * ncurr; ncurr = ncurr – 1;

}

return TRUE; // возвращение успеха

}

}

Рекурсивное определение:

int FactRecur( int n ) // нельзя вернуть ошибку

{

if ( n > 0 )

// условие отсечки

return n * FactRecur( n – 1 );

else

 

return 1;

// условие отсечки сработало

}

166

Текст рекурсивного варианта значительно короче (что вообще характерно для рекурсивных алгоритмов); с другой стороны, проверка некорректного (n < 0) входного значения потребовала бы в этом варианте определенных усилий, так как рекурсивная функция в этом примере возвращает не значение корректности завершения ее работы, но необходимый для следующего вызова результат.

Классическим примером применения рекурсии являются процедуры просмотра двоичных деревьев (ранее использованные в разделе 2.2.12). Всего существует три варианта рекурсивного просмотра (все начинающиеся с корня дерева):

1) просмотр в прямом порядке

(PreOrder)

посетить корень поддерева;

 

просмотреть левое поддерево;

просмотреть правое поддерево;

 

2) просмотр в симметричном порядке

(InOrder)

просмотреть левое поддерево;

 

посетить корень поддерева;

 

просмотреть правое поддерево;

 

3) просмотр в обратном порядке

(PostOrder)

просмотреть левое поддерево;

 

просмотреть правое поддерево;

 

посетить корень поддерева.

 

Ясно, что просмотр некоторого поддерева и есть рекурсивное обращение к процедуре просмотра дерева. На рис.2.16 изображено двоичное дерево (а точнее, дерево двоичного поиска), для которого порядок обхода узлов дерева для всех трех вариантов следующий:

1)6-4-0-5-9-8

2)0-4-5-6-8-9

3)0-5-4-8-9-6

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

Часто используются так называемые рекурсивные цепи.

Рекурсивный алгоритм может не обращаться к самому себе непосредственно — он может обращаться к себе косвенно, через другой алгоритм

(рис.3.1):

Здесь алгоритм a обращается к алгоритму b, который в свою очередь обращается к а, который может опять обратиться к b. Таким образом, и a и b являются рекурсивными алгоритмами, поскольку они косвенно обращаются сами

167

к себе. Рекурсивность здесь не очевидна. Алгоритм a обращается к другому алгоритму b, и при анализе алгоритма a изолированно от алгоритма b невозможно определить, что он будет косвенно обращаться сам к себе.

алгоритм a

алгоритм b

__________

___________

__________

___________

__________

___________

обращение к алгоритму b

 

___________

 

обращение к алгоритму a

__________

___________

__________

___________

конец алгоритма

a конец алгоритма b

Рис. 3.1. Косвенная рекурсия

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

3.1.1.1.Задачи на рекурсию

3.1.1.1.1.Рекурсивное определение алгебраических выражений

Приведем пример такой рекурсивной цепи алгоритмов. Рассмотрим следующую рекурсивную группу определений.

1.Выражение является некоторым термом, за которым следуют знак плюс и терм, или одним термом.

2.Терм является некоторым множителем, за которым следуют звездочка и множитель, или одним множителем.

3.Множитель является или некоторой буквой, или некоторым выражением, заключенным в скобки.

Прежде чем рассматривать какие-либо примеры, отметим, что никакой из трех приведенных выше элементов не определяется непосредственно в терминах самого себя. Однако каждый из них определяется в терминах самого себя косвенным образом. Выражение определяется в терминах некоторого терма, терм определяется в терминах некоторого множителя, а множитель определяется в терминах некоторого выражения. Аналогичным образом множитель определяется

втерминах некоторого выражения, которое определяется в терминах некоторого терма, который определяется в терминах некоторого множителя. Таким образом, весь набор определений составляет некую рекурсивную цепь.

Примеры. Простейшей формой множителя является буква. Таким образом, буквы А–Z являются множителями. Они также являются термами, поскольку некоторый терм может быть одним множителем. Они также являются выражениями, поскольку выражение может быть одним термом. Поскольку А является некоторым выражением, то (А) является множителем и, следовательно,

168

термом так же, как и выражением; А + В является примером некоторого выражения, которое не является ни термом, ни множителем; (А+В), однако, является всеми тремя; А*В является термом и, следовательно, некоторым выражением, но не множителем; А*В + С является выражением, но не является ни термом, ни множителем; А*(В – С) является термом и выражением, но не множителем.

Строка А + *В, однако, не является ни выражением, ни термом, ни множителем в соответствии с вышеприведенными определениями.

Напишем некоторый алгоритм, который читает строку символов, печатает эту строку и затем печатает «верно», если она является правильным выражением, и «неверно» — в противном случае. Мы будем использовать три отдельные функции для распознавания соответственно выражений, термов и множителей. Сначала представим набросок на псевдокоде для вспомогательной функции getsym, которая имеет два входных параметра — str и pos. Параметр str содержит входную строку символов, а параметр pos является позицией в str текущего обрабатываемого символа. При входе в функцию getsym параметр pos сравнивается с длиной данной строки. Если pos <= strlen(str), то функция getsym возвращает символ из str в позиции pos и pos увеличивается на 1. Если pos> strlen (str), то функция getsym возвращает пробел.

char getsym (str, pos)

{

if pos>len(str) return ‘ ‘;

else

{

// взять символ из строки в позиции pos pos = pos+1;

return // символ

}

}

Функцию, распознающую выражения, назовем ехрress. Eе входные параметры — также str и pos. Функция ехрress возвращает значение TRUE, если в позиции pos в строке str начинается некоторое выражение, в противном случае она выдаст FALSE. Она также переустанавливает параметр pos в позицию, следующую за самым длинным выражением, которое она может найти. Функции factor и term очень похожи на функцию ехрress, за исключением того, что они заняты распознаванием соответственно множителей и термов. Они также перемещают pos в позицию, следующую за самым длинным множителем или термом внутри строки str, которые они могут найти. Псевдокод главной функции может быть таким:

// читать строку str

169

// печатать строку str pos=1;

ok= ехрress (str, pos);

if ( ok == TRUE and pos > strlen(str) )

{

printf(“верно \n”); return TRUE;

}

else

{

printf(“неверно \n”); return FALSE;

};

/*данное условие может быть не выполнено по одной из двух причин (или обеим причинам): если ok равно FALSE, то нет верного выражения, начиная с позиции pos; если pos<= strlen (str), то может быть некоторое верное выражение, начиная с начала строки str, но оно не занимает всю строку */

Приведем теперь более детальный псевдокод для функций ехрress, term и factor. Каждая из этих функций возвращает либо TRUE, либо FALSE.

int express (char *str, int pos)

{

// поиск некоторого терма ok = term(str, pos);

if (ok == FALSE) return FALSE;

//проверка следующего символа с = getsym(str, pos);

if ( c != ’+‘ )

/*найдено самое длинное выражение (единственный терм) и передвигаем переменную pos так, что она указывает на позицию, следующую за данным выражением */

pos = pos – 1; return TRUE;

}

//найден некоторый терм и знак плюс, нужно искать другой терм ok= term (str, pos);

if (ok == TRUE) return TRUE;

else

return FALSE;

170

Соседние файлы в папке Учебники