Методическое пособие для подготовки к государственному экзамену по информатике для студентов специальности «Прикладная математика»



бет4/5
Дата28.06.2016
өлшемі418 Kb.
#162858
түріМетодическое пособие
1   2   3   4   5
§10. Алгоритмы полного перебора.
Пусть T - множество значений некоторого порядкового типа, T={Первый<Второй<…Последний}, succ - соответствующая функция следования, а Seq(L)=Seq(T,L)=[1..L]T - множество всех последовательностей длины L.

Лексикографический, или словарный порядок на Seq(L) определяется следующим образом. Пусть a,b  Seq(n), ab и N=N(a,b)=min {n: a(n) b(n)}- наименьший номер i, на котором они различаются. Тогда a считается меньшей b, если у а на N-ом месте стоит меньшая (в смысле порядка на T) компонента, чем у b: aT b(N).
Пример. T=кириллица (русский алфавит), последовательности символов - слова. Тогда 'шабаш'< 'шалаш' ( в Seq(T,5)).

Алгоритм определения следующей последовательности.

Следующая(a)=b  найдется число N такое, что для всех i, i  [1..N], b(i)=a(i), b(N)=succ(a(N)) и b(j)=Первый, для всех j, j  [N+1..L]. Причем, нетрудно заметить (и доказать), что N в этом случае определяется однозначно - N=max{i:a(i) Последний}.

Идея вычисления функции Следующая:

a) Вынимай из конца последовательности а все последние (в T) значения, пока не найдешь меньшего значения с. Если такого значения с нет, то а - это последняя последовательность.

б) Положи в конец a значение succ(c)

в) Доложи в конец последовательности необходимое число первых значений.


Пример. Построим следующее за 00011 слово (в Seq({0<1},5) - после шага a) получаем 00, после б) 001, после в) - 00100.
Обработка последовательностей "с одного конца", как мы помним, реализуется в терминах стеков (см. "Абстрактные линейные типы").
{exists,a:= b (b=Следующая(a)), Следующая(a)}

