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

Костюк - Основы программирования

.pdf
Скачиваний:
134
Добавлен:
30.05.2015
Размер:
1.3 Mб
Скачать

61

Рис. 3.1

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

Если какой-либо элемент списка больше не нужен, его можно удалить стандарт­ ной процедурой free, аргумент которой – переменная-указатель, ссылающаяся на

удаляемый элемент списка, например: free(p1);

После удаления доступ к элементу становится невозможным, а указателю при­ сваивается значение nil.

Пример 3.20. Программа ввода с клавиатуры совокупности целых чисел и фор­

мирования из них последовательного списка. Конец ввода определяется по введенно­

му числу 0. Указатель p1

указывает на первый, а указатель p2 – на последний

элемент списка.

type pel=^elem;

elem=record s:integer; p:pel end; var p1,p2,p3:pel;

begins: integer; p1:=nil; p2:=nil; read(s);

while s<>0 do (3.21) begin new(p3);

if p1=nil then p1:=p3 else p2^.p:=p3;

p2:=p3; p2^.s:=s; p2^.p:=nil; read(s)

end;

...

end.

Конец примера.

Следующий пример показывает, как можно просмотреть все элементы последо­ вательного списка.

Пример 3.21. Алгоритм вычисления суммы содержимого элементов линейного списка, созданного в предыдущем примере программой (3.21).

 

 

 

p3:=p1;

sum:=0;

 

while p3<>nil do

 

begin

 

(3.22)

sum:=sum+p3^.s;

 

62

p3:=p3^.p

end;

Конец примера.

Пример 3.22. Алгоритм удаления всех элементов линейного списка, созданного программой (3.21).

while p1<>nil do begin

p3:=p1^.p

free(p1); (3.23) p1:=p3

end;

p2:=nil;

Конец примера.

В примерах 3.21 – 3.23 список может быть пустым, тогда оба указателя должны иметь значение nil.

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

Пример 3.23. Алгоритм слияния двух непустых упорядоченных по возрастанию линейных списков.

 

 

procedure slist(var p1,p2,p3:pel);

 

var p4:pel;

 

 

begin

 

 

if p1^.s<=p2^.s then

 

begin p3:=p1; p4:=p1; p1:=p1^.p end

 

else

 

 

begin p3:=p2; p4:=p2; p2:=p2^.p end;

 

{наименьший элемент перемещен в выходной список}

 

while (p1<>nil)and(p2<>nil) do

 

{цикл, пока оба входных списка не пусты}

(3.24)

if p1^.s<=p2^.s then

begin p4^.p:=p1; p4:=p1; p1:=p1^.p end

 

else

 

 

begin p4^.p:=p2; p4:=p2; p2:=p2^.p end;

 

{в цикле наименьший элемент – в выходной список}

 

if p1<>nil then p4^.p:=p1

 

else p4^.p:=p2;

{оставшийся непустым входной

 

список присоединяется в конец выходного списка}

 

p1:=nil; p2:=nil

 

 

63

end;

Алгоритм представлен в виде процедуры slist с тремя параметрами. Пара­ метр p1 является указателем на начало первого списка, p2 – указателем на начало второго списка, эти списки являются входными для алгоритма. Третий параметр p3 является выходным: после выполнения процедуры он будет являться указателем на начало объединенного списка. В отличие от алгоритма слияния массивов, процедура slist не копирует содержимое элементов в новый список, а изменяет ссылки в элементах так, чтобы получился объединенный, полностью упорядоченный список. Входные списки после выполнения процедуры перестают существовать, поэтому указателям p1 и p2 присваивается nil.

Запуск на исполнение алгоритма (3.24) осуществляется вызовом процедуры slist:

slist(p,q,r);

До вызова переменные p, q, r должны быть описаны с типом pel, причем p

и q должны указывать на предварительно упорядоченные списки элементов типа elem.

Нетрудно видеть, что трудоемкость алгоритма – линейная от количества элемен­ тов в объединяемых списках.

Конец примера.

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

В ряде задач применяются такие специальные структуры данных, как очереди и стеки (магазины). Каждая из этих структур содержит набор однородных элементов, которые можно добавлять в набор и извлекать из набора. Очередью называют такой набор данных, который работает по принципу: первый вошел – первый ушел. Стек работает по принципу: последний вошел – первый ушел. Если в задаче заранее из­ вестно максимальное количество элементов очереди или стека, то их можно реализо­ вать в виде массивов, а если неизвестно – то в виде списков.

Пример 3.24. Алгоритмы, выполняющие действия со стеком и очередью. Пусть элементы стека и очереди имеют описание, приведенное в алгоритме (3.21). Для списка, реализующего стек или очередь, заданы также два указателя: pb, указываю­ щий на начало списка, и pe – на конец списка.

