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



бет2/5
Дата28.06.2016
өлшемі418 Kb.
#162858
түріМетодическое пособие
1   2   3   4   5
§ 3. Классификация типов данных.

(Начни с определения понятия абстрактного типа данных как области - пары , FAA – см. §1).


Примеры базовой семантики конкретных типов языка Паскаль (в реальности, как мы хорошо знаем, каждый тип обогащен множеством других функций)
Real, integer - арифметика вещественных и целых чисел,

string - полугруппа слов относительно операции конкатенации (сцепления слов),
array[I] of T - множество всех функций IT, c операцией App применения (аппликации) функции к аргументу, App(a,i)=a[i],
record n1:T1;…;nm:Tm end - именованное декартово произведение (определение см. §1) основных множеств типов T1,…,Tm с конечным множеством имен {n1,…,nm}, с операцией аппликации, App(r,n)=r.n,
file of T - множество всех последовательностей NT, c операторами:

reset(f)  маркер(f)=0; режим(f):=чтение;

read(f,x)  inc(маркер(f));x:=f(маркер(f))

область определения: (режим(f)=чтение)& (маркер(f) Dom(f))

rewrite(f)  маркер(f)=0; режим(f):=запись;f:=<> (пустая последовательность)

write(f,e)  inc(маркер(f));f(маркер(f)):=e;

область определения: (режим(f)=запись)

eof(f)  маркер(f) Dom(f)  компонента f(маркер(f)) определена

где маркер(f), режим(f) - некоторые вспомогательные системные (т.е. предопределенные, не определяемые пользователем) переменные.
(В отличие от объектного подхода), классический процедурный стиль склоняется к раздельному описанию

а) способов определения основного множества значений А, при фиксированном классе функций F (собственно, и относимых к определению типа) и