{ясно, exists= j [1..LenA] (a(j)<>Последний}

{pa - ссылка на содержимое стека, содержащего последовательность а}

{LenA - константа, длина последовательности}

procedure Следующая(pa:pSeq; exists:boolean);

var


i:integer; {число вынутых компонент}

x:T; {значение последней компоненты}

begin

i:=0; exists:=false;



{a} while (i<=LenA) and not found do

begin pop(pa,c); i:=i+1; if c<>Последний then exists:=true end;

if exists

then begin {b} push(pa,succ(c));

{c} while i<>0 do begin push(pa,Первый);i:=i-1 end

end;
Любую задачу с конечным числом вариантов решений можно представить как поиск пути в некотором графе из некоторого множества инициальных (исходных, «данных») в некоторое множество финальных (конечных, желаемых). Перечисление последовательностей дает далеко не эффективный, но универсальный алгоритм решения таких конечных задач полным перебором всевозможных путей решения.


Задача о раскраске графа. Найти ("распечатать") все правильные раскраски произвольного графа с фиксированным числом вершин n (если таковые найдутся) в m цветов. Раскраска правильная, если никакие две соседние (т.е. связанные некоторым ребром) вершины не окрашены в один цвет.
Предварительный анализ

found= r  tРаскраска (правильная(r))

где Правильная(r)=  i,j Вершины (Соседи(i,j)Цвет(r,i) Цвет(r,j))
Следующий алгоритм полного перебора раскрасок - тривиальное кратное обобщение задачи поиска (см. "Поиск" и "Вычисление свойств")
function Правильная(r:tРаскраска):boolean;

begin


b:=true;

i:=ПерваяВершина;

while (i<=pred(ПоследняяВершина)) and not b do

begin j:=succ(i);

while (j<=ПоследняяВершина) and not b do

if Coседи(i,j) and (Цвет(r,i)=Цвет(r,j))

then b:=false else j:=succ(j);

i:=succ(i)

end;
procedure ПолныйПеребор(graph:tGraph; var found:boolean);

var r: tРаскраска; exists:boolean; {}

begin

r:=ПерваяРаскраска; found:=false; exists:=true;



while exists (= not КончилисьРаскраски} do

begin if Правильная(r) then begin found:=true; Печать(r) end

Следующая(r,exists) {r:=Следующая(r)}

end;


end;
Дальнейший анализ.

Тип tВершины должен быть порядковым - можно, например пронумеровать все вершины - tВершины=1..n;

Тип tGraph хорошо бы реализовать так, чтобы было легко вычислять функцию Соседи: tВершины  tВершины  Boolean. Хороший вариант - задать граф матрицей инциндентности (т.е. табличным определением функции Соседи)

tGraph=array[tВершины, tВершины] of Boolean;

Тип tРаскраска хорошо бы реализовать так, чтобы было легко вычислять цвет каждой вершины r: tВершиныtЦвет (реализация типа tЦвет произвольна, лишь бы было определено равенство, пусть tЦвет=1..m).

Хороший вариант tРаскраска=array[tВершины] of tЦвет= array[1..n] of tЦвет, но тип array - не порядковый, что требуется нашим алгоритмом. Но - теперь мы умеем организовать перебор последовательностей с помощью стековых операций.

Вывод - реализовать раскраски как стек (который, в свою очередь, в данном случае лучше реализовать массивом).
Опустив детали реализации, подведем некоторые предварительные итоги решения. Алгоритм прост, но малоэффективен. Перебор всех mn раскрасок - функций/последовательностей [1..n][1..m] - дело долгое.
§ 11. Перебор с возвратом.
Как сократить область перебора в переборных алгоритмах? Вернемся к решению задачи о раскрасках карты из § 12 (здесь - повторить постановку и вкратце идею и «дефект» решения алгоритмом полного перебора).
Идея сокращения проста, но изящна - рассматривать неполные раскраски - динамические последовательности, т.е. функции [1..k][1..m], kn. Основание - если (неполная) раскраска r уже не правильна, нет никакого смысла терять время на перебор раскрасок большей длины.
Для этого доопределим лексикографический порядок на последовательностях Seq произвольной длины, Seq=  {Seq(L):LN}. Пусть снова a,b  Seq - теперь, вообще говоря, разной длины LenA и LenB, ab и N=N(a,b)=min {n: a(n) b(n)}- наименьший номер i, на котором они различаются. Тогда a


  1. Либо N Dom(a) (N  LenA), N Dom(b) (N  LenB) и a(N)<T b(N).

  2. Либо N Dom(a) (N  LenA) и N Dom(b) (N > LenB), т.е. a - начальный отрезок последовательности b

(можно свести все к "старому" случаю a), если мысленно доопределить последовательность меньшей длины добавленным в T "пустым" значением , сделав его минимальным  =Нулевой=pred(Первый))

Пример. 'шабаш'<'шабашка', 'шабаш'<'шалаш'


Алгоритм определения следующей последовательности - полностью повторяет прежний, за исключением пункта в) (теперь нам не зачем доопределять следующее значение до фиксированной длины). Операцию перехода к следующему значению в таком порядке принято называть возвратом.
procedure ПереборCВозвратом(graph:tGraph; var found:boolean);

var r: tРаскраска; exists:boolean;

begin

r:=ПерваяРаскраска; {заметь - теперь это пустая последовательность!}



found:=false; exists:=true;

while exists {= not КончилисьРаскраски длины менее n} do

begin if Правильная(r)

then if Полная(r) {т.е. длины n}

then begin found:=true; Печать(r) end

else {дополнить - добавить первое значение в T}

Push(r, Первый)

else {не правильная} Возврат(r, exists) {=Следующая(r,exists)}

end;

end;
Замечание. Экзаменатор вправе потребовать довести реализацию до конца!