Алгоритм добавления элемента со значением X в очередь (в конец списка):

 

 

new(pr); pr^.s:=X; pr^.p:=nil;

 

if pe=nil then pb:=pr

{если список был пуст}

(3.25)

else pe^.p:=pr;

{иначе - не пуст}

 

pe:=pr;

 

 

 

 

 

 

64

Алгоритм добавления элемента со значением X в стек (в начало списка):

 

 

 

 

(3.26)

 

new(pr); pr^.s:=X; pr^.p:=pb; pb:=pr;

 

if pe=nil then pe:=pr;

{если список был пуст}

 

 

Алгоритм удаления элемента из очереди или стека и копирование его значения в переменную X (удаление с начала списка):

 

 

 

 

 

if pb<>nil then begin

{если список не пуст}

 

 

pr:=pb; X:=pb^.s; pb:=pb^.p; free(pr)

(3.27)

 

end;

 

 

Конец примера.

 

 

3.5 Алгоритмы с процедурами и функциями

При описании алгоритма в виде процедуры или функции ее параметры (аргумен­ ты), называемые формальными параметрами, могут относиться к различным катего­ риям:

параметры-значения, которые внутри процедуры не должны изменяться, и ко­ торые описываются с указанием соответствующего типа;

параметры-переменные, которые внутри процедуры могут изменяться, и ко­ торые описываются с указанием слова var и соответствующего типа;

параметры-процедуры (или функции), которые описываются, как имеющие специальный процедурный тип.

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

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

Параметр-переменная при вызове процедуры подставляется по ссылке, т.е. в процедуру передается адрес ее расположения в памяти, и любое изменение парамет­ ра внутри процедуры означает изменение значения подставляемой вместо него пере­ менной. Именно поэтому в качестве параметра-переменной нельзя подставлять вы­

65

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

Для параметра-процедуры предварительно должно быть описание процедурного типа, в котором определяется, сколько параметров должна иметь процедура (или функция) и каковы типы параметров, а также (для функции) тип вырабатываемого ею значения. При вызове вместо параметра-процедуры подставляется имя какой-либо процедуры (или функции), которая имеет в точности такие же свои собственные па­ раметры. Рассмотрим пример применения параметра-процедуры.

Пример 3.25. Рассмотрим задачу приближенного вычисления определенного ин­ теграла I от произвольной функции одной переменной f (x) на интервале [a, b] методом трапеций путем деления интервала на n равных частей. Интеграл будем вычислять по формуле:

I = d × ( f (a) / 2 + f (a + d ) + f (a + 2d ) + ... + f (a + (n -1)d ) + f (b) / 2) ,

где d = (b a) / n.

Пусть в программе имеется следующее описание процедурного типа для инте­ грируемой функции:

type func=function(x:real):real;

Алгоритм вычисления интеграла опишем в виде функции, имеющей параметрфункцию с типом func:

function Integr(f:func;a,b:real;n:integer):real; var i: integer;

d,s:real;

begin s:=(f(a)+f(b))/2;

d:=(b-a)/n; (3.28) for i:=1 to n-1 do

s:=s+f(a+i*d);

Integr:=s*d

end;

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

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

Integr.

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

 

(3.29)

function f1(x:real):real;

begin f1:=exp(sqrt(x)) end;

 

 

 

 

 

 

66

 

 

 

 

 

(3.30)

 

function f2(x:real):real;

 

 

begin f2:=sqrt(exp(x)) end;

 

Тогда вызовом

 

 

 

 

I1:=Integr(f1,0,1,10);

 

 

 

 

вычисляется интеграл функции f (x) = e

x

 

на интервале [0,1] путем дробления ин­

тервала на 10 частей, а вызовом

 

 

 

 

I2:=Integr(f2,-1,1,20);

 

 

 

 

вычисляется интеграл функции f (x) =

ex

на интервале [–1,1] путем дробления

интервала на 20 частей.

 

 

 

 

Конец примера.

 

 

 

 

Еще один полезный способ использования набора процедур (или функций) – описание массива процедур (функций). Такой массив может описываться как пере­ менная или как константа.

Пример 3.26. Пусть в программе описан константный массив функций f1 и f2

из примера 3.25:

 

 

 

 

 

 

const af:array[1..2]of func=(f1,f2);

(3.31)

Тогда интеграл I1 можно вычислить вызовом

 

I1:=Integr(af[1],0,1,10);

 

а интеграл I2 – вызовом

 

I2:=Integr(af[2],-1,1,10);

 

