Костюк - Основы программирования
.pdf61
Рис. 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 определяются графическим режимом работы компьютера. Чем больше эти числа, тем точнее изображаются линии и фигуры на экране. Пиксель ха рактеризуется не только координатами, но и цветом (точнее, его номером). Количе ство возможных цветов также определяется графическим режимом. Таким образом, информационной моделью графического экрана является целочисленный двумерный массив. Число его элементов равно произведению A∙B. Матрица хранится в видеопа мяти компьютера. Для задания пикселю экрана некоторого цвета необходимо соот ветствующей ячейке видеопамяти присвоить номер этого цвета.
Современные компьютеры способны работать в нескольких графических режи мах. Количество различных цветов в них характеризуется числами 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 символа для изображения