§12. Нелинейные типы данных.
Существует много определений одних и тех же (с точки зрения математики) содержательных (интуитивных) понятий. Так, под графами мы содержательно подразумеваем визуальные (зрительное) понятие - диаграммы, состоящие из вершин и соединяющих их ребер - отрезков (неориентированный граф) или стрелок (ориентированный граф).
С формальной стороны, (ориентированный/неориентированный) граф можно отождествлять с множеством его ребер Arrows, а последнее, в свою очередь с некоторым (произвольным/симметричным) бинарным отношением, т.е. подмножеством декартового квадрата (множества всех пар) NodesNodes:

 a,b Nodes

( Arrows  в графе есть стрелка, ведущая из вершины a в вершину b)

Так определенный граф мы, скорее всего, реализуем т.н. функцией инциндентности (соседства) - предикатом NodesNodesBoolean, а последний - булевским массивом

type

tArrows=array[tNodes,tNodes] of boolean;



{при реализации симетричного отношения желательны треугольные массивы, в Паскале отсутствующие}
С другой стороны, граф можно отождествлять с функцией перехода из вершины по данному ребру GoArrowsNodesNodes, для некоторого множества ребер Arrows и множества вершин Nodes:

 r Arrows  a,b Nodes (Go(r,a)=b  ребро r ведет из вершины a в вершину b)

Так определенные диаграммы мы будем называть автоматами (без выхода). При этом вершины из Nodes обычно понимаются как маркировка/именование состояний некоторого объекта, а стрелки - маркировкой некоторых элементарных преобразований состояний.
И, соответственно, Go - обозначением операции аппликации (применения функции к аргументу). Ясно, что здесь неявно подразумевается некоторая система обозначений значений и их преобразований, т.е. обозначение некоторого типа данных. Для программиста теория автоматов важна именно в качестве теории обозначений типов данных.
Понятно различие двух определений. В первом мы (явно ли нет) именуем/указываем/помечаем лишь множество вершин, во втором - и множество ребер. Для программиста важно осознавать и различие определений (они ведут к разным типам-реализациям), так и возможность их отождествления (преобразования типов с сохранением семантики).
Работа (функционирование) автомата описывается индуктивно определяемой функцией Go*Arrow*NodesNodes. При этом конечные последовательности из Arrow* понимаются как пути в диаграмме или же - трассы преобразований состояний (см. и сравни "Поиск").
type

tArrows=1..nMaxArrows;

tNodes=1..nMaxNodes;

{реализация автомата массивами}

tAutomaton=array[tArrows,tNodes] of tNodes;



{реализация автомата ссылками}

pAutomaton=pNode; {ссылка на начальное состояние автомата - см. a),b) ниже}

pNode=^tNode;

tNode=record

Content:T;{содержимое вершины/ определение значения состояния}

Arrows:array[tArrows] of pNode;

{последовательность исходящих стрелок, реализованная массивом}

{динамический вариант - линейный список}

end;
Здесь нас интересуют графы/автоматы специального вида - бинарные деревья (общую теорию графов и автоматов см. курс дискретной математики). Автомат Go - дерево, если существует корневая вершина (начальное состояние автомата) ,


  1. в которую не ведет ни одно ребро,  a Nodes (Go(r,a)=  a=),

  2. но из которой существует путь в любую вершину,

 a Nodes  r Arrows (Go(r,)=a),

причем этот путь - единственный,  r,r' Arrows (Go(r,)=Go(r',)  r=r').

Go - бинарное дерево, если, к тому же, множество стрелок состоит из лишь из двух стрелок, далее обозначаемых как левая (left) и правая (right), Arrows={left,right}.
Другие возможные обозначения: Arrows={0,1} или Arrows={true,false}. При желании и возможности дайте также графовое определение понятие дерева - в частности, рекурсивное определение, соответствуюющее диаграмме ниже.
{реализация бинарного дерева ссылками}

pTree=pNode; {дерево задается ссылкой на корень}

pNode=^tNode;

tNode=record

Content:T;{содержимое вершины}

left,right: pNode;

end;
Задача обхода дерева заключается в переборе его вершин в некотором порядке, т.е. построении некоторой функции следования (счета) на Arrows*Nodes.
Самый простой, но исключительно важный класс таких порядков связан с лексикографическим порядком на множестве "слов"/путей Arrows* (см. "Перечисление последовательностей"), связанных со всеми возможностями определения такого порядка на "алфавите" - множестве Arrows.