Если же в программе массив функций описан в виде массива-переменной

 

 

 

 

 

 

var af:array[1..2]of func;

(3.32)

то перед вызовами функции Integr необходимы присваивания af[1]:=f1; af[2]:=f2;

При этом непосредственный вызов функции, являющейся i–м элементом масси­

ва, записывается в виде

Y:=af[i](x);

где x – аргумент функции.

Конец примера.

Замечание. При использовании процедурных переменных в трансляторе Turbo Pascal необходимо установить опцию “Force for calls” в меню “Options”, “Code generations”.

67

3.6 Алгоритмы построения графиков функций

Информационная модель графического экрана. Изображение на экране компьютера растровое, т.е. представляет собой матрицу из крошечных прямоуголь­ ников (пикселей). Каждый пиксель характеризуется двумя целочисленными коорди­ натами (x, y). При этом 0 ≤ x A – 1, 0 ≤ y B – 1, где A, B – количество рядов пикселей по горизонтали и вертикали соответственно. Нумерация рядов – слева направо и сверху вниз, поэтому нуль системы координат экрана находится в левом верхнем углу.

Числа A, B определяются графическим режимом работы компьютера. Чем больше эти числа, тем точнее изображаются линии и фигуры на экране. Пиксель ха­ рактеризуется не только координатами, но и цветом (точнее, его номером). Количе­ ство возможных цветов также определяется графическим режимом. Таким образом, информационной моделью графического экрана является целочисленный двумерный массив. Число его элементов равно произведению AB. Матрица хранится в видеопа­ мяти компьютера. Для задания пикселю экрана некоторого цвета необходимо соот­ ветствующей ячейке видеопамяти присвоить номер этого цвета.

Современные компьютеры способны работать в нескольких графических режи­ мах. Количество различных цветов в них характеризуется числами 24 = 16, 28 = 256, 215 = 32768, 216 = 65536, 224 = 16777216. Показатель степени двойки задает количе­ ство бит, необходимых для записи номера цвета пикселя. Таким образом, длина ячей­ ки видеопамяти может быть от 1/2 до 3–х байт.

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

Самый простой для Windows тип графического экрана (который был доступен еще в DOS) называется VGA (virtual graphic array – виртуальный графический мас­ сив). В VGA для записи номера цвета используется 4 бита. Стандартное кодирование цвета в VGA следующее: первый, старший бит задает повышенную или нормальную яркость для всех видов цветовых элементов, второй бит – включение красного, тре­ тий – зеленого, четвертый – синего элемента. В разделе А.3 приложения А приведе­ ны все 16 цветов VGA и названия обозначающих их констант в графической библио­ теке. (Вообще говоря, в VGA можно задавать палитру из 16-ти цветов, когда в изображении может одновременно присутствовать только 16 цветов, но каждый цвет задается из набора 218 = 262144 цветов).

При использовании графического экрана VGA можно работать в одном из трех графических режимов, с размером растра 640×480 (высокий VGA, vgahi), 640×350 (средний VGA, vgamed) и 640×200 (низкий VGA, vgalo). Геометрические разме­ ры стандартных мониторов имеют соотношение сторон 4:3, поэтому в высоком ре­

68

жиме VGA пиксели имеют квадратную форму, а в среднем и низком – прямоуголь­ ную. В Windows можно использовать графические режимы с большим разрешением: 800×600, 1024×768, 1280×1024, 1600×1200 и более, однако при использовании графи­ ческой библиотеки Turbo Pascal эти режимы недоступны.

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

Краткое описание основных процедур графической библиотеки приведено в раз­ деле А.3 приложения А.

Управление графическим выводом. Процедуры стандартного вывода изобра­ жают символы в текстовом режиме монитора. В этом режиме символы на экране располагаются по строчкам (в основном текстовом режиме 25 строчек по 80 симво­ лов в каждой). Таким образом, экран отображает матрицу символов, причем все сим­ волы (включая промежутки между ними) имеют одинаковые размеры.

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

графическим драйвером. Для режимов VGA необходим графический драйвер, име­ ющий название egavga.bgi.

Общую структуру программы, отображающей данные в графическом виде, мож­ но представить в следующем виде:

 

 

 

uses graph;

{подключение графической библиотеки}

var

gt,gm:integer;

 

{переменные для включения графики}

 

begin

gt:=vga; gm:=vgahi; {выбор графического режима}

 

initgraph(gt,gm,'');

{включение графического режима}

 

if graphresult=grok

{если удалось включить графику}

 

then begin

 

{вывод графиского изображения}

(3.33)

 

. . .

 

 

 

readln;

 

