Upload
fedor-tsarev
View
5.949
Download
1
Embed Size (px)
DESCRIPTION
Слайды лекции спецкурса "Олимпиадное программирование"
Citation preview
Линейные структуры данных
Федор ЦаревСпецкурс «Олимпиадное
программирование»Лекция 1
01.12.2008, 08.12.2008Санкт-Петербург, Гимназия 261
Цель лекции
Изучить теоретические основы линейных структур данных
Изучить методы их реализации на языке программирования Pascal (Delphi)
Структуры данных
Алгоритмы + структуры данных = программы (Никлаус Вирт)
Способ организации хранения данных Набор операций и их свойства
Линейные структуры данных
Наиболее просты для понимания Не содержат разветвлений Являются базовыми и используются во
многих алгоритмах и более сложных структурах данных
Список, стек, очередь, дек, …
Тип «запись»
Служит для описания сущности, которая характеризуется несколькими параметрами
type student = recordname : string;class : integer;marks : array[1..10] of integer;
end;
Поля записи
Запись – набор полей
vars : student;
s.name := ‘Vasya’;writeln(s.name);
Указатели в языке Pascal
Указатель = адрес в памяти компьютера Выделение (new) и освобождение (delete)
памяти Тип данных «указатель на» pstudent = ^student
Односвязный список Состоит из элементов (узлов) Данные и указатель на следующий узелtypepnode = ^node;node = recorddata : integer;next : pnode;
end; Указатель на начало списка first
firstData
Next
Data
Next
Data
Next
NIL
Операции со списком
Добавление элемента (add) Поиск элемента (find) Удаление элемента (remove)
Добавление элемента
firstData
Next
Data
NextNIL
Data
Next
first
Data
Next
Data
NextNIL
Data
Next
До добавления
После добавления
Добавление элемента – программа
procedure add(x : longint);varp : pnode;
beginnew(p);p^.data := x;p^.next := first;first := p;
end;
Поиск элементаfunction find(x : integer) : boolean;varwp : pnode;
beginwp := first;while (wp <> nil) do begin
if (wp^.data = x) then beginresult := true;exit;
end;wp := wp^.next;
end;result := false;
end;
Удаление элемента
Элемент необходимо найти и удалить
firstData
Next
Data
Next
Data
Next
NIL
До удаления
После удаления
firstData
Next
Data
Next
Data
Next
NIL
Удаление элемента
Два случая удаление первого элемента удаление не первого элемента
Необходимо знать не только удаляемый элемент, но и предыдущий
Программа (начало)
procedure remove(x : integer);varcur, prev, tmp : pnode;
beginif (first = nil) then begin
exit;end;if (first^.data = x) then begin
tmp := first;first := first^.next;delete(tmp);exit;
end;
Программа (продолжение)
prev := first;cur := first^.next;while (cur <> nil) do begin
if (cur^.data = x) then beginprev^.next := cur^.next;delete(cur);exit;
end;cur := cur^.next;prev := prev^.next;
end;end;
Время работы операций с односвязным списком
add – O(1) – константное время find – O(n) – линейно зависит от числа
элементов в списке remove – O(n)
Двусвязный список В каждом элементе хранится указатель не
только на следующий, но и на предыдущийtypepnode = ^node;node = recorddata : integer;next, prev : pnode;
end;
NILfirst
next
data
prev
next
data
prev
next
data
prev
Операции с двусвязным списком
Добавление элемента (add) Поиск элемента (find) Удаление элемента (remove)
Добавление элемента – программаprocedure add(x : longint);varp : pnode;
beginnew(p);p^.data := x;p^.next := first;if (first <> nil) then begin
first^.prev := p;end;first := p;
end;
Поиск элемента Так же, как для односвязногоfunction find(x : integer) : boolean;varwp : pnode;
beginwp := first;while (wp <> nil) do begin
if (wp^.data = x) then beginresult := true;exit;
end;wp := wp^.next;
end;end;
Удаление элемента
NILfirst
next
data
prev
next
data
prev
next
data
prev
NILfirst
next
data
prev
next
data
prev
next
data
prev
До удаления
После удаления
Четыре случая
Нет ни предыдущего, ни следующего Есть только предыдущий Есть только следующий Есть и предыдущий, и следующий
Удаление элемента – программа (начало)
procedure remove(x : integer);
var
cur, prev, next : pnode;
begin
if (first = nil) then begin
exit;
end;
end;
Удаление элемента – программа (продолжение)cur := first;while (cur <> nil) do begin
if (cur^.data = x) then beginprev := cur^.prev;next := cur^.next;// Обработка 4 случаев:// следующие два слайдаexit;
end;cur := cur^.next;
end;
Удаление элемента – обработка случаев (1, 2)if (prev=nil) and (next=nil) then begin// Элемент в списке одинfirst := nil;delete(cur);
end;if (prev <> nil) and (next = nil) thenbegin// Удаляется последний в списке элементprev^.next := nil;delete(cur);
end;
Удаление элемента – обработка случаев (3, 4)if (prev=nil) and (next<>nil) thenbegin// Удаляется первый в списке элементfirst := next;delete(cur);
end;if (prev<>nil) and (next<>nil) then begin// Общий случайprev^.next := next;next^.prev := prev;delete(cur);
end;
Запутались в случаях?
Используйте сторожевые элементы (sentinel elements) – специальные элементы, которые всегда присутствуют в списке, но не хранят никакой информации
Необходимо два таких элемента – в начале и в конце списка
Список со сторожевыми элементамиtypepnode = ^node;node = recorddata : integer;next, prev : pnode;isSentinel : boolean;
end;
NIL
first
next
data
prev
next
data
prev
next
prev
next
prev
Пустой список со сторожевыми элементами
NIL
first
next
prev
next
prev
Добавление в списокprocedure add(x : longint);varp : pnode;
beginnew(p);p^.data := x;p^.next := first;first^.prev := p;first := p;
end;
Поискfunction find(x : integer) : boolean;varwp : pnode;
beginwp := first;while (wp <> nil) do begin
if ( (not (wp^.isSentinel)) and (wp^.data = x) ) then begin
result := true;exit;
end;wp := wp^.next;
end;result := false;
end;
Удаление элементаprocedure remove(x : integer);varcur, prev, next : pnode;
begincur := first;while (cur <> nil) do begin
if ( (not (cur^.isSentinel)) and (cur^.data = x) ) then begin
cur^.prev^.next := cur^.next;cur^.next^.prev := cur^.prev;delete(cur);exit;
end;cur := cur^.next;
end;end;
Вместо четырех случаев – один!
Время работы операций с двусвязным списком
add – O(1) – константное время find – O(n) – линейно зависит от числа
элементов в списке remove – O(n)
Стек
Stack Принцип LIFO (Last In
– First Out) Используется в
алгоритме обхода графа в глубину и при реализации рекурсии
Операции со стеком
push(x) – положить элемент в стек pop() – взять элемент с верхушки стека isEmpty() – пуст ли стек
Хранение стека в виде массива
top
1 2 3 4 5 6 7 8
Массив stack элементов, хранящихся в стеке Переменная top, хранящая номер верхнего
элемента
Программная реализацияprocedure push(x : integer);begininc(top);stack[top] := x;
end;function isEmpty() : boolean;beginresult := (top = 0);
end;
Программная реализация
function pop() : integer;beginif (isEmpty()) then begin
// Ошибка – извлекаем из // пустого стекаexit;
end;result := stack[top];dec(top);
end;
Очередь
Queue Принцип FIFO (First In – First Out) Используется в алгоритме обхода графа в
ширину
Операции с очередью
add – добавить элемент remove – извлечь элемент isEmpty – проверить пустоту
Хранение очереди в виде массива
tail
1 2 3 4 5 6 7 8
head
Массив queue элементов, которые хранятся в очереди head – номер первого элемента массива, который
принадлежит очереди tail – номер элемента, следующего за последним в
очереди Элементы добавляются в хвост, извлекаются из головы
Программная реализация
procedure add(x : integer);beginqueue[tail] := x;inc(tail);
end;
function isEmpty() : boolean;beginresult := (head = tail);
end;
Программная реализация
function remove() : boolean;beginif (isEmpty()) then begin
// Ошибка – извлекаем из // пустой очередиexit;
end;result := queue[head];inc(head);
end;
Дек
Dequeue = double-ended queue – очередь с двумя концами
Операции с деком
addLeft – добавить слева removeLeft – извлечь слева addRight – добавить справа removeRight – извлечь справа isEmpty – проверить пустоту
Представление дека в виде массива
right
1 2 3 4 5 6 7 8
left
Массив, хранящий элементы дека left – левая граница дека right – правая граница дека Надо быть аккуратным с границами массива, когда дек
пуст Можно объявить массив dequeue: array[-10..10] of longint;
Программная реализация
function isEmpty() : boolean;beginresult := left > right;
end;
procedure addRight(x : integer);begininc(right);dequeue[right] := x;
end;
Программная реализация
procedure addLeft(x : integer);begindec(left);dequeue[left] := x;
end;
Программная реализация
function removeLeft() : boolean;beginif (isEmpty()) then begin
// Ошибка – извлекаем из // пустого декаexit;
end;result := dequeue[left];inc(left);
end;
Программная реализация
function removeRight() : boolean;beginif (isEmpty()) then begin
// Ошибка – извлекаем из // пустого декаexit;
end;result := dequeue[right];dec(right);
end;
Упражнение
Реализовать стек, дек и очередь на базе списков, а не на базе массивов
Собственный менеджер памяти
Использовать указатели не всегда удобно Выделение и освобождение памяти –
достаточно медленные операции (порядка 100 тысяч операций в секунду, других операций можно совершить до 50 миллионов)
Можно написать свой менеджер памяти!
Менеджер памяти Вместо памяти – массив memory : array[1..MAXMEM] of node; MAXMEM – «размер памяти» free – номер первой свободной ячейки Вместо указателей – номера ячеек массива
memory -1 ↔ nil 1 2 3 4 5 6 7 8
free
Односвязный список
first234
Next
345
Next
567
Next
NIL
234
1
345
3
567
-1
first = 2free = 4
1 2 3 4 5 6
Добавление в список
procedure add(x : longint);varp : integer;
beginp := free;inc(free);memory[p].data := x;memory[p].next := first;first := p;
end;
Поиск элементаfunction find(x : integer) : boolean;varwp : integer;
beginwp := first;while (wp <> -1) do begin
if (memory[wp].data = x) then beginresult := true;exit;
end;wp := memory[wp].next;
end;result := false;
end;
Удаление элемента (начало)
procedure remove(x : integer);varcur, prev, tmp : integer;
beginif (first = -1) then begin
exit;end;if (memory[first].data = x) then begin
tmp := first;first := memory[first].next;exit;
end;
Удаление элемента (продолжение)prev := first;cur := memory[first].next;while (cur <> -1) do begin
if (memory[cur].data = x) then beginmemory[prev].next :=
memory[cur].next;exit;
end;cur := memory[cur].next;prev := memory[prev].next;
end;end;
Упражнение
Реализовать двусвязный список на собственном менеджере памяти
Выводы
Односвязный и двусвязный списки основаны на указателях
Для их реализации можно использовать как встроенный менеджер памяти (new, delete), так и собственный
Стек, очередь и дек можно реализовать на основе списков и массивов
Я рекомендую использовать свой менеджер памяти и реализацию стека, очереди и дека на базе массива