(По старой программистской традиции мы рисуем деревья "вверх тормашками"). Так, при КЛП-обходе для любого поддерева исходного дерева корень обрабатывается (идет в перечислении раньше), чем все нижние вершины, причем все вершины его левого поддерева обрабатываются раньше, чем все вершины правого. Аналогично определяются ЛПК, ЛКП, ПЛК, ПКЛ и КПЛ обходы.


Чуть точнее: добавим во множество Arrows пустой элемент  (соответствующей стрелке из каждой вершины в себя) и рассмотрим всевозможные порядки на множестве Arrows={,left,right}. Любой такой порядок продолжается на множество путей Arrows* - конечных последовательностей 1,..,rL>, рассматриваемое теперь как множество функций r с бесконечной областью определения N таких, что r(i)= ri, для i [1..L] и r(j)=, для j>L. Положим теперь r10)0) (в смысле выбранного порядка на Arrows), i0=min {i: r1(i)r2(i)}. Например, при сравнении 3-х путей 'Left', 'LeftLeft' и 'LeftRight' {Определение обхода в терминах лексикографического порядка дает следующую идею алгоритма обхода заданием приоритета выбора вершин:

Шаг 0. Начни с первого, в данном порядке, пути

В одних порядках это - пустой путь к корню дерева, в других его необходимо предварительно построить!

Шаг i+1


(если нет никаких направлений - обход окончен, иначе)

(если можно идти в направлении D1,) иди в направлении D1,

(если нет, но можно идти в направлении D2,) иди в направлении D2,

(если нет, но можно идти в направлении D3,) иди в направлении D3,


Здесь D1,D2,D3 принадлежат алфавиту {up ("вверх"), left ("налево"), right (направо)}, нумерация задается выбранным на нем порядком, причем ход наверх соответствует значению .
Реализация спуска налево и направо от текущей вершины очевидна. Главная трудность - в реализации подъема: в деревьях все пути ведут "вниз"!

Идея

  • нужно запоминать ссылки на корни деревьев, которые еще предстоит обработать - самое простое - класть их в конец некоторой (динамической!) последовательности.

  • для реализации подъема нужно вытащить ссылку на вершину из хранилища; в зависимости от вида обхода, это может означать как "вытаскивание" как с того же, так и другого конца последовательности.

Сказанное определяет тип последовательности-хранилища: это либо стек, реализующий "вытаскивание" с того же конца, либо очередь, реализующая "вытаскивание" с противоположного конца. (См. "Абстрактные линейные типы").


{пример обхода "в глубину"   < {left,right} }

{ более короткие ветки (слова, пути) идут в перечислении раньше более длинных}

procedure КЛП(root:pTree);

var


pt:pNode;{ссылка на текущую вершину}

stack:pStack; { реализация - см. "Абстрактные линейные типы"}

begin

Create(stack);

if root<>nil then push(stack,root);

while not empty(stack) do

begin

pop(stack,pt); {подъем наверх}



with pt^ do

begin


{ какая-то обработка содержимого текущей вершины pt^.value}

if left<>nil {можешь идти налево?}

then {тогда иди, но сохрани правую ссылку}

begin pt:=left; if right<>nil then push(stack,right) end

else {налево нельзя}

if pt^.right<>nil {- можно ли направо?}

then pt:= pt^.right

end;


Destroy(Stack)

end;
{пример обхода в ширину - ветви одинаковой длины ("буквы" left, right) соседствуют в перечислении}

procedure (root:pTree);.

var


Queue:pQueue; { реализация - см. "Абстрактные линейные типы"}

pt:pNode;{ссылка на текущую вершину}

begin

Create(Queue); {создать очередь}



if root<>nil then Put(Queue,root); {Поставить В Очередь }

while not Empty(Queue) {очередь не пуста} do

begin

Get(Queue, pt); {Вывести Из Очереди}



{обработать содержимое pt^.value вершины pt}

if pt!.left<>nil then Put(Queue, pt!.left);

if pt!.right<>nil then Put(Queue, pt!.right);

end;


Destroy(Queue)

end;
Замечание. Деревья просто определяются рекурсивно, а потому – для проявления эрудиции – нетрудно дополнить ответ рекурсивными вариантами обхода «в глубину». Но – не заменять ими итеративный вариант!


Алгоритмы символьной обработки.
П
оследние 3 вопроса относятся к алгоритмам символьной обработки. Важность задач обработки символьных последовательностей (текстов) связана с универсальностью текстов как имен (обозначений). Значение любого типа как-то выражается, определяется текстом его записи (в некоторой системе обозначений).
В этой связи круг естественно возникающих при обработке текстов задач можно разделить на 3 части.

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

  2. Формальная обработка («редактирование») текстов как собственно последовательностей, никак не связанная с ролью текстов как обозначений.

  3. Формальные вычисления - порождение текста результата некоторого преобразования значений по тексту ее аргументов



§ 13. Алгоритмы обработки выражений.

Определим арифметическое выражение (в полноскобочной инфиксной записи) как последовательность символов, являющуюся



  1. либо термом, т.е.

  1. либо именем константы вида b1… bn.c1… cm , где

(n>0) & (m>0) &  i[1,n](bi ['0','9']) &  j[1,m](cj ['0','9'])

  1. либо именем переменной вида d1… dk, где

(k>0) & (d1 ['a','z']) &  i[2,k](di ['0','9']['a','z'])

  1. либо строкой вида (E1)f(E2), где E1,E2 - выражения, а f - один из знаков +,-,*,/

Аналогично определяются выражения в префиксной f(E1)(E2), и постфиксной (E1)(E2)f формах.
О
пределим также дерево выражения E как дерево над базовым типом string, состоящее из единственной вершины, содержащей Е, если Е - терм или же дерево вида
если Е - выражение вида (E1)f(E2).
Аналогично определяются деревья выражений в префиксной и постфиксной формах. Ясно, что вид дерева не зависит от синтаксиса/структуры сложного выражения. В чем и заключается их прелесть. Минус – больший расход памяти, по сравнению в линейной, строковой форме.
Как перейти от представления выражения деревом к линейной - инфиксной, префиксной и постфиксной форме? Ответ ищи в разных порядках обхода дерева в "Нелинейные типы данных".
Типом выражения E (и, соответственно, вершины, его содержащей) будем называть cимвол-метку 't', если Е - терм, и символ 'f', если Е - выражение, содержащее знак операции.
Задача вычисления дерева выражения. Найти результат выражения при заданных значениях переменных, если выражение представлено деревом.
Строго говоря, для решения задачи мы должны также зафиксировать семантику выражений - что же они на самом деле обозначают? Ясно, что по умолчанию подразумевается традиционная арифметика вещественных чисел.
Для того, чтобы кратко и ясно представить основную идею алгоритма как сведение вычисления сложного выражения к вычислению элементарных выражений или атомов (содержащих, по определению, единственный знак операции), будем предполагать, что значения термов (констант и переменных) уже найдены (как это сделать - см. "Задачи текстовой обработки,3") и сохранены в самом дереве.
type

pNode=^tNode;

tNode= record

Name:string;

Val:integer;

left,right:pNode;

end;
Очевидно, это не очень экономное решение в смысле расхода памяти - для двух термов с одинаковым значением мы сохраняем значение дважды, а остальные (содержащие знаки операций) вершины вообще изначально не имеют значения. Обычно значения термов и промежуточные результаты сохраняют во внешних по отношению к дереву таблицах.
{вычисление элементарного выражения}

function AtomVal(root:pExpr):T;

begin

with root^ do



case Name of

'+': AtomVal:=left^.Val+right^.Val;

'-': AtomVal:=left^.Val-right^.Val;

'*': AtomVal:=left^.Val*right^.Val;

'/': AtomVal:=left^.Val/right^.Val;

{для простоты, опускаем обработку исключения - деления на 0}

end; end;
{выяснение типа вершины}

function NodeType(pt:pNode):char;

begin

with pt^ do



if (right=nil) and (left=nil)

then NodeType='t'

else NodeType='f'

end;
{поиск атома, лежащего ниже данной вершины}

{идея: вычисление -свойства found:= ptpNode (ptnil & pt^.leftnil & pt^.rightnil) }

{см."Вычисление свойств"}

{предусловие: вершина pt содержит знак операции, NodeType(pt)='f'}

procedure FindAtom(var pt:pNode; var stack: pStack);

var found:Boolean;

begin


with pt^ do

begin


found:=false;

while not found do

begin

if NodeType(left) ='f'



then begin push(pt,stack); pt:=left end

else if NodeType(right)='f'

then begin push(pt,stack); pt:=right end

else


{ это терм  NodeType(pt)='f' & NodeType(left) ='t' & NodeType(right)='t'}

found:=true

end;

end;


{предусловие - дерево не вырождено, все термы уже означены}

function ExpVal(root:pExpr):T;

var

stack:pStack;



{вспомогательный стек ссылок pNode на еще не вычисленные знаки операций}

{реализацию см. "Абстрактные линейные типы данных"}

found:Boolean; {есть еще атомы?}

RootType,LeftType,RightType:char; {{'t','f')}

begin

Create(stack); UpperType:=NodeType1(root);



if UpperType='f' then push(stack,root);

while not empty(stack) {= есть невычисленные операции} do

begin

{следующий поиск начинаем c вершины, ближайшей к вычисленному атому}



pop(stack,pt); FindAtom(pt,stack);

{Вычисли значение атомарного выражения}

pt^.val:= AtomValue(pt);

{Сократи дерево}

destroy(pt^.right); pt^.right:=nil;

destroy(pt^.left); pt^.left:=nil;

end;

{Дерево состоит из одной вершины}



ExpVal:=root^.val;

end;
Замечание. Побочным (и не очень желательным) эффектом простоты нашего алгоритма является уничтожение самого дерева. В реальности, мы можем не уничтожать вычисленные термы, а лишь помечать их как уже вычисленные.


Задача. Синтаксический анализ выражения, представленного деревом. Проверить, является ли произвольное бинарное символьное дерево деревом выражения.
Задача синтаксического анализа очевидно распадается на анализ 1) термов и 2) сложных выражений. В данном случае, анализ термов прост и напрямую сводиться к задаче поиска (см. "Поиск"). Центральная идея: свести задачу анализа сложного выражения к задаче вычисления.


  1. Анализ термов.

