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

алгоритмы1

.pdf
Скачиваний:
9
Добавлен:
10.06.2015
Размер:
1.34 Mб
Скачать

27. Составной оператор (блок). Условный оператор. Оператор перехода goto и помеченный оператор. Оператор цикла с параметром (оператор for)

Блок:

begin

x:=1;

y:=2;

end;

Условный оператор:

if ptr E=nil then x:=1 else x:=2;

После служебных слов then и else можно размещать по 1-му оператору, если надо совершить много действий, то следует оформить как блок goto и помеченный оператор:

(пример оператора перехода в составе условного оператора)

if x<=3 then goto 23;

23:y:=5; // помеченный оператор

В псевдокоде в качестве меток могут использоваться целые не отрицательные числа или идентификаторы.

Оператор с параметром (for):

Оператор начинается с for, после которого с помощью операторов присваивания задаются стартовые значения параметрам цикла, после to задаются конечные значения параметров, после do ставится тело цикла (1 оператор). При выполнении каждой итерации соответствующей параметрам цикла, значения параметров передаются от стартового до конечного, после цикл завершает работу.

Пример:

z:=0;

for i:=2 to n begin

mas [i] := i*x/y;

z := z+i*y;

end;

28. Оператор цикла с предусловием (оператор while). Оператор цикла с постусловием (оператор repeat). Оператор варианта (оператор case).

Оператор с предусловием (while):

Оператор начинается со служебного слова while, после записывается логическое выражение (по смыслу условия продолжения цикла и если false, то цикл завершается), после do ставится тело цикла (1 оператор).

Пример:

var

c: char;

c:=0; // стартовое значение счетчика итераций

while (i<=maxsize) and ((x/y)<i) do begin

i:=i+1;

mas1[1]:=x*i;

Dmas[i+1, c] :=r+I;

end;

Оператор с постусловием (repeat):

Начинается с repeat, после которого записывается последовательность операторов (тело цикла), после идет until, после котрого записывается логическое выражение логики работы цикла: в начале тело цикла → вычисление логического значения, если false, то новая итерация, иначе конец цикла.

Пример:

var

Ri:real;

DEL:real = 1.0e^(-7); // объявляем вещественные переменные

i:=0; y:=0; x:=0; // начальные значения переменных

repeal

y:=y+x;

i:=i+1;

x:=x*(z/i);

Ri:=y*x;

until Ri<DEL;

Оператор варианта (case):

Аналогичным в С++ является переключатель svich.

Оператор начинается со служебного слова case, после которого записывается «ключ выбора» - это выражения порядкового типа (целочисленное выражение), далее после of идет список выбора, состоящий из элементов выбора, каждый элемент включает в себя 1-ну или несколько меток выбора, после каждой метки ставится «:». В качестве меток должны использоваться те же типы, что и ключи, после меток в каждом элементе ставится 1-н оператор и после «;», далее идет else, после которого задается список операторов через «;». Оператор заканчивается «end;». Допустимо сокращение без

«else».

Логика работы: вычисляется ключ выбора и управление передается тому оператору, который помечен меткой совпадающей по значению с ключом выбора, после выполнения работа оператора варианта в целом заканчивается, если значение ключа не совпало ни с одной меткой, то выполняется список операторов после else, а если else нет, то оператор прекращает работу.

Пример:

До входа некоторой переменной нужно стартовое значение (i), в примере оператор с 3-мя ветвями и дополнительная ветвь else

case (i div 5) of

1: j:=1;

3, 4, 9: j:=1000;

else j:=0, x:=0,99;

end;

29. Функции и процедуры в псевдокодах

Объявляя функции включают заголовок и тело, заголовок открывается function, после которого указывается через пробел имя функции, в ( ) указывается список формальных параметров → «:» тип возвращаемого значения «;».

В списке формальных параметров могут находится секции 3-х видов:

1.Параметры переменной:

Начинается с var

2.Параметры константы

3.Параметры значения:

Никакого слова в начале нет Внутри каждой секции приводится список именованных формальных параметров через «,», а

после «:» тип параметров данной секции, это может быть любой тип, но задавать его нужно именем, т.е тип должен быть объявлен до объявления функции.

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

Поясним назначение параметров разных видов, опираясь на аналог C++.

1.Параметры переменной передаются по адресу

2.Параметры значения передаются по значению

3.Параметры константы отличаются тем, что в теле функции их значения менять запрещается

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

Кроме того результат может быть возвращен через параметры переменных.

Тело функции включающее объявление локальных конструкций в частности может быть пустым.

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

Пусть функция имеет имя f1, тогда возможны следующие формы указанных операторов:

-f1:= <выражение>;

-Result := <выражение>; // тут используется имя переменной Result, которое подстраивает свой тип под тип возвращаемого результата

-return (<выражение>);

В теле функции может быть несколько операторов указанных типов. Функция прекращает работу в следующих случаях:

1.При выполнении return

2.При выполнении exit