б) способов определения класса F - преобразований значений типа (см. § 2).
Это определяет и соответствующую классификацию типов по способу определений значений основного множества.


  1. Разделение между скалярными и производными типами уже проведено в §1. В последнем случае, значения структурированы. Общая схема такой структуры - именованное декартово произведение, т.е. некоторый класс функций. Различие производных типов - в конкретных областях определения и значений этих функций, и (фиксированных для каждого типа) операторов их обработки. Примеры скалярных типов в Паскале - integer, real, char, boolean, перечислимый, ограниченный, производных типов - string, text, array, file, record, set.




  1. Стандартные и пользовательские типы. В первом случае множество значений преопределено, во втором (до)определяется программистом. Примеры стандартных типов - integer, real, char, boolean, string, text; пользовательских типов - перечислимый, ограниченный, array, file, record, set.




  1. Порядковые и непорядковые типы. Понятие перебора (перечисления, пересчета) является одним из наиболее фундаментальных в программировании. Порядковые типы - те множества значений X, на которых определен (хотя бы один) порядок перебора, т.е. сравнение (линейный порядок) <, xлюбое сравнение определяет порядок перебора, а лишь такое, для которого определены (доступны)

  • функция следования (прямого перебора) succ, succ(x)=min{x': x'>x} и

  • функции предшествования (обратного перебора) pred, pred(x)=max{x’:x’


Примеры порядковых типов Паскаля -

integer - succ(n)=n+1,

char, - succ(ci)= ci+1 (ci - - i-ый символ в предопределенном языком фиксированном алфавите, таблице символов)

Boolean - false
Значения производных типов (array T1 of T2, record, file of T) обычно не считаются порядковыми, но нужный порядок можно (до)определить, если соответствующие базовые типы - порядковые (см. перечисление последовательностей в §10 и §11). Очень интересны примеры


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

  • множества всех бесконечных последовательностей, с теми же свойствами. (см. "Перечисление последовательностей").

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


  1. Динамические и статические производные типы. Значения производных типов - функции. Область их определения может быть фиксирована (статический тип) - пример - множество всех функций [1,n]T для фиксированного n, либо нет (динамический тип), пример - множество всех функций [1,n]T для произвольного n. Примеры статических типов - record, статический array (в стандартном Паскале); динамических - file, string, динамический array ( в Object Pascal).

В последнем случае определение типа предполагает явное или неявное наличие соответствующих операторов расширения и сужения области определения. Примеры - оператор Паскаля SetLength явного установления области определения динамических массивов; оператор write увеличивает длину файла неявно.


Практический интерес представляют типы, которые можно назвать псевдодинамическими. Это функции, область определения которых не фиксирована, но содержится в некотором фиксированном множестве значений. Пример: множество всех последовательностей f с длиной менее заданной, f:[1..n]T, n

  1. Абстрактные типы. Совершенно иной смысл имеет условное разделение абстрактных и конкретных типов. Абстрактные типы - типы, точно определяемые как математические объекты (объекты некоторого языка спецификаций), но не как объекты данного языка программирования. Пример - комплексные числа и точки плоскости не есть тип Паскаля, тип record x,y: real end - тип Паскаля. Установление соответствия между (значениями и операциями) абстрактного и конкретного типов называется реализацией (определением, моделированием или представлением) абстрактного типа.

  2. Тесно связано с понятием реализации (как способа определения) деление типов по способам доступа. На практике, программисту важен именно способ определения, задания объекта/функции. Другое дело, что такие определения можно снова точно сформулировать в терминах функций/операторов. Существует три важных вида таких определений: реккурентное (данные последовательного доступа, пример - файлы и списки, язык описания - функции следования), явное (данные прямого доступа, пример - массивы и записи, язык описания - оператор аппликации) и рекурсивное (пример - деревья и графы, язык описания - функции на последовательностях)


Пояснить классификацию типов на конкретном примере.
Задача: найти длину lmax самого длинного слова w (в текстовом файле f) и само это слово w; (слова представлены символьным списком.)

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

type

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

slovo=^component;

component=record letter:char;next:slovo end;

{аналогично - найди место в каждой классификации для остальных типов, используемых в спецификации и реализации программы}
procedure P(var f:text; var lmax:integer; var w:slovo);

var v:slovo;

begin

lmax:=0;


w:=ПустоеСлово;

stop:= КончилисьСлова(f); v:=Oчередное слово(f); l:= Длина(v);

while not stop do

begin


if l>lMax then begin lmax:=l; Копировать(w,v) end;

{подготовка переменных к следующему проходу}

Уничтожить(v);

stop:= КончилисьСлова(f); v:=Oчередное слово(f); l:= Длина(v);

end;

Внимание - хитрость примера - обратное влияние или "побочный" эффект реализации - независимое определение функции КончилисьСлова ( остаток файла содержит хотя бы одно слово  хотя бы один не пробел) делает практически невозможным (точнее, сильно неэффективным) реализацию процедур OчередноеСлово и Длина. Отдельные, с точки зрения логики, операторы, могут быть реализованы только совместно.


procedure P(var f:text; var lmax:integer; var w:slovo);

var v:slovo;

begin

lmax:=0;


w:=nil; {w:=ПустоеСлово}

ОпределиКончилисьСловаЕслиНетОпределиОчередноеСловоИЕгоДлину(f,stop,v,l);

{читай файл до непробела/начала слова, если таковое есть - читай все непробелы в список v, одновременно подсчитывая их число в l}

while not stop do

begin

if l>lMax then begin lmax:=l; Копировать(w,v) end;



{подготовка переменных к следующему проходу}

Уничтожить(v);

ОпределиКончилисьСловаЕслиНетОпределиОчередноеСловоИЕгоДлину(f,stop,v,l);

end;
Замечание. Если есть трудности с реализацией порождения, уничтожения и копирования списков - см. "Абстрактные линейные типы данных"



§4. Вычисление предикатов.
Предикат (свойство, условие) - произвольная функция с областью определения Boolean={true,false}. Пример (двуместных) предикатов - бинарные отношения, в частности, отношения сравнения <,>,=,<>.
Вычисление предикатов облегчается тем, что существует общематематический язык логики (исчисления предикатов) - исключительно выразительный и одновременно компактный - содержащий бинарные операции & (and, в синтаксисе Паскаля),  (or),  (not) - их относят к операциям алгебры логики, а также два квантора (оператора над множествами)  и  (отсутствуют в Паскале). Перевод формулы из языка ИП в программу на структурном языке программирование - техническая задача, при учете следующих основных моментов.


  1. Написание (спецификация) формул может быть само по себе достаточно сложной задачей. Математическая логика располагает множеством стратегий таких спецификаций в виде алгоритмов записи любого предиката в некоторой канонической (нормальной) форме. Наиболее простые из таких форм для бескванторных формул - днф (дизъюнкция элементарных коньюнкций), кнф (конъюнкция элементарных дизъюнкций); по определению, элементарные формулы могут содержать отрицание, но не знаки бинарных логических операций.

  2. Для кванторных формул канонической является т.н. предваренная нормальная форма вида Q1x1… Qn xn B(x1,…,xn,z) ( где Qi - либо  либо , причем каждые два соседних квантора можно считать различными, а B уже не содержит кванторов).

Пример. Определить принадлежность точки p(x,y) к фигуре, составленной из 4 колец. Каждое кольцо задаваемется центром и длинами внутреннего и внешнего радиусов.

Решение - стратегия днф.

а) Точка принадлежит к фигуре, если она принадлежит одному из колец.