type tSetOfChar=set of char;


{Поиск позиции "исключения" - первого символа в подстроке s[start,finish], не принадлежащего множеству alphabet }

procedure FindException

(s:string; start,finish:Cardinal; alphabet:tSetOfChar; position:Cardinal; found:boolean);

{идея: проверка свойства found= position [start,end] (s[position]  alphabet)}

{см."Вычисление свойств" и "Поиск"}

begin


found:=false; position:=start;

while (position<=finish) and (not found) do

if s[position] in alphabet then inc(position) else found:=true;

end; { function FindException }

{проверка имени константы}

function ConstantName(s:string):boolean;

var

position, {позиция "исключения" - см. procedure FindException }



len:Cardinal; {длина выражения s}

found:boolean; {"исключение" найдено}

begin

len:=Length(s); ConstantName:=false;



FindException(s,1,len,['0'..'9'],position,found);

if (position=1) or (position=len) or (not found)

then {нет обязательной внутренней не-цифры} exit; {завершаем}

if s[position]<>'.'

then {эта не-цифра - не точка} exit; {завершаем}

{есть ли не-цифры после точки?}

FindException(s,position+1,len,['0'..'9'],position,found);

ConstantName:=not found

end;
{проверка имени переменной - еще проще}

function VariableName(s:string):boolean;