3.При достижении end закрывается блок тела функции

Пример:

function f1 (var g, n: integer; const r:char; a, b: Double): DMO;

type

var …

labl …

procedure …

function …

begin …

end

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

Процедура – это функция не возвращающая результат, в тоже время результат может быть передан во внешнюю среду через параметры вместо function исп procedura. Список формальных параметров оформлен по тем же правилам, что и у функции.

Пример заголовка:

Procedure P1 (var g, w: integer; const r:char; a, b: double);

Тело оформлено по тем же правилам и имеет ту же семантику, что и тело функции, единственное отличие, что в теле процедуры не должно быть оператора вида:

P1 := <выраженим>;

Result: <выражение>;

return (<выражение>);

Соответственно процедура остается при exit и при достижении end, закрывающим тело процедуры.

30.Бинарное дерево поиска. Сортировка элементов, хранящихся в бинарном дереве поиска.

Будем отталкиваться от простой модели мира, а именно от множества однотипных элементов. Будем полагать, что в этом множестве задан линейный порядок «≤» - предщшествования. Рассмотрим бинарное дерево поиска и такие алгоритмы на нем, как DELETE, MEMBER (проверка принадлежности), MIN (поиск минимального значения).

Бинарным деревом поиска называют дерево, все вершины которого упорядочены, каждая вершина имеет не более двух потомков (назовём их левым и правым), и все вершины, кроме корня, имеют родителя. Вершины, не имеющие потомков, называются листами.

Рис. 4.1

Рассмотрим случай k-арного дерева:

-если дерево Т является пустым, то в списке обхода заносится пустая запись

-если дерево состоит из 1-го уза, то в список заносится этот узел

-если дерево Т состоит из узла n, то Т1, Т2, … , Тn

Рис. 4.2

то посещают все узлы поддерева Т1, после этого переходят в корень n, а далее последовательно в симметричном порядке посещают поддеревья Т2, Тn.

Если такой обход применен к бинарному дереву поиска, то мы получим упорядоченный набор хранилища в дереве.

Работу рассмотрим на примере бинарного дерева поиска изображенного на рисунке 4.1

В соответствии с правилами симметрии обхода сначала надо рассмотреть поддерево Т1.

Рис. 4.3

У Т1 есть 2 поддерева, левое поддерево пустое, а правое с одной вершиной. Т.к в левом нет вершин, то первым в списке появится «6». А далее появится единственный подузел Т1,2 «7», после включают корень исходного дерева, т.е «10».

Рис. 4.4

В конечном счете получаем: 6, 7, 10, 11, 12, 13, 14.

31. Оператор проверки принадлежности элемента множеству. Оператор вставки элемента в бинарное дерево поиска.

MEMBER (x, A); x – элемент, А – множество, как дерево поиска. Мы рассмотрим этот алгоритм внутри INSERT и определим его как рекурсивную функцию.

Мы будем обозначать символом Element Type и будем полагать, что на множестве этого типа определено отношение «=» и «<», «>».

type

Node Type = record

element: Element Type;

Left Child, Right Child: ^Node Type;

end;

Программным образом дерево будет указатель следующего типа:

var

A: SET

type

SET =^Node Type;

Указатель будет адресовываться к корню дерева. Соответственно оператор создания пустого дерева сведется к виду:

A:=nil;

Псевдокод функции MEMBER:

function MEMBER (x: Element Type; A: SET): Boolean;

begin:

if A=nil then return (false)

else if x=A^.element then return (true)

else if x<A^.element then return (MEMBER(x, A^.LeftChild))

else return (MEMBER (x, A^.RightChild));

end;

Оператор вставки элемента в бинарное дерево поиска:

INSERT (x, A)

Определяется как рекурсивная процедура. Тут дадим алгоритм, который является аналогм операции new в С++. В нашем примере этот оператор будет выполнять область для хранения объектов типа Node Type, а адрес этой области будет передаваться оператору, который является элементом оператора new.

procedure INSERT (x: Element Type; var A:SET);

begin

if A=nil then begin

new(A); //создаем корень дерева

A^.element := x; // A^. – разименование

A^.LeftChild:=nil;

A^.RightChild:=nil;

else if “x<A^.element then INSERT (x, A^.LeftChild)

end;

// если x=A^.element, то никакие действия не осуществятся, т.к x уже хранится в дереве.

32.Оператор удаления элемента из бинарного дерева

DELETE (x, A) – удаление элемента x из множества A.

Определим как рекурсивную процедуру: если лист находится в узле, который является листом, то этот узел просто удаляется, в противном случае его нельзя просто удалить, т.к разрушиться связность дерева. Обозначим эту ситуацию inoude, если узел inode имеет только одного сына, как узел (6) на рис. 4.1, то узел inode можно заменить сыном, если узел inode имеет 2-х сыновей, как узел (12) на рис. 4.1, то среди потомков правого сына нужно найти наименьший элемент и заменить им удаленный элемент (каждый узел одновременно является потомком самого себя).