{ожидание нажатия клавиши enter}

 

 

closegraph

 

{возврат в текстовый режим}

 

end.end

 

 

 

Рисование графиков функций. При рисовании графика функции приходится учитывать множество требований, причем нередко нарушение даже одного из них приводит к неудовлетворительному результату. В частности, изображение необходи­ мо масштабировать таким образом, чтобы оно занимало почти всю полезную пло­ щадь экрана и не выходило за его пределы. Кроме того, чтобы было понятно, для ка­

69

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

Пример 3.27. Пусть функция, для которой требуется нарисовать график, описана на Паскале, как функция f(x) от одного вещественного аргумента, вырабатываю­ щая вещественное значение. Кроме того, заданы границы a, b интервала (a < b), для которого рисуется график, и шаг d изменения аргумента для вычисления значе­ ний функции.

График функции можно представить в виде ломаной, последовательно соединя­ ющей точки (a, f(a)), (a+d, f(a+d)), (a+2*d, f(a+2*d)), …, (b, f(b)). Среди

вычисленных значений ординат функции f(x) можно определить минимальное fmin и максимальное fmax значение.

После этого можно привязать точки графика к координатам экрана, масштабируя их по оси абсцисс и ординат. Пусть график располагается на экране (с разрешением 640×480) внутри прямоугольника 600×450 пикселей с отступом слева на 20 и сверху

на 15 пикселей. Тогда пересчет точки (xi, yi) в координаты экрана (X, Y)

можно вы­

полнить по формулам

 

 

 

 

 

X = 20 +

xi − a

600,

Y = 15 +

fmax − yi

450.

(3.22)

b − a

 

 

 

 

fmax − fmin

 

Алгоритм рисования функции запишется в виде цикла:

x1:=a; y1:=f(a); while x1<b-eps do

begin x2:=x1+d; y2:=f(x2); line(20+round((x1-a)/(b-a)*600),

15+round((ymax-y1)/(ymax-ymin)*450), (3.34)

20+round((x2-a)/(b-a)*600), 15+round((ymax-y2)/(ymax-ymin)*450));

x1:=x2; y1:=y2; end;

Значение eps должно быть на порядок меньше, чем d, чтобы нейтрализовать накопленные в вещественной переменной x1 ошибки округления (если из b не вы­ читать eps, то последнее вычисленное значение x1 может оказаться почти на d больше, чем b). Процедура line отображает на растре прямолинейный отрезок между точками (x1, y1) и (x2, y2), масштабированными по формулам (3.22) к раз­

мерам графика.

Пусть также выполняются условия: a 0 ≤ b, fmin 0 ≤ fmax. Тогда можно изобразить оси координат для графика и разметить их. Рисование координатных осей X и Y выполняется алгоритмом (3.35). Для разметки оси абсцисс должна быть зада­

на величина шага разметки dx, а для разметки оси ординат – величина шага размет­ ки dy.

70

line(20,15+round(ymax/(ymax-ymin)*450), 620,15+round(ymax/(ymax-ymin)*450));

line(20-round(a/(b-a)*600),15, (3.35) 20-round(a/(b-a)*600),465);

В алгоритме (3.36) изображаются поперечные штрихи на оси X и числовые под­ писи около штрихов. Штрихи изображаются отрезками длиной по 10 пикселей.

x1 := a;

while x1<b+0.0001 do begin

line(20+round((x1-a)/(b-a)*600), 10+round(ymax/(ymax-ymin)*450), 20+round((x1-a)/(b-a)*600), 20+round(ymax/(ymax-ymin)*450)); (3.36)

str(x1:3:0,st); outtextxy(20+round((x1-a)/(b-a)*600),

20+round(ymax/(ymax-ymin)*450),st); x1 := x1+dx;

end;

В алгоритме (3.37) аналогично изображаются и подписываются штрихи на оси Y, при этом пропускается подпись около начала координат, которая уже изображена в алгоритме (3.36).

y1 :=ymi;

while y1<yma+0.0001 do begin

line(15-round(a/(b-a)*600), 15+round((ymax-y1)/(ymax-ymin)*450), 25-round(a/(b-a)*600), 15+round((ymax-y1)/(ymax-ymin)*450)); (3.37)

str(y1:3:0,st); if st<>' 0' then

outtextxy(25-round(a/(b-a)*600), 15+round((ymax-y1)/(ymax-ymin)*450),st);

y1 := y1+dy; end;

Стандартная процедура str преобразует первый аргумент (вещественное чис­ ло) в строку символов (второй аргумент st). Преобразование выполняется в соответ­ ствии с указанным для первого аргумента форматом (3 символа для изображения