type


tPoint=record x,y:real end;

tRing=?


tFigure=array[1..n] of tRing;
function ПринадлежитФигуре(p: tPoint; figure:tFigure):boolean;

begin


ПринадлежитФигуре:=

ПринадлежитКольцу(p,figure[1]) or

ПринадлежитКольцу(p,figure[2]) or

ПринадлежитКольцу(p,figure[3])

end

b) Точка принадлежит кольцу, если она находится одновременно внутри внешнего и вне внутреннего окружностей кольца.



type

tRing =record center:tPoint; radius1,radius2:real end;

function ПринадлежитКольцу(p:tPoint; Ring:tRing):boolean;

begin


ПринадлежитКольцу:=

ПринадлежитОкружности(p,Ring.center,Ring.radius1) and

not ПринадлежитОкружности(p,Ring.center,Ring.radius2)

end;


  1. Принадлежность точки P с заданными координатами Px,Py к окружности O c центром Ox,Oy и радиусом Oradius описывается атомарной формулой

(Px-Ox)2+(Py-Oy)2 Oradius2

function ПринадлежитОкружности(p:tPoint,center:tPoint; radius:real):boolean;

begin

ПринадлежитОкружности:= sqr(P.x-Center.x)+sqr(P.y-Center.y)<=sqr(radius)



end;

  1. Алгоритмическое определение операций алгебры логики.

  1. y:=not b  if b=true then y:=false else y:=true

  2. y:=b1 and b2  1) y:=false; if b1=true then if b2=true then y:=true;  2) y:=true; if b1=false then y:=false; if b2=false then y:=false;

  3. y:=b1 or b2  1) y:=true; if b1=false then if b2=false then y:=false 2) y:=false; if b1=true then y:=true; if b2=true then y:=true

Внимание - указанные эквивалентности верны лишь для определенных значений b1,b2. Программы 1) определенные значения y при неопределенном значении b2, программы 2) - нет. Семантика 1) соответствует т.н. быстрому означиванию операций и определяют т.н. условные коньюнкцию и дизъюнкции - строго говоря, отличных от классических (семантика 2). В Паскале эта неоднозначность ("темное" место языка) разрешается директивой компилятора $B. Разные значения директивы может сделать одну и ту же программу верной или неверной!

Задача (линейный поиск)

type

tIndex=1..MaxLen;



ArrayOfT=array[tIndex] of T;

procedure Poisk(x:T; a:ArrayOfT; LenA:tIndex; var found:boolean; var index:tIndex);

begin

index:=1;



while (index<=LenA) and (x<>a[index]) do inc(index);

found:= index<=LenA

end;

При быстром означивании формула (index<=LenA) and (x<>a[index]) при index=LenA+1 дает false (программа верна), при классическом - значение неопределено (программа неверна). Совет: желательно избегать неопределенных значений -



index:=1; found:=false;

while (index<=LenA) and not found do if x=a[index] then found:=true else inc(index);



  1. Вычисление кванторных формул.

  1. y:= x X (B(x))

Рассмотрим частный (для классической логики), но наиболее важный для программирования (и конструктивной логики) случай. X - множество значений порядкового динамического типа, для которого определены функция следования (перебора) и предикат конца перебора значений - заданный, например, в виде сравнения. Тогда  x X (B(x)) - это кратная дизъюнкция, а y есть первый true в реккурентной последовательности y0=false, yi+1= yi or B(xi) (либо false, если такового нет). Поэтому, можно воспользоваться общей схемой вычисления членов реккурентных последовательностей:

y:=  x X (B(x)) 

y:=false; i:=1; while (i<=LenX) and not y do if B(xi) then y:=true else inc(i)

Точно также можно определить формулу  x X (B(x)) как кратную конъюнкцию:

y:=  x X (B(x)) 

y:=true; i:=1; while (i<=LenX) and y do if B(xi) then y:=false else inc(i)


Пример вычисления кванторной формулы. Последовательность A длины LenA (строго) периодическая (или, попросту - кратная), если оно состоит из двух или более одинаковых "сцепленных" подпоследовательностей некоторой длины k, называемой в этом случае периодом A. Выяснить, является ли данная последовательность строго периодической.

Спецификация

b:=A - строго периодическая 

b:= k  [1,LenA div 2] ( k - период A) 

b:= k  [1,LenA div 2] ( LenA mod k =0   i [1..LenA-k] (A[i]=A[i+k]))