var

position, {позиция "исключения" - см. procedure FindException }



len:Cardinal; {длина выражения s}

found:boolean; {"исключение" найдено}

begin

len:=Length(s); VariableName:=false;



if len=0 then exit;

if not (s[1] in ['a'..'z']) then exit;

FindException(s,2,len,['0'..'9']+['a'..'z'],position,found);

VariableName:=not found

end;


  1. Анализ (сложных) выражений.

Определим понятие расширенного (или просто р-) выражения как строки вида (E1)F(E2), где E1,E2 - произвольные, в том числе пустые строки, а F - произвольная непустая строка. Мотивация - любому невырожденному дереву однозначно сопоставляется некоторое р-выражение.


Расширим также понятие типа р-выражения за счет добавления "неопределенного" значения '' : если р-выражение не имеет типа 'f' или 't', то оно имеет тип ''. Формальным вычислением р-выражения назовем операцию Reduce (сократить, англ):

 терм 'x', если Тип(E1)=Тип(E2)='t' и Тип(F)='f'

Reduce((E1)F(E2))= 

 '', в противном случае

(смысл прост - результатом вычисления правильного выражения является терм, а результат вычисления неправильного выражения не определен)
Идея алгоритма: Выражение правильно  тип результата формального вычисления = терм.
{расширенный тип вершины}