Можно действовать по другому: нужно найти наибольший среди потомков левого сына, таким образом при разработке рекурсивного DELETE нужно иметь рекурсивную функцию DELETE MIN, которая будет удалять минимальный элемент из каждого НЕ пустого дерева => и поддерева, после чего соответствующим образом меняя дерево или поддерево.

function DELETEMIN (var A:SET): Element Type;

if A^.LeftChild = nil then // равенство означает, что А указывает на наименьший элемент (1.1)

begin (1.2)

DELETEMIN = A^.element; (1.3)

A:=A^.RightChild; // замена узла, на который указывало А, его правым сыном, если правого сына нет, то А получает nil, таким образом узел с наименьшим элементом будет удален из множества, однако следует учитывать, что из динамической кучи соответствующей области памяти удален не будет, в реальной программе надо позаботится о таком удалении (1.4)

end (1.5)

else DELETEMIN:=DELETEMIN (A^.LeftChild); (1.6)

procedure DELETE (x: Element Type; var A:SET);

begin

if A< > nil then (2.1)

if x<A^.element then (2.2)

DELETE (x, A^.LeftChild) (2.3)

else if x<A^.element then (2.4)

DELETE (x, A^.RightChild) (2.5)

else if (A^.LeftChild = nil) end (A^.RightChild = nil) then // у узла нет сыновей (2.6)

A:=nil // удаление листов содержащих элемент x (2.7)

else if A^.LeftChild = nil then (2.8)

A^.RightChild (2.9)

else if A^.RightChild = nil then (2.10)

A:=A^.LeftChild (2.11)

else // у узла есть оба сына (2.12)

A^.element:=DELRTMIN (A^.RightChild); (2.13)

end;

Пример:

Пусть из дерева рис. 4.1 необходимо удалить элемент 10(находящийся в корне)

Первым будем использовать оператор вызова функции DELETMIN с элементом указывающим на узел (12). Т.к DELETMIN – рекурсивная функция, то в ней реализуется еще 1 вызов DELETMIN на узел (11). Узел (11) не имеет левого сына поэтому DELETMIN возвращает (11) и устанавливает указатель на (12) на левого сына равным nil. После этого заменим (10) на число (11) возвращаемое nil.

В результате мы получим дерево следующего вида:

33. Оценка эффективности алгоритмов на бинарном дереве поиска.

Рассмотрим 2 случая:

1-й пусть бинарное дерево полное, тогда не существует пути содержащего более 1+log2(n) при движении от корня к любому листу => операторы INSERT, MEMBER, DELETE, DELETEMIN имеют время выполнения порядка О(log2(n)), это означает, что если n будет расти, то время будет расти пропорционально log n.

2-й крайний случай соответствует, когда при заполнении дерева элементы поступаю, например, в порядке возрастания, тогда дерево вырождается в цепочку. В том случае оператор указывает выше время выполнения (в худшем случае О(n)). При заполнении такого дерева на вставку

потребуется время такого порядка О(i), а все действия при заполнении n элементами:

=1 = (n*(n+1))/2 => О(n2)

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

Рассмотрим каков в этом случае порядок времени выполнения основных операторов, введем функцию, аргументы которой являются количеством n элементов в хранилище P(n), возвращая среднее число узлов, которое нужно пройти двигаясь от корня дт какого-то листа.

Составим рекурсивное выражение для функции P(n). Если дерево пустое, то P(0) = 0, если n=1, то Р(1) = 1. Пусть есть n≥2, и пусть 1-м на выход будет элемент «а». Если исходный список отсортировать, то «а» может оказаться на любом месте. Пусть в упорядоченном списке i<a элементов и соответственно (n-i-1)>a, тогда при построении дерева в корне будет элемент «а», т.к он первый перешел, i – потомки левого сына, а (n-i-1) элементов – потомки правого сына.

Рис. 4.6

Будем считать, что «а» с равной вероятностью может оказаться на любом месте в упорядоченном списке, т.к любая последовательность i меньших элементов и (n-i-1) больших элементов равновероятны, то среднюю длину путей и в левом, и в правым поддеревьях. Если вершина к исходному дереву, то вычисляя пути от «а» к любому листу мы значениям P(i) и P(n-i-1) должны добавить 1.

P(i)+1 в левой части на рис. 4.6 средняя длина P(i)+1, а в правой P(n-i-1)+1. Отсюда среднюю P(n) можно представить как среднее значение в левой и правой частях дерева рис. 4.6 i/n(P(i)+1)+((n- i-1)/n)*(P(n-i-1)+1). Чтобы окончательно получить среднее значение мы должны передать все n от 0

до (n-1). И мы получим:

1)

(-//-

( 1) 1

1/ ^2

=0 ( )

 

=0 ( ( )

P(n) =

 

 

 

− −1

 

−1

−1

( Возьмем1) ( вторую1часть) 1суммы

//-//-) (4.1)

 

 

 

из 4.1 и запишем ее отдельно