type


tIndex:1..MaxLen;

tArray:array[tIndex] of T;


function Period(k:tIndex; a:tArray; LenA:tIndex):boolean;

var b2:boolean; UpperLimit2:tIndex; {= LenA-k }

begin

b2:=LenA mod k; UpperLimit2:= LenA-k; i:=1;



while (i<= UpperLimit2) and b do

if A[i]<>A[i+k] then b2:=false else inc(i)

Period:=b2

while
end;

function Periodic(a:tArray; LenA:tIndex):boolean;

var UpperLimit1:tIndex; {=LenA div 2} b1:boolean;

begin

b:=false; k:=1; UpperLimit:=LenA div 2;



while (k<=UpperLimit) and not b do

if period(k,A.LenA) then b:=true else inc(k);

Periodic:=b;

end;
§5. Поиск.

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

- аппликация (по хранилищу f и указателю/имени x определить значение f(x)),



  • (пере)определению значения функции f в точке x новым значением y, и

  • (для динамических типов) расширению/сужению области определения функции.

Понятие хранилища данных тесно связано по смыслу, но существенно шире по объему понятия структуры (производного типа). В качестве хранилища, при фиксации кодирования, могут выступать и скалярные значения. Так, например, по основной теореме арифметики, любое n,n N, есть некоторое произведение степеней простых чисел  p(i) a(i) (p(i) i-е простое число). Следовательно, его можно рассматривать как хранилище последовательности а, т.е. потенциальную или "виртуальную", т.е. воображаемую структуру.

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

Задача поиска формулируется в двух внешне различных формулировках.


  1. b:=y  Val(f)  x  Dom(f) (f(x)=y) - выяснить, храниться ли данное значение y в структуре f.

  2. x:=min{x': f(x')=y} определить первое (в некотором порядке) "имя", под которым храниться данное значение.

(задача - обратная к задаче доступа).

В реальности, эта одна задача. С конструктивной точки зрения, мы не не можем ответить на вопрос а), не предъявив такое x, что f(x)=y и не можем найти x в b), не выяснив, существует ли таковое вообще.


b,x:=y  Val(f)  x  Dom(f) (f(x)=y), min{x' Dom(f): f(x')=y}
К задаче поиска легко сводяться многие важные задачи

  • выяснить, храниться ли в структуре f значение с заданным свойством, в частности,

  • выяснить, храниться ли в структуре f значение почти равное заданному (т.е. хорошее приближение заданного значения)

  • определить все "имена", под которым храниться данное значение (значение с заданным свойством) и т.п.

В любом случае, решение предполагает наличия некоторого порядка перебора на Dom(f)={x1,..,xn,..}.Тривиальное решение задачи - линейный поиск - мгновенно следует из схемы вычисления -свойств (см. "Вычисление свойств") в данном случае основанном на реккурентном отношении:

(*)  x  {x1,..,xn,..} (f(x)=y) (f(x1)=y) or  x  {x2,..,xn,..} (f(x)=y))
procedure LinearSearch(f:tStructure;y:tVal;var b:boolean;var x:tDom);

begin


b:=false; i:=1;

while (i<=LenDom) and not b do if f(xi)=y then begin b:=true;x:=xi end else inc(i)

end;

Понятно, это схема. Пересчет tDom и условие его окончания может выглядеть совершенно иначе.


Пример (поиск в конечном дереве и графе). Дерево (граф) в качестве хранилища - функция вида A*T, A - некоторый алфавит, A* - множество всех конечных последовательностей (ветвей дерева), а T - тип вершин дерева (графа). Для решения задачи поиска достаточно определить некоторый порядок перебора ветвей.

procedure LinearSearch(f:tTree;y:tVal;var b:boolean;var x:tDom);

begin

b:=false; x:=ПервоеСлово; {естественно взять в качестве первого пустое слово - других может и не быть!}



while not КончилисьСлова(f) and not b do

if f(x)=y then begin b:=true else x:=Следующее(x)

end;

Окончание решения - см. "Перечисление последовательностей".


