Слайд 2
Алгоритмизация и программирование
§ 38. Целочисленные алгоритмы
Слайд 3
Решето Эратосфена
Эратосфен Киренский
(Eratosthenes, Ερατοσθδνη)
(ок. 275-194 до н.э.)
Новая
версия – решето Аткина .
2 3 4 5
6 7 8 9 10 11 12 13 14 15 16
Алгоритм:
начать с k = 2
«выколоть» все числа через k, начиная с 2·k
перейти к следующему «невыколотому» k
если k <= N , то перейти к шагу 2
напечатать все числа, оставшиеся «невыколотыми»
высокая скорость, количество операций
нужно хранить в памяти все числа от 1 до N
O((N·log N)·log log N )
2
3
k·k
k·k <= N
Слайд 4
Решето Эратосфена
Задача. Вывести все простые числа от 2
до N.
Объявление переменных:
цел i, k, N = 100
логтаб A[2:N]
Сначала все невычеркнуты:
нц для i от 2 до N
A[i]:= да
кц
const N = 100;
var i, k: integer;
A: array[2..N]
of boolean;
for i:= 2 to N do
A[i]:= True;
Слайд 5
Решето Эратосфена
Вычёркивание непростых:
k:= 2
нц пока k*k
если A[k] то
i:= k*k
нц пока
i <= N
A[i]:= нет
i:= i + k
кц
все
k:= k + 1
кц
k:= 2;
while k*k <= N do begin
if A[k] then begin
i:= k*k;
while i <= N do begin
A[i]:= False;
i:= i + k
end
end;
k:= k + 1
end;
Слайд 6
Решето Эратосфена
Вывод результата:
нц для i от 2 до
N
если A[i] то
вывод i, нс
все
кц
for
i:= 2 to N do
if A[i] then
writeln ( i );
Слайд 7
«Длинные» числа
Ключи для шифрования: ≥ 256 битов.
Целочисленные типы
данных: ≤ 64 битов.
Длинное число – это число, которое
не помещается в переменную одного из стандартных типов данных языка программирования.
«Длинная арифметика» – алгоритмы для работы с длинными числами.
Слайд 8
«Длинные» числа
A = 12345678
нужно хранить длину числа
неудобно вычислять
(с младшего разряда!)
неэкономное расходование памяти
Обратный порядок элементов:
Слайд 9
«Длинные» числа
Упаковка элементов:
12345678 = 12·10002 + 345·10001 +
678·10000
система счисления с основанием 1000!
от –231 = – 2
147 483 648 до 231 – 1 = 2 147 483 647.
longint:
должны помещаться все промежуточные результаты!
A = 12345678
Слайд 10
Вычисление факториала
Задача 1. Вычислить точно значение факториала
100!
= 1·2·3·…·99·100
1·2·3·…·99·100 < 100100
201 цифра
6 цифр в ячейке
⇒ 34 ячейки
цел N = 33
целтаб A[0:N]
const N = 33;
var A: array[0..N]
of longint;
Основной алгоритм:
[A]:= 1
нц для k от 2 до 100
[A]:= [A] * k
кц
длинное число
основание 1000000
Слайд 11
Вычисление факториала
основание d = 1 000 000
[A] = 12345678901734567
734 567·3 =
2 203 701
*3
остаётся в A[0]
r := перенос в A[1]
s:=
A[0]*k
A[0]:= mod(s,d)
r:= div(s,d)
s:= A[0]*k;
A[0]:= s mod d;
r:= s div d;
s:= A[1]*k + r
Слайд 12
Вычисление факториала
r:= 0
нц для i от 0 до
N
s:= A[i]*k + r
A[i]:= mod(s,d)
r:= div(s,d)
кц
r:= 0;
for i:= 0 to N do begin
s:= A[i]*k + r;
A[i]:= s mod d;
r:= s div d
end;
Умножение «длинного» числа на k:
Вычисление 100!:
нц для k от 2 до 100
...
кц
for k:=2 to 100 do
begin
...
end;
Слайд 13
Вывод длинного числа
[A] = 1000002000003
найти старший ненулевой разряд
вывести
этот разряд
вывести все следующие разряды, добавляя лидирующие нули до
6 цифр
i:= N
нц пока A[i] = 0
i:= i - 1
кц
i:= N;
while A[i] = 0 do
i:= i - 1;
вывод A[i]
write( A[i] );
Слайд 14
Вывод длинного числа
нц для k от i-1 до
0 шаг -1
Write6(A[k])
кц
for k:=i-1 downto 0 do
Write6(A[k]);
Вывод остальных разрядов:
со старшего
Write6:
x = 12345
012345
x div 100000
x mod 100000
Слайд 15
Вывод длинного числа
Вывод числа с лидирующими нулями:
алг Write6
(цел x)
нач
цел M, xx
xx:= x
M:= 100000
нц пока M > 0
вывод div(xx, M)
xx:= mod(xx, M)
M:= div(M, 10)
кц
кон
Слайд 16
Вывод длинного числа
Вывод числа с лидирующими нулями:
procedure Write6(x:
longint);
var M: longint;
begin
M:= 100000;
while M > 0 do begin
write(x div M);
x:= x mod M;
M:= M div 10
end
end;
Слайд 17
Алгоритмизация и программирование
§ 39. Структуры (записи)
Слайд 18
Зачем нужны структуры?
Книги в библиотеках:
автор
название
количество экземпляров
…
символьные строки
целое число
Задачa:
объединить разнотипные данные в один блок.
Несколько массивов:
var authors: array[1..N]
of string;
titles: array[1..N] of string;
count: array[1..N] of integer;
...
неудобно работать (сортировать и т.д.), ошибки
Слайд 19
Структуры (записи)
Структура – это тип данных, который может
включать в себя несколько полей – элементов разных типов
(в том числе и другие структуры).
type
TBook = record
author: string[40];{ автор, строка }
title: string[80]; { название, строка}
count: integer { количество, целое }
end;
новый тип данных
структура (запись)
Слайд 20
Объявление структур
const N = 100;
var B: TBook; {
одна структура }
Books: array[1..N] of TBook; {
массив }
writeln(sizeof(TBook)); { 124 }
writeln(sizeof(B)); { 124 }
writeln(sizeof(Books)); { 12400 }
type
TBook = record
author: string[40];
title: string[80];
count: integer
end;
2 байта (FreePascal)
40 + 1 (размер) байт
80 + 1 (размер) байт
Слайд 21
Обращение к полям структур
B.author { поле author структуры
B }
Точечная нотация:
Books[5].count { поле count структуры
Books[5] }
writeln(sizeof(B.author)); { 41 }
writeln(sizeof(B.title)); { 81 }
writeln(sizeof(B.count)); { 2 }
read(B.author); { ввод полей }
read(B.title);
read(B.count);
writeln(B.author, ' ', { вывод }
B.title, '. ', B.count, ' шт.' );
Слайд 22
Обращение к полям структур
B.author:= 'Пушкин А.С.';
B.title:= 'Полтава';
B.count:= 1;
p:=
Pos(' ', B.author);
fam:= Copy(B.author, 1, p-1); { фамилия }
B.count:=
B.count – 1; { одну книгу взяли }
if B.count = 0 then
writeln('Этих книг больше нет!');
Присваивание:
Использование:
Слайд 23
Запись структур в файлы
'Пушкин А.С.';'Полтава';12
'Лермонтов М.Ю.';'Мцыри';8
Текстовые файлы:
символ-разделитель
Типизированные файлы:
var
F: file of TBook;
Assign(F, 'books.dat');
Rewrite(F);
B.author:= 'Тургенев И.С.';
B.title:=
'Муму';
B.count:= 2;
write(F, B);
Close(F);
for i:=1 to N do
write(F, Books[i]);
только для TBook!
Слайд 24
Чтение структур из файла
Assign(F, 'books.dat');
Reset(F);
Read(F, B);
writeln(B.author,', ',B.title,
', ',B.count);
Close(F);
только для
TBook!
for i:= 1 to N do { известное количество }
read(F, Books[i]);
i:= 0;
while not Eof(F) do begin { до конца файла }
i:= i + 1;
Read(F, Books[i])
end;
счётчик прочитанных структур
Слайд 25
Сортировка структур
Ключ – фамилия автора:
for i:=1 to N-1
do
for j:=N-1 downto i do
if Books[j].author
> Books[j+1].author
then begin
B:= Books[j];
Books[j]:= Books[j+1];
Books[j+1]:= B
end;
структуры перемещаются в памяти
B:= Books[j];
Books[j]:= Books[j+1];
Books[j+1]:= B
var B: TBook;
Слайд 26
Указатели
Указатель – это переменная, в которой можно сохранить
адрес любой переменной заданного типа.
type PBook = ^TBook;
pointer
указатель на
переменную типа TBook
var P: PBook;
P:=@B;
P:=@Books[3];
P^.author ⇔ Books[3].author
Слайд 27
Сортировка по указателям
var p: array[1..N] of PBook;
for i:=1
to N do
p[i]:= @Books[i];
Задача – переставить указатели:
Слайд 28
Сортировка по указателям
for i:= 1 to N-1 do
for j:= N-1 downto i do
if p[j]^.author
> p[j+1]^.author
then begin
p1:= p[j]; p[j]:= p[j+1];
p[j+1]:= p1
end;
var p1: PBook;
обращение к полям через указатели
Вывод результата:
for i:=1 to N do
writeln(p[i]^.author, '; ',
p[i]^.title, '; ',
p[i]^.count);
переставляем указатели!
Слайд 29
Алгоритмизация и программирование
§ 40. Множества
2-е издание
Слайд 30
Зачем нужны множества?
Задача. Определить количество знаков препинания в
символьной строке.
count:= 0;
for i:=1 to Length(s) do
if (s[i]='.') or (s[i]=',')
or (s[i]=';') or (s[i]=':')
or (s[i]='!') or (s[i]='?') then
count:= count + 1;
Использование множества:
if s[i] in ['.', ',', ';', ':', '!', '?'] then
count:= count + 1;
входит во
множество
['.', ',', ';', ':', '!', '?']
(s[i]='.') or (s[i]=',')
or (s[i]=';') or (s[i]=':')
or (s[i]='!') or (s[i]='?')
Слайд 31
Использование множеств
if s[i] in ['0'.. '9'] then
count:= count + 1;
диапазон
if s[i] in ['a'..'z', '0'..'9',
'.', '-', '_'] then
{ символ правильный }
var digits: set of '0'..'9';
Переменная типа «множество»:
множество цифр
Слайд 32
Использование множеств
Задача. Вывести все различные цифры, присутствующие в
символьной строке s.
var s: string;
i: integer;
c: char;
digits: set of '0'..'9';
begin
readln(s);
digits:=[]; { пустое множество }
for i:=1 to Length(s) do
if s[i] in ['0'..'9']
then
for c:='0' to '9' do
if c in digits then writeln(c)
end.
digits:= digits + [s[i]];
сложить два множества
вывод через перебор
Слайд 33
Использование множеств
var cs: set of char;
bs:
set of byte;
до 255 элементов
Имена для элементов множества:
type TFontStyles
= (fsBold, fsItalic,
fsUnderline);
стили шрифта
жирный
курсив
подчёркивание
var fs: set of TFontStyles;
Операции с множеством:
fs:= [fsBold, fsItalic]; { присвоить значение}
fs:= fs + [fsUnderline]; { добавить элементы }
fs:= fs - [fsItalic]; { исключить элементы }
Слайд 34
Вывод множества на экран
type TFontStyles = (fsBold, fsItalic,
fsUnderline);
var style: TFontStyles;
fs:= [fsBold, fsItalic];
for style:=fsBold to fsUnderline do
if style in fs then
write(1)
else write(0);
первый
последний
110
fsBold
fsItalic
fsUnderline
Слайд 35
Сравнение множеств
Равенство/неравенство:
if A = B then write('A =
B'); { нет }
if B = C then write('B
= C'); { да }
var A, B, C: set of 0..9;
...
A:= [1,2,3,4]; B:= [2,3]; C:= [2,3]
if A <> B then write('A <> B'); { да }
if C <> B then write('C <> B'); { нет }
Включение одного в другое:
if A >= B then write('A >= B'); { да }
if C <= B then write('C <= B'); { да }
if A > B then write('A > B'); { да }
if C < B then write('C < B'); { нет }
Слайд 36
Множества в памяти
Пустое множество:
var digits: set of 0..9;
digits:=
[];
Непустое множество:
digits:= [1, 3, 5, 7];
Пересечение множеств:
digits:= [1, 3,
5, 7] *
[2, 3, 4];
AND
логическое умножение
[3]
Слайд 37
[1, 2, 3, 4, 5, 7]
Множества в памяти
Объединение
множеств:
digits:= [1, 3, 5, 7] +
[2, 3, 4];
OR
логическое сложение
Вычитание множеств:
digits:= [1, 3, 5, 7] -
[2, 3, 4];
[1, 5, 7]
Слайд 38
Алгоритмизация и программирование
§ 40. Динамические массивы
Слайд 39
Чем плох обычный массив?
const N = 100;
var A:
array[1..N] of integer;
статический массив
память выделяется при трансляции
нужно заранее знать
размер
изменить размер нельзя
Задача. В файле записаны фамилии (сколько – неизвестно!). Вывести их в другой файл в алфавитном порядке.
выделить заранее большой блок (с запасом)
выделять память во время работы программы (динамически!)
Слайд 40
Динамические структуры данных
создавать новые объекты в памяти
изменять их
размер
удалять из памяти, когда не нужны
… позволяют
Задача. Ввести с
клавиатуры целое значение N, затем – N целых чисел, и вывести на экран эти числа в порядке возрастания.
{ прочитать данные из файла в массив }
{ отсортировать их по возрастанию }
{ вывести массив на экран }
Слайд 41
Динамические массивы (FreePascal)
Объявление:
var A: array of integer;
размер не
указан
Выделение памяти:
read(N);
SetLength(A, N);
установить длину
[0.. N-1]
Чтение данных:
for i:=0 to N-1
do read(A[i]);
for i:=0 to High(A) do read(A[i]);
наибольший индекс
Слайд 42
Динамические массивы (FreePascal)
Определение длины:
writeln( Length(A) );
Освобождение памяти:
SetLength(A, 0);
Динамические
матрицы:
var A: array of array of integer;
SetLength(A, 4, 3);
writeln(
High(A) );
3
writeln( High(A[0]) );
2
Слайд 43
Динамические массивы (FreePascal)
В подпрограммах:
procedure printArray(X: array of integer);
begin
for i:= 0 to High(X) do
write(X[i],
' ')
end;
Слайд 44
Расширение массива
Задача. С клавиатуры вводятся натуральные числа, ввод
заканчивается числом 0. Нужно вывести на экран эти числа
в порядке возрастания.
Расширение по 1 элементу:
read(x);
while x <> 0 do begin
SetLength(A, Length(A)+1);
A[High(A)]:= x;
read(x)
end;
Слайд 45
Расширение массива
Расширение по 10 элементов:
N:= 0; { счётчик
элементов }
read(x);
while x 0 do begin
if N
> High(A) then
SetLength(A, Length(A)+10);
A[N]:= x;
N:= N + 1;
read(x)
end;
Слайд 46
Как это работает?
Эксперимент:
SetLength(A, 100);
write(sizeof(A));
write(100*sizeof(integer));
4
200
размер массива
type TArray = record
data: array of integer;
size:
integer
end;
Слайд 47
Как это работает?
var A: array of array of
integer;
SetLength(A, 10);
выделить массив указателей
for i:=0 to High(A) do
SetLength(A[i],
i+1);
Строки разной длины:
writeln(Length(A[0])); { 1 }
writeln(Length(A[9])); { 10 }
Слайд 48
Алгоритмизация и программирование
§ 41. Списки
Слайд 49
Зачем нужны списки?
Задача. В файле находится список слов,
среди которых есть повторяющиеся. Каждое слово записано в отдельной
строке. Построить алфавитно-частотный словарь: список слов в алфавитном порядке, справа от каждого слова должно быть указано, сколько раз оно встречается в исходном файле.
Список – это упорядоченный набор элементов одного типа, для которого введены операции вставки (включения) и удаления (исключения).
Слайд 50
Алгоритм (псевдокод)
нц пока есть слова в файле
прочитать
очередное слово
если оно есть в списке то
увеличить на 1 счётчик для этого слова
иначе
добавить слово в список
записать 1 в счетчик слова
все
кц
Слайд 51
Хранение данных
type
TPair = record
word:
string; { слово }
count: integer
{ счётчик }
end;
Пара «слово-счётчик»:
type
TWordList = record
data: array of TPair;
size: integer
end;
Список таких пар:
динамический массив
количество слов в списке
Слайд 52
Хранение данных
var L: TWordList;
Переменная-список:
SetLength(L.data, 0);
L.size:= 0;
Начальные значения:
Вывод результата:
Assign(F,
'output.dat');
Rewrite(F);
for p:=0 to L.size-1 do
writeln(F, L.data[p].word, ': ',
L.data[p].count);
Close(F);
автомат: 1
ананас: 12
...
Слайд 53
Основной цикл
while not Eof(F) { пока не
конец файла }
do begin
readln(F, s);
{ читаем слово }
p:= Find(L, s); { ищем его в словаре}
if p >= 0 then { если нашли... }
Inc(L.data[p].count)
{ ...увеличить счётчик }
else begin { иначе... }
p:= FindPlace(L, s); { найти место }
InsertWord(L, p, s); { вставить в список }
end
end;
Слайд 54
Поиск слова
function Find(L: TWordList;
word: string): integer;
var i: integer;
begin
Find:=
-1;
for i:=0 to L.size-1 do
if L.data[i].word = word then begin
Find:= i;
break
end
end;
вернуть -1, если нет в списке
вернуть номер элемента в списке
выйти из цикла
Слайд 55
Поиск места вставки
function FindPlace(L: TWordList;
word: string):
integer;
var i, p: integer;
begin
p:= -1;
for i:=0 to L.size-1 do
if L.data[i].word > word then begin
p:= i;
break
end;
if p < 0 then p:= L.size;
FindPlace:= p
end;
-1: не найдено
запомнить номер
если не найдено, вставить в конец
первое слово «больше» заданного
Слайд 56
Вставка слова
дерево
for i:=L.size-1 downto k+1 do
L.data[i]:= L.data[i-1];
Сдвиг
вниз:
с последнего
Слайд 57
Вставка слова
procedure InsertWord(var L: TWordList;
k: integer;
word: string);
var i: integer;
begin
IncSize(L);
for i:=L.size-1 downto k+1 do
L.data[i]:= L.data[i-1];
L.data[k].word:= word; { записать слово }
L.data[k].count:= 1 { встретилось 1 раз }
end;
список меняется
увеличить размер, если нужно
сдвиг вниз
Слайд 58
Расширение списка
procedure IncSize (var L: TWordList);
begin
Inc(L.size);
if
L.size > Length(L.data) then
SetLength(L.data, Length(L.data) + 10)
end;
список меняется
если новый размер больше размера массива
добавить 10 элементов
Слайд 59
Вся программа
program AlphaList;
{ объявления типов TPair и
TWordList }
var F: text;
w: string;
L:
TWordList;
p: integer;
{ процедуры и функции }
begin
SetLength(L.data, 0);
L.size:= 0;
Assign(F, 'input.dat');
Reset(F);
{ основной цикл: составление списка слов }
Close(F);
{ вывод результата в файл output.dat }
end.
Слайд 60
Модули
program AlphaList;
{ процедура 1 }
{
процедура 2 }
{ процедура 3 }
{ процедура
4 }
{ ...}
{ процедура N-1 }
{ процедура N }
begin
{ программа }
end.
program AlphaList;
uses WordList;
begin
{ программа }
end.
unit WordList;
{ процедура 1 }
{ процедура 2 }
{ процедура 3 }
{ процедура 4 }
{ ...}
{ процедура N-1 }
{ процедура N }
end.
проще разбираться
(«разделяй и властвуй»)
модуль пишет другой программист
Слайд 61
Структура модуля
unit WordList;
interface
…
implementation
…
end.
«интерфейс» – общедоступная информация:
объявление
типов данных
объявления процедур и функций
«реализация» – внутренняя информация модуля:
код
процедур и функций
внутренние данные
Слайд 62
Модуль WordList
unit WordList;
interface
{ объявления типов TPair и
TWordList }
function Find(L: TWordList;
word: string): integer;
function FindPlace(L: TWordList;
word: string): integer;
procedure InsertWord(var L: TWordList;
k: integer; word: string);
implementation
{ код процедур и функций }
end.
Слайд 63
Подключение модуля
program AlphaList;
uses WordList; { подключение модуля }
var
F: text;
s: string;
L: TWordList;
p: integer;
begin
{ тело основной программы }
end.
тип известен из интерфейса модуля
можно использовать все процедуры, объявленные в интерфейсе модуля
Слайд 64
Связные списки
узлы могут размещаться в разных местах в
памяти
только последовательный доступ
Рекурсивное определение:
пустой список – это список
список –
это узел и связанный с ним список
конец списка
Слайд 65
Связные списки
Head
Циклический список:
Двусвязный список:
Head
Tail
«хвост»
обход в двух направлениях
сложнее вставка
и удаление
Слайд 66
Связные списки: структуры данных
Односвязный список:
Двусвязный список:
type
PNode
= ^TNode;
TNode = record
data: integer;
next: PNode
end;
type
PNode = ^TNode;
TNode = record
data: integer;
next: PNode
end;
prev,
ссылка на следующий узел
указатель
ссылки на предыдущий (previous) и следующий узлы
Слайд 67
Алгоритмизация и программирование
§ 42. Стек, дек, очередь
Слайд 68
Что такое стек?
Стек (англ. stack – стопка) –
это линейный список, в котором элементы добавляются и удаляются
только с одного конца («последним пришел – первым ушел»).
LIFO = Last In – First Out.
Системный стек:
адреса возврата из подпрограмм
передача аргументов подпрограмм
хранение локальных переменных
Слайд 69
Реверс массива
Задача. В файле записаны целые числа. Нужно
вывести их в другой файл в обратном порядке.
нц пока
файл не пуст
прочитать x
добавить x в стек
кц
нц пока стек не пуст
вытолкнуть число из стека в x
записать x в файл
кц
1
2
3
4
5
5
4
3
2
1
Слайд 70
Использование динамического массива
type TStack = record
data: array of integer;
size:
integer
end;
procedure Push(var S: TStack; x: integer);
begin
if S.size > High(S.data) then
SetLength(S.data, Length(S.data) + 10);
S.data[S.size]:= x;
S.size:= S.size + 1
end;
изменяется
«Втолкнуть» x в стек:
Слайд 71
Использование динамического массива
function Pop(var S:TStack): integer;
begin
S.size:= S.size-1;
Pop:= S.data[S.size]
end;
изменяется
procedure InitStack(var S: TStack);
begin
SetLength(S.data, 0);
S.size:= 0
end;
«Вытолкнуть»
из стека в x :
Инициализация стека :
Слайд 72
Использование динамического массива
InitStack(S);
while not Eof(F) do begin
read(F,
x);
Push(S, x)
end;
Заполнение стека:
for i:=0 to S.size-1 do begin
x:= Pop(S);
writeln(F, x)
end;
Вывод результата в файл:
var F: text;
Слайд 73
Вычисление арифметических выражений
(5+15)/(4+7-1)
инфиксная форма (знак операции между
данными)
первой стоит последняя операция (вычисляем с конца)
1920 (Я. Лукашевич):
префиксная форма
(знак операции перед данными)
/ + 5 15 - + 4 7 1
/ 20 - + 4 7 1
/ 20 - 11 1
/ 20 10
2
не нужны скобки
Слайд 74
Вычисление арифметических выражений
(5+15)/(4+7-1)
1950-е: постфиксная форма
(знак операции
после данных)
не нужны скобки
вычисляем с начала
5 15 + 4
7 + 1 - /
20 4 7 + 1 - /
20 11 1 - /
20 10 /
2
Слайд 75
Использование стека
5
15
+
4
7
+
1
-
/
5 15 + 4 7 + 1
- /
если число – «втолкнуть» в стек
если операция –
выполнить с верхними элементами стека
Слайд 76
Скобочные выражения
Задача. Вводится символьная строка, в которой записано
некоторое (арифметическое) выражение, использующее скобки трёх типов: ( ),
[ ] и { }. Проверить, правильное ли расставлены скобки.
()[{()[]}]
[()
)(
[()}
([)]
Для одного типа скобок:
( ) ( ( ) ( ( ) ) )
счётчик 0
1
0
1
2
1
2
3
2
1
0
счётчик всегда ≥ 0
в конце счётчик = 0
({[)}]
Слайд 77
Скобочные выражения (стек)
когда встретили закрывающую скобку, на вершине
стека лежит соответствующая открывающая
в конце работы стек пуст
если открывающая
скобка – «втолкнуть» в стек
если закрывающая скобка – снять парную со стека
Слайд 78
Скобочные выражения (стек)
type TStack = record
data: array of char;
size:
integer
end;
Модель стека:
Cтек пуст:
function isEmpty(S: TStack): boolean;
begin
isEmpty:= (S.size = 0)
end;
Слайд 79
Скобочные выражения (стек)
const L = '([{'; { открывающие
скобки }
R = ')]}'; { закрывающие
скобки }
var S: TStack;
p, i: integer;
str: string; { рабочая строка }
err: boolean; { признак ошибки }
c: char;
Константы и переменные:
Вывод результата:
if not err then
writeln('Выражение правильное.')
else writeln('Выражение неправильное.');
Слайд 80
Скобочные выражения (стек)
for i:=1 to Length(str) do begin
p:= Pos(str[i], L);
if p > 0 then Push(S,
str[i]);
p:= Pos(str[i], R);
if p > 0 then begin
if isEmpty(S) then err:= True
else begin
c:= Pop(S);
if p <> Pos(c,L) then err:= True
end;
if err then break
end
end;
открывающую скобку в стек
если закрывающая скобка…
если не та скобка…
Слайд 81
Что такое очередь?
Очередь – это линейный список, для
которого введены две операции:
• добавление элемента в конец
• удаление первого элемента
FIFO
= Fist In – First Out.
Применение:
очереди сообщений в операционных системах
очереди запросов ввода и вывода
очереди пакетов данных в маршрутизаторах
…
Слайд 82
Заливка области
Задача. Рисунок задан в виде матрицы A,
в которой элемент A[y,x] определяет цвет пикселя на пересечении
строки y и столбца x. Перекрасить в цвет 2 одноцветную область, начиная с пикселя (x0,y0).
(2,1)
Слайд 83
Заливка: использование очереди
добавить в очередь точку (x0,y0)
запомнить цвет
начальной точки
нц пока очередь не пуста
взять из очереди
точку (x,y)
если A[y,x] = цвету начальной точки то
A[y,x]:= 2;
добавить в очередь точку (x-1,y)
добавить в очередь точку (x+1,y)
добавить в очередь точку (x,y-1)
добавить в очередь точку (x,y+1)
все
кц
Слайд 84
Очередь (динамический массив)
type TPoint = record
x, y: integer
end;
TQueue = record
data: array of TPoint;
size: integer
end;
function Point(x, y: integer): TPoint;
begin
Point.x:= x;
Point.y:= y
end;
Построение структуры «точка»:
Слайд 85
Очередь (динамический массив)
Добавить точку в очередь:
procedure Put(var Q:
TQueue; pt: TPoint);
begin
if Q.size > High(Q.data) then
SetLength(Q.data,
Length(Q.data) + 10);
Q.data[Q.size]:= pt;
Q.size:= Q.size + 1;
end;
расширить, если нужно
4
5
Слайд 86
Очередь (динамический массив)
Получить первую точку в очереди:
function Get(var
Q:TQueue): TPoint;
var i: integer;
begin
Get:= Q.data[0];
Q.size:= Q.size -
1;
for i:=0 to Q.Size - 1 do
Q.data[i]:= Q.data[i+1];
end;
уменьшить размер
продвинуть оставшиеся элементы
2
4
3
4
5
1
Слайд 87
Заливка
const XMAX = 5; YMAX = 5;
NEW_COLOR = 2;
var Q: TQueue;
x0, y0,
color: integer;
A: array[1..YMAX,1..XMAX] of integer;
pt: TPoint;
{ заполнить матрицу A }
x0:= 2; y0:= 1; { начать заливку отсюда }
color:= A[y0,x0]; { цвет начальной точки }
Put(Q, Point(x0,y0));
Константы и переменные:
Начало программы:
Слайд 88
Заливка (основной цикл)
while not isEmpty(Q) do begin
pt:=
Get(Q); { взять точку из очереди }
if A[pt.y,
pt.x] = color then begin
A[pt.y, pt.x]:= NEW_COLOR;
if pt.x > 1 then
Put(Q, Point(pt.x-1, pt.y));
if pt.x < XMAX then
Put(Q, Point(pt.x+1, pt.y));
if pt.y > 1 then
Put(Q, Point(pt.x, pt.y-1));
if pt.y < YMAX then
Put(Q, Point(pt.x, pt.y+1))
end
end;
пока очередь не пуста
Слайд 89
Очередь: статический массив
нужно знать размер
не двигаем элементы
голова
хвост
Удаление элемента:
Добавление
элемента:
Слайд 90
Очередь: статический массив
Замыкание в кольцо:
Очередь заполнена:
Очередь пуста:
Слайд 91
Что такое дек?
Дек – это линейный список, в
котором можно добавлять и удалять элементы как с одного,
так и с другого конца.
Моделирование:
статический массив (кольцо)
динамический массив
связный список
Слайд 92
Алгоритмизация и программирование
§ 43. Деревья
Слайд 93
Что такое дерево?
«Сыновья» А: B, C.
«Родитель» B:
A.
«Потомки» А: B, C, D, E, F,
G.
«Предки» F: A, C.
Корень – узел, не имеющий предков (A).
Лист – узел, не имеющий потомков (D, E, F, G).
Слайд 94
Рекурсивные определения
пустая структура – это дерево
дерево – это
корень и несколько связанных с ним отдельных (не связанных
между собой) деревьев
Двоичное (бинарное) дерево:
пустая структура – это двоичное дерево
двоичное дерево – это корень и два связанных с ним отдельных двоичных дерева («левое» и «правое» поддеревья)
Применение:
поиск в большом массиве неменяющихся данных
сортировка данных
вычисление арифметических выражений
оптимальное сжатие данных (метод Хаффмана)
Слайд 95
Деревья поиска
Ключ – это значение, связанное с узлом
дерева, по которому выполняется поиск.
слева от узла – узлы
с меньшими или равными ключами
справа от узла – узлы с большими или равными ключами
O(log N)
Двоичный поиск O(log N)
Линейный поиск O(N)
Слайд 96
Обход дерева
Обойти дерево ⇔ «посетить» все узлы по
одному разу.
⇒ список узлов
КЛП – «корень-левый-правый» (в прямом
порядке):
посетить корень
обойти левое поддерево
обойти правое поддерево
ЛКП – «левый-корень-правый» (симметричный):
посетить корень
обойти левое поддерево
обойти правое поддерево
ЛПК – «левый-правый-корень» (в обратном порядке):
посетить корень
обойти левое поддерево
обойти правое поддерево
Слайд 97
Обход дерева
ЛПК:
КЛП:
ЛКП:
* + 1 4 – 9 5
1
+ 4 * 9 - 5
1 4 + 9
5 - *
префиксная форма
инфиксная форма
постфиксная форма
Обход «в ширину»: «сыновья», потом «внуки», …
* + - 1 4 9 5
«в глубину»
Слайд 98
Обход КЛП – обход «в глубину»
записать в стек
корень дерева
нц пока стек не пуст
V:= выбрать узел
с вершины стека
посетить узел V
если у узла V есть правый сын то
добавить в стек правого сына V
все
если у узла V есть левый сын то
добавить в стек левого сына V
все
кц
Слайд 99
Обход КЛП – обход «в глубину»
*
+
1
4
–
9
5
Слайд 100
Обход «в ширину»
записать в очередь корень дерева
нц пока
очередь не пуста
V:= выбрать узел из очереди
посетить
узел V
если у узла V есть левый сын то
добавить в очередь левого сына V
все
если у узла V есть правый сын то
добавить в очередь правого сына V
все
кц
Слайд 101
Обход «в ширину»
(1+4)*(9-5)
*
+
-
1
4
9
5
голова очереди
Слайд 102
Вычисление арифметических выражений
40–2*3–4*5
В корень дерева нужно поместить последнюю
из операций с наименьшим приоритетом.
Слайд 103
Вычисление арифметических выражений
найти последнюю выполняемую операцию
если операций нет
то
создать узел-лист
выход
все
поместить операцию в корень дерева
построить левое
поддерево
построить правое поддерево
построить левое поддерево
построить правое поддерево
Построение дерева:
Слайд 104
Вычисление арифметических выражений
n1:= значение левого поддерева
n2:= значение правого
поддерева
результат:= операция(n1, n2)
значение левого поддерева
значение правого поддерева
Вычисление по
дереву:
Слайд 105
Использование связанных структур
Дерево – нелинейная структура ⇒ динамический
массив неудобен!
type
PNode = ^TNode; { указатель на узел
}
TNode = record { узел дерева }
data: string[20];
left, right: PNode { ссылки на сыновей }
end;
ссылка вперёд
Слайд 106
Работа с памятью
Выделить память для узла:
var p: PNode;
{ указатель на узел }
New(p);
Обращение к новому узлу (по
указателю):
p^.data:= s;
p^.left:= nil;
p^.right:= nil;
Освобождение памяти:
Dispose(p);
Слайд 107
Основная программа
var T: PNode; { ссылка на дерево
}
s: string; { строка с выражением }
{ процедуры и функции }
begin
readln(s);
T:= Tree(s); { построить дерево }
writeln('Результат: ',
Calc(T)); { вычисление }
end.
Слайд 108
Построение дерева
function Tree(s: string): PNode;
var k: integer;
begin
New(Tree);
{ выделить память }
k:=
LastOp(s); { вернет 0, если нет операции }
if k = 0 then begin { создать лист }
Tree^.data:= s;
Tree^.left:= nil;
Tree^.right:= nil
end
else begin { создать узел-операцию }
Tree^.data:= s[k];
Tree^.left:= Tree(Copy(s,1,k-1));
Tree^.right:= Tree(Copy(s,k+1,Length(s)-k))
end
end;
вернёт адрес нового дерева
Tree(Copy(s,1,k-1));
Tree(Copy(s,k+1,Length(s)-k))
Слайд 109
Вычисление по дереву
function Calc(Tree: PNode): integer;
var n1, n2,
res: integer;
begin
if Tree^.left = nil then
Val(Tree^.data,
Calc, res)
else begin
n1:= Calc(Tree^.left);
n2:= Calc(Tree^.right);
case Tree^.data[1] of
'+': Calc:= n1 + n2;
'-': Calc:= n1 - n2;
'*': Calc:= n1 * n2;
'/': Calc:= n1 div n2;
else Calc:= MaxInt
end
end
end;
Calc(Tree^.left);
Calc(Tree^.right);
Слайд 110
Приоритет операции
function Priority(op: char): integer;
begin
case op of
'+','-': Priority:= 1;
'*','/': Priority:= 2
else Priority:= 100
end
end;
Слайд 111
Последняя выполняемая операция
function LastOp(s: string): integer;
var i, minPrt:
integer;
begin
minPrt:= 50; { любое между 2 и 100
}
LastOp:= 0; { если нет операции }
for i:=1 to Length(s) do
if Priority(s[i]) <= minPrt then begin
minPrt:= Priority(s[i]);
LastOp:= i
end
end;
<=
вернёт номер символа
Слайд 113
Алгоритмизация и программирование
§ 44. Графы
Слайд 114
Что такое граф?
Граф – это набор вершин
и связей между ними (рёбер).
петля
Матрица смежности:
Список смежности:
( A(B, C),
B(A, C, D),
C(A, B, С, D),
D(B, C) )
Слайд 115
Связность графа
Связный граф – это граф, между
любыми вершинами которого существует путь.
Слайд 116
Дерево – это граф?
дерево
ABC ABDC
BCD CCC…
Дерево – это связный
граф без циклов (замкнутых путей).
Слайд 117
Взвешенные графы
Весовая матрица:
вес ребра
Слайд 118
Ориентированные графы (орграфы)
Рёбра имеют направление (начало и конец),
рёбра называю дугами.
Слайд 119
Жадные алгоритмы
Жадный алгоритм – это многошаговый алгоритм, в
котором на каждом шаге принимается решение, лучшее в данный
момент.
Задача. Найти кратчайший маршрут из А в F.
Слайд 120
Жадные алгоритмы
Задача. Найти кратчайший маршрут из А в
Слайд 121
Задача Прима-Крускала
Задача. Между какими городами нужно проложить линии
связи, чтобы все города были связаны в одну систему
и общая длина линий связи была наименьшей?
(минимальное остовное дерево)
Алгоритм Крускала:
начальное дерево – пустое
на каждом шаге добавляется ребро минимального веса, которое ещё не входит в дерево и не приводит к появлению цикла
Слайд 122
Раскраска вершин
4
B
2
1
2
9
7
8
1
3
D
E
F
A
C
ищем ребро минимальной длины среди всех рёбер,
концы которых окрашены в разные цвета;
найденное ребро (iMin,jMin) добавляется
в список выбранных, и все вершины, имеющие цвет col[jMin], перекрашиваются в цвет col[iMin].
Сделать N-1 раз:
for i:=1 to N do col[i]:= i;
каждой вершине свой цвет
Слайд 123
Раскраска вершин
const N = 6;
var { весовая
матрица }
W: array[1..N,1..N] of integer;
{ цвета вершин }
col: array[1..N] of integer;
{ номера вершин для выбранных ребер }
ostov: array[1..N-1,1..2] of integer;
i, j, k, iMin, jMin, min, c: integer;
Данные:
Вывод результата:
for i:=1 to N-1 do
writeln('(', ostov[i,1], ',',
ostov[i,2], ')');
Слайд 124
Раскраска вершин
for k:= 1 to N-1 do begin
{ поиск ребра с минимальным весом }
min:=
MaxInt;
for i:= 1 to N do
for j:= 1 to N do
if (col[i] <> col[j]) and
(W[i,j] < min) then begin
iMin:= i; jMin:= j; min:= W[i,j]
end;
ostov[k,1]:= iMin; { добавление ребра }
ostov[k,2]:= jMin;
c:= col[jMin];
for i:= 1 to N do { перекрашивание вершин }
if col[i] = c then
col[i]:= col[iMin]
end;
Слайд 125
Кратчайший маршрут
Алгоритм Дейкстры (1960):
кратчайшее расстояние
откуда ехать
ближайшая от
A невыбранная вершина
Слайд 126
Кратчайший маршрут
Алгоритм Дейкстры (1960):
кратчайшее расстояние
откуда ехать
W[x,z] + W[z,y]
< W[x,y]
может быть так, что
9
B
Слайд 127
Кратчайший маршрут
Алгоритм Дейкстры (1960):
кратчайшее расстояние
откуда ехать
W[x,z] + W[z,y]
< W[x,y]
может быть так, что
5
C
Слайд 128
Кратчайший маршрут
Алгоритм Дейкстры (1960):
кратчайшее расстояние
откуда ехать
7
E
8
E
Слайд 129
Кратчайший маршрут
длины кратчайших маршрутов из A в другие
вершины
A → C → E → F
Слайд 130
Алгоритм Дейкстры
const N = 6;
var W: array[1..N,1..N] of
integer;
active: array [1..N] of boolean;
R,
P: array [1..N] of integer;
i, j, min, kMin: integer;
Данные:
Начальные значения (выбор начальной вершины):
for i:=1 to N do begin
active[i]:= True; { вершины не выбраны }
R[i]:= W[1,i]; { только маршруты из A }
P[i]:= 1 { вершина A }
end;
active[1]:= False; { вершина A выбрана }
P[1]:= 0; { это начало маршрута }
Слайд 131
Алгоритм Дейкстры
for i:= 1 to N-1 do begin
min:= MaxInt;
for j:= 1 to N do
if active[j] and (R[j] < min) then begin
min:= R[kMin];
kMin:= j
end;
active[kMin]:= False;
for j:= 1 to N do
if R[kMin] + W[kMin,j] < R[j] then begin
R[j]:= R[kMin] + W[kMin,j];
P[j]:= kMin
end
end;
Основной цикл:
выбор следующей вершины, ближайшей к A
проверка маршрутов через вершину kMin
Слайд 132
Алгоритм Дейкстры
i:= N; { конечная вершина }
while i
< > 0 do begin
write(i:5);
i:= P[i]
{ к следующей вершине }
end;
Вывод результата (маршрут 1 → N):
для начальной вершины P[i]=0
Слайд 133
Алгоритм Флойда
for k:= 1 to N do
for
i:= 1 to N do
for j:= 1
to N do
if W[i,k]+ W[k,j]< W[i,j] then
W[i,j]:= W[i,k]+ W[k,j];
Все кратчайшие пути (из любой вершины в любую):
Слайд 134
Алгоритм Флойда + маршруты
for i:=1 to N do
begin
for j:=1 to N do
P[i,j]:= i;
P[i,i]:= 0
end;
Дополнительная матрица:
for k:= 1 to N do
for i:= 1 to N do
for j:= 1 to N do
if W[i,k]+ W[k,j]< W[i,j] then begin
W[i,j]:= W[i,k]+ W[k,j];
P[i][j]:= P[k][j]
end;
Кратчайшие длины путей и маршруты:
Слайд 135
Задача коммивояжера
Коммивояжер (бродячий торговец) должен выйти из города
1 и, посетив по разу в неизвестном порядке города
2,3,...N, вернуться обратно в город 1. В каком порядке надо обходить города, чтобы путь коммивояжера был кратчайшим?
Точные методы:
простой перебор;
метод ветвей и границ;
метод Литтла;
…
Приближенные методы:
метод случайных перестановок (Matlab)
генетические алгоритмы
метод муравьиных колоний
…
большое время счета для больших N
O(N!)
не гарантируется оптимальное решение
Слайд 136
Некоторые задачи
Задача на минимум суммы. Имеется N населенных
пунктов, в каждом из которых живет pi школьников (i=1,...,N).
Надо разместить школу в одном из них так, чтобы общее расстояние, проходимое всеми учениками по дороге в школу, было минимальным.
Задача о наибольшем потоке. Есть система труб, которые имеют соединения в N узлах. Один узел S является источником, еще один – стоком T. Известны пропускные способности каждой трубы. Надо найти наибольший поток от источника к стоку.
Задача о наибольшем паросочетании. Есть M мужчин и N женщин. Каждый мужчина указывает несколько (от 0 до N) женщин, на которых он согласен жениться. Каждая женщина указывает несколько мужчин (от 0 до M), за которых она согласна выйти замуж. Требуется заключить наибольшее количество моногамных браков.
Слайд 137
Алгоритмизация и программирование
§ 44. Динамическое программирование
Слайд 138
Что такое динамическое программирование?
Числа Фибоначчи:
;
.
F1 = F2
= 1
Fn = Fn-1 + Fn-2, при n >
2
function Fib(N: integer):
integer;
begin
if N < 3 then
Fib:= 1
else Fib:= Fib(N-1) + Fib(N-2)
end;
повторное вычисление тех же значений
Слайд 139
Динамическое программирование
Объявление массива:
const N = 10;
var F: array[1..N]
of integer;
Заполнение массива:
F[1]:= 1; F[2]:= 1;
for i:= 3 to
N do
F[i]:= F[i-1] + F[i-2];
F1 = F2 = 1
Fn = Fn-1 + Fn-2, при n > 2
нужны только два последних!
Слайд 140
Динамическое программирование
Динамическое программирование – это способ решения сложных
задач путем сведения их к более простым задачам того
же типа.
1
2
5
ABE: 5 + 20 = 25
AСE: 2 + 30 = 32
ADE: 1 + 40 = 41
дополнительный расход памяти
увеличение скорости
Слайд 141
Количество вариантов
Задача. Найти количество KN цепочек, состоящих из
N нулей и единиц, в которых нет двух стоящих
подряд единиц.
Решение «в лоб»:
0/1
битовые цепочки
построить все возможные цепочки
проверить каждую на «правильность»
2N
Сложность алгоритма O(2N)
Слайд 142
Количество вариантов
Задача. Найти количество KN цепочек, состоящих из
N нулей и единиц, в которых нет двух стоящих
подряд единиц.
Простые случаи:
K1=2
N = 1:
0 1
K2=3
N = 2:
00 01 10
Общий случай:
KN-1 «правильных» цепочек начинаются с нуля!
KN-2 «правильных» цепочек начинаются с единицы!
KN-2
KN-1
KN = KN-1 + KN-2
= FN+2
Слайд 143
Оптимальное решение
Задача. В цистерне N литров молока. Есть
бидоны объемом 1, 5 и 6 литров. Нужно разлить
молоко в бидоны так, чтобы все бидоны были заполнены и количество используемых бидонов было минимальным.
Перебор?
при больших N – очень долго!
«Жадный алгоритм»?
N = 10:
10 = 6 + 1 + 1 + 1 + 1
10 = 5 + 5
K = 5
K = 2
Слайд 144
Оптимальное решение
Сначала выбрали бидон…
KN – минимальное число бидонов
для N литров
KN = 1 + KN-1
1 л:
KN =
1 + KN-5
5 л:
KN = 1 + KN-6
6 л:
min
KN = 1 + min (KN-1 , KN-5 , KN-6)
при N ≥ 6
KN = 1 + min (KN-1 , KN-5)
при N = 5
KN = 1 + KN-1
при N < 5
Рекуррентная формула:
Слайд 145
Оптимальное решение (бидоны)
1
1
2
1
3
1
4
1
1
5
1
6
2
1
3
1
4
1
2
5
KN = 1 + min (KN-1
, KN-5 , KN-6)
2 бидона
5 + 5
Слайд 146
Задача о куче
Задача. Из камней весом pi (i=1,
…, N) набрать кучу весом ровно W или, если
это невозможно, максимально близкую к W (но меньшую, чем W).
камень
взят
камень
не взят
2N
Сложность алгоритма O(2N)
Решение «в лоб»:
Слайд 147
Задача о куче
Задача. Из камней весом pi (i=1,
…, N) набрать кучу весом ровно W или, если
это невозможно, максимально близкую к W (но меньшую, чем W).
Идея: сохранять в массиве решения всех более простых задач этого типа (при меньшем количестве камней N и меньшем весе W).
Пример: W = 8, камни 2, 4, 5 и 7
w
pi
базовые случаи
T[i,w] – оптимальный вес, полученный для кучи весом w из i первых по счёту камней.
i
Слайд 148
Задача о куче
Добавляем камень с весом 4:
для w
< 4 ничего не меняется!
0
2
2
для w ≥ 4:
если его
не брать:
T[2,w] = T[1,w]
если его взять:
T[2,w] = 4 + T[1,w-4]
max
4
4
6
6
6
Слайд 149
Задача о куче
Добавляем камень с весом 5:
для w
< 5 ничего не меняется!
0
2
2
4
5
6
7
7
Слайд 150
Задача о куче
Добавляем камень с весом 7:
для w
< 7 ничего не меняется!
0
2
2
4
5
6
7
7
Слайд 151
Задача о куче
Добавляем камень с весом pi:
для w
< pi ничего не меняется!
Рекуррентная формула:
Слайд 152
Задача о куче
Оптимальный вес 7
5 + 2
Слайд 153
Задача о куче
Заполнение таблицы:
W+1
N
псевдополиномиальный
Слайд 154
Количество программ
Задача. У исполнителя Утроитель есть команды:
1.
прибавь 1
2. умножь на 3
Сколько есть разных программ,
с помощью которых можно из числа 1 получить число 20?
Слайд 155
Количество программ
Как получить число N:
N
если делится на 3!
последняя
команда
Рекуррентная формула:
KN = KN-1 если N
не делится на 3
KN = KN-1 + KN/3 если N делится на 3
Слайд 156
Количество программ
Заполнение таблицы:
Рекуррентная формула:
KN = KN-1
если N не делится на 3
KN = KN-1
+ KN/3 если N делится на 3
1
2
2
2
3
3
3
5
5
5
7
7
7
9
9
9
12
12
12
K2+K1
K5+K2
K8+K3
одна пустая!
Слайд 157
Количество программ
Только точки изменения:
12
20
Программа:
K[1]:= 1;
for i:=
2 to N do begin
K[i]:= K[i-1];
if i mod 3 = 0 then
K[i]:= K[i] + K[i div 3]
end;
Слайд 158
Размен монет
Задача. Сколькими различными способами можно выдать сдачу
размером W рублей, если есть монеты достоинством pi (i=1,
…, N)? В наборе есть монета достоинством 1 рубль (p1 = 1).
Перебор?
при больших N и W – очень долго!
Динамическое программирование:
запоминаем решения всех задач меньшей размерности: для меньших значений W и меньшего числа монет N.
Слайд 159
Размен монет
Пример: W = 10, монеты 1, 2,
5 и 10
w
pi
базовые случаи
T[i,w] – количество вариантов для суммы
w с использованием i первых по счёту монет.
i
Рекуррентная формула (добавили монету pi):
при w < pi:
при w ≥ pi:
T[i,w] = T[i-1,w]
T[i,w] = T[i-1,w] + T[i,w-pi]
все варианты размена остатка
T[i-1,w]
без этой монеты
T[i,w-pi]
Слайд 160
Размен монет
Пример: W = 10, монеты 1, 2,
5 и 10
Рекуррентная формула (добавили монету pi):
Слайд 161
Конец фильма
ПОЛЯКОВ Константин Юрьевич
д.т.н., учитель информатики
ГБОУ СОШ №
163, г. Санкт-Петербург
kpolyakov@mail.ru
ЕРЕМИН Евгений Александрович
к.ф.-м.н., доцент кафедры мультимедийной
дидактики и ИТО ПГГПУ, г. Пермь
eremin@pspu.ac.ru