function ExtentedNodeType(pt:pNode):char;

var len:Cardinal; {длина строки}

begin


{теперь не уверены, что "листья" дерева - это термы!}

ExtendedNodeType:='';

if pt=nil { тип пустого слова неопределен}

then exit; {= завершить определение значения функции }

with pt^ do

begin


len:=Length(name);

if len=0 then exit;

if (Len=1) and (name[1] in ['+','-','*','/']) and (left<>nil) and (right<>nil)

then begin ExtendedNodeType:='f'; exit end;

if (left=nil) and (right=nil) and

(ConstantName(pt^.name) or VariableName(pt^.name)

then ExtentedNodeType:='t'

end; {with}

end; { function ExtentedNodeType }

{теперь не уверены, что найдутся правильные атомы!}

{но, в таком случае, обязательно найдется не правильный }

{см. определение ниже в теле процедуры}

procedure FindExtendedAtom(var pt:pNode; var stack: pStack);

var found:Boolean;

begin

with pt^ do



begin

found:=false;

while not found do

begin


UpperType=NodeType(pt);

LeftType= NodeType(left);

RightType=NodeType(right);

if (UpperType<>'f') or (LeftType='') or (RightType='')

then

{дальше искать нечего, формально считаем - найден "не правильный" атом}



found:=true

else if LeftType='f'

then begin push(pt,stack); pt:=pt^.left end

else if RightType='f'

then begin push(pt,stack); pt:=pt^.right end

else {UpperType='f' & LeftType='t' & RightType='t'}

{  найден правильный атом }

found:=true

end; {while}

end; {with }

end; { procedure FindAtom }
{формальное вычисление расширенного элементарного выражения}

function ExtendedAtomVal(root:pExpr):string;

begin

with root^ do



if (ExtendedNodeType(root)='f') and

(ExtendedNodeType(left)='t') and

(ExtendedNodeType(right)='t') {корректный атом}

then ExtendedAtomVal:='x' {сгодиться любой терм!}

else ExtendedAtomVal:='';

end;
function ExpressionCorrect(root:pExpr):boolean;

begin

result:=true;



create(stack);

RootType:=ExtendedNodeType(root);

case RootType of

'': result:=false;

'f': push(stack,root);

end;


while (not empty(stack)) and result do

begin


pop(stack,pt);

FindExtendedAtom(pt,stack);

{Формальное "вычисление"}

pt^.Name:=ExtendedAtomValue(pt);

if NodeType(pt)=''

{если результат элементарного вычисления неопределен}

then Result:=false {то результат всего вычисления - и подавно}

else


begin {Сократи дерево}

destroy(pt^.right); pt^.right:=nil;

destroy(pt^.left); pt^.left:=nil;

end; {if}

end; {while}

Result:=(NodeType(root)='t')

end;



Достарыңызбен бөлісу:
1   2   3   4   5




©dereksiz.org 2024
әкімшілігінің қараңыз

    Басты бет