Очевидно, в худшем случае алгоритм линейного поиска требует полного перебора всех элементов tDom. Можно ли ускорить поиск? Да, если предполагать наличие некоторого сравнения (линейного порядка) < на tDom (в отличие от tDom, это не обязательно порядок перебора) и рассматривать в качестве хранилищ упорядоченные последовательности (или, в общем случае, монотонные функции) f: tDomtVal

 x,x' tDom (xx'  f(x).f(x'))


Ограниченный поиск. Первая идея "сокращения пространства поиска" основана на простом наблюдении - в случае монотонности f бесполезно проверять равенство f(x)=y для тех x, для которых f(x)>y :

  • x  tDom (f(x)=y)  x  {x1,…, xk} (f(x)=y), где k=min {k': f(xk)  y}.

{предусловие - f монотонна (упорядочена)}

procedure RestrictedSearch(f:tOrderedStructure;y:tVal;var b:boolean;var x:tDom);

var UpperLimitFound:boolean; { x  tDom (f(x)  y)}; y1:tVal;

begin

{найди первое x, что f(x)  y}



UpperLimitFound:=false; i:=1;

while (i<=LenDom) and not UpperLimitFound do

begin y1:=f(xi); if y1>=y then begin UpperLimitFound:=true;x:=xi end else inc(i); end;

if UpperLimitFound then b:=y1=y else b:=false

end;
Пример применения схемы - поиск в упорядоченном файле

{предусловие - f монотонна (упорядочена)}

procedure RestrictedSearch(f:tOrderedFile;y:tVal;var b:boolean;var n:Cardinal);

var UpperLimitFound:boolean; { x  tDom (f(x)  y)}; i:Cardinal;

begin

{найди первое x, что f(x)  y}



UpperLimitFound:=false;

reset(f); {j:=1} i:=0;

while not eof(f) {(i<=LenDom)} and not UpperLimitFound do

begin read(f,y1); {y1:=f(j);inc(j)}

if y1 >=y then begin UpperLimitFound:=true;n:=i end else inc(i);

end;


if UpperLimitFound then b:=y1=y else b:=false

end;
Идея дихотомического поиска (поиска методом деления пополам) также предполагает упорядоченность f и основана на соотношении

(*)  x  {xn1,..,xn2} (f(x)=y) 

 x  {xn1,..,xm} (f(x)=y)) xor  x  {xm+1,..,xn2} (f(x)=y)), где m=(n1+n2) div 2 

(для монотонной f)

(f(xm)=y) xor

(f(xm)n1,..,xm-1} (f(x)=y)) xor

(f(xm)>y) &  x  {xm+1,..,xn2} (f(x)=y))

(т.е. в реальности делим Dom(f) на 3 части, не на 2)
{предусловие - f монотонна (упорядочена)}

procedure Dichotomy(f:tOrderedStructure;y:tVal;var b:boolean;var x:tDom);

UpperLimit, LowerLimit:tDom; {верхняя и нижняя границы пространства поиска}

begin


b:=false; UpperLimit:=1; LowerLimit:=LenDom;

while (UpperLimit>LowerLimit) and not b do

begin

m:= (UpperLimit+LowerLimit) div 2;



if f(xm)=y

then b:=true

else if f(xm)

end; {while}

end; { procedure Dichotomy }
Традиционно дихотомический поиск считается быстрым поиском, поскольку позволяет найти ответ не более, чем за ln2 n сравнений, в то время как верхняя оценка линейного поиска - n сравнений (где n=Card(Dom) - число элементов в пространстве поиска Dom). Однако, нельзя забывать, что, в отличие от линейного поиска, этот алгоритм требует вычисления f(xm), которое может оказаться неоправданно дорогим - например, в случае поиска в файле.
Идея дихотомии (как и ограниченного поиска!) непосредственно продолжается на деревья (и графы) специального вида - деревья поиска. Дерево в качестве хранилища есть некая функция f, f:A*T, где A* - множество путей (ветвей) дерева, а Т - содержимое его вершин. Разберем самый простой случай бинарных деревьев - A={0<1} (кратное обобщение на случай произвольного конечного A={a1<…< an} - техническая задача).
Функция f, f:{0<1}*T > - дерево поиска, если оно f монотонна относительно лексикографического порядка << на словах (см. "Перечисление последовательностей") :  v1,v2  {0,1}* (v1<T f(v2))
procedure OrderedTreeSearch(f:tOrderedTree; y:tVal; var b:boolean; var x:tDom);

begin


b:=false; v:=ПустоеСлово;

while (v  Dom) and not b do

begin

if f(v)=y then b:=true



else if f(v)>y then v:=v0 else v:=v1;

end;


end;

Здесь  - операция приписывания (конкатенации) буквы к слову. В случае реализации деревьев ссылками (см. обозначения в "Нелинейные типы данных"):

v:=ПустоеСлово  p:=Root;

v:=v0  p:=p^.left

v:=v1  p:=p^.right

v Dom  p<>nil

где Root, p - ссылки на корень и текущую вершину дерева.



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




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

    Басты бет