01 линейные структуры данных

Preview:

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), так и собственный

Стек, очередь и дек можно реализовать на основе списков и массивов

Я рекомендую использовать свой менеджер памяти и реализацию стека, очереди и дека на базе массива

Спасибо за внимание!

Вопросы? Комментарии?

fedor.tsarev@gmail.com

Recommended