Professional Documents
Culture Documents
анализа на алгоритми
Предавање #3
• поврзани
2
Линеарни податочни структури
Низи/Полиња (Arrays)
3
Линеарни податочни структури
Низи/Полиња (Arrays)
4
Линеарни податочни структури
Операции со низи
Вообичаени операции со елементите на низите опсервирани како парови (вредност, индекс) се:
5
Линеарни податочни структури
Операции со низи
Вметнување (insertion)
Наједноставно вметнување на нов елемент во низата е додавање на крајот (после последниот).
Во тој случај индексирањето обезбедува пристап во константно време O(1)!
Вметнување меѓутоа на било која друга позиција (во подредена низа) бара поместување на сите
од таа позиција НАДЕСНО што имплицира FOR/WHILE јамка за поединечните поместувања, а
што продуцира O(n) трошок!
6
Линеарни податочни структури
Операции со низи
Бришење (deletion)
Бришење на елемент на било која позиција (освен крајната) бара поместување на сите по таа
позиција НАЛЕВО што имплицира FOR/WHILE јамка за поединечните поместувања, а што
повторно продуцира O(n) трошок!
7
Линеарни податочни структури
Операции со низи
Пребарување (searching)
Во најлош случај O(n) при секвенцијално испитување на секој елемент (несортирана низа),
бидејќи треба да се изминат сите елементи!
При бинарно пребарување,
(под услов низата да е сортирана)
трошокот е O(lg n):
Ai = AI + (i - 1) · s
• ефикасен пристап
• неефикасно вметнување и бришење
10
Линеарни податочни структури
Примена на низи - 2D матрица
Ai1,.., in = A1,..,1 + ((i1 - 1)u2u3...un + (i2 - 1) u3u4...un +…+ (in-1 - 1)un + in - 1)s
offset = 0
for j = 1 to n do
offset = U [j ] offset + I [j ] - 1
end_for
A = A1,..,1 + s * offset 12
Линеарни податочни структури
Примена на низи - Триаголни и ретки (sparse) матрици
Ретките матрици се чест реален случај на големи матрици со значителен (доминантен) број на
нули - во општ случај рандомизирано позиционирани.
Чести облици се и триаголните и тридијагоналните матрици:
Една ретка матрица со (просечни) димензии 100.000 100.000 од тип float (4 бајти) зафаќа
значаен простор од 40 GB. Ако е пола празна тоа имплицира некои 14 Gb непотребен
мемориски трошок!
Ако матрицата е РЕТКА, тогаш со задржана ЕФИКАСНОСТ, а сепак и ЗАШТЕДЕН ПРОСТОР може
да се претстави преку вектор од координати и вредности на ненултите елементи, во формат
(row, column, value):
R C V
0 0 4 0 0 0 0 1 3 4
2 4 5
0 0 0 5 0 11 0
2 6 11
X = 0 0 0 0 0 0 0
4 1 9
9 0 0 8 0 0 0
4 4 8
0 0 0 0 0 0 15 5 7 15
15
Линеарни податочни структури
Примена на низи - Ретки (sparse) матрици
16
Линеарни податочни структури
Примена на низи - Претставување на полиноми
Еден начин на репрезентација: P1(x) = [m, em, bm, ..., e0, b0]
Пример: P( x) 3x 5 7 x 4 2 x 2 9
P1 ( x) 5 3 4 7 3 0 2 2 1 0 0 9
P2 ( x) 5 3 7 0 2 0 9
Проблем настанува ако при извршување се појави полином од ред за кој не е предвиден
соодветен простор!
17
Линеарни податочни структури
Array ADT
18
Линеарни податочни структури
Листи
Терминологија:
• empty (листа) - празна, без ниеден податочен елемент (објект)
• length (должина) - моменталниот број на елементи во листата
• head - почетокот на листата (може да е елемент)
• tail / trailer - крајот на листата
• сортирана листа - елементите се подредени по растечки редослед (согласно типот)
• несортирана листа - нема релација помеѓу податокот и позицијата на елементите
19
Линеарни податочни структури
Листи
При избор на листата потребно е да се утврдат операциите кои треба да бидат подржани од
имплементацијата. Интуитивно се очекува дека листата треба да може да се
издолжува/скратува со вметнување/бришење на елементи; да се пристапи и модифицира
ефикасно било кој елемент; да се креира и (ре-)иницијализира. Најбитно, да може
едноставно да се достапат претходниот и наредниот елемент од тековниот!
20
Линеарни податочни структури
List ADT
Вметнување на елементот 99 во листата <12 | 32, 15> ("|" означува тековна позиција - во
овој случај 32):
22
Линеарни податочни структури
List ADT - Имплементација со низа (AList)
// Konstruktori
AList() { this(defaultSize); }
AList(int size)
{
maxSize = size;
listSize = curr = 0;
listArray = (E[])new Object[size];
}
23
Линеарни податочни структури
List ADT - Имплементација со низа (AList)
26
Линеарни податочни структури
Поврзана листа (Linked List)
Јазлите се составени од податочен дел (data, info) и поинтерски дел (links) кој покажува(ат)
на соседните јазли. Линковите на терминалните јазли покажуваат на NULL.
27
Линеарни податочни структури
Единечно поврзана листа (Singly Linked List - SLL)
Јазлите на единечно поврзаната листа (SLL) имаат информационен дел и еден линк
(покажувач) за поврзување со наредниот во серијата.
head tail
SLL има почетен јазол (head) и краен (tail). Референцата на листата покажува на почетниот, а
покажувачот на крајниот покажува на NULL преку што се утврдува крајот на изминувањето.
Секој јазол освен крајниот има следбеник и секој освен првиот има претходник.
SLL без ниеден јазол се нарекува празна листа! Постои само референцата спрема истата
(list).
SLL има редослед како и низите, но нема предефинирана димензија и нема индекс поради
што тековната позиција сама по себе не дава информација за редниот број на јазелот!
head
curr
head
curr
head
curr
29
Линеарни податочни структури
Операции со SLL
Додавање јазол пред head или после tail е едноставно (само нивно преместување) O(1)!
Додавање јазол ПОСЛЕ тековниот:
head
curr
head
curr
Комплексност O(1)!
30
Линеарни податочни структури
Операции со SLL
Додавање јазол ПРЕД тековниот: редовната процедура бара повторно изминување O(n)!
head
curr
Но, има можност за реализација и без ново изминување - се додава новиот јазол ПОСЛЕ
тековниот и двата си ги разменуваат вредностите:
1. се дефинира линкот на јазолот new: newnext = currnext
2. се дефинира неговото info поле: newinfo = currinfo
3. се разменува вредноста: currinfo = data
4. се вметнува во листата: currnext = new
head
curr
Комплексност O(1)! 31
Линеарни податочни структури
Операции со SLL
head
curr
Но, може да се употреби истиот трик - тековниот копира од наредниот и се брише наредниот:
head
curr
1. тековниот јазол го превзема наредното info поле: currinfo = curr next info
2. тековниот јазол го превзема линкот од наредниот: currnext = currnext next
3. се ослободува меморијата зафатена од наредниот
Комплексност O(1)!
32
Проблем при бришење на ПОСЛЕДНИОТ! - Ќе мора пак до претпоследниот O(n)!
Линеарни податочни структури
Операции со SLL - Заклучок
Покрај постоењето на head поинтерот со кој се референцира SLL, погодно е да се има и tail
поинтер со кој веднаш се адресира последниот јазол, а со што се овозможени сите варијанти
на додавање јазол во SLL да се извршуваат во O(1) време!
Друг специјален случај кој може да направи проблем во алгоритмите на методите е ПРАЗНА
SLL! Во таа ситуација сите поинтери (head, curr, tail) немаат на што да покажуваат (NULL).
Наместо да се комплицираат методите со предвидување на ваквите специјални случаи,
наједноставно е да се дефинира празен јазол ВОДАЧ со линк head кон кој во случај на празна
SLL покажуваат поинтерите.
При дизајнирањето на Java интерфејсот за ADT за SLL добро би било е да се креира посебна
генеричка класа за јазлите (Link) која би можела да прими објект од било кој тип. 33
Линеарни податочни структури
Имплементација на SLL јазол (Link)
Објектот на Link има еден податочен елемент и еден поинтер кон следниот јазол:
class Link<E>
{
private E element; // Vrednost na ovoj jazol
private Link<E> next; // Pointer kon sledniot jazol
// Konstruktori
Link(E it, Link<E> nextval)
{
element = it;
next = nextval;
}
Link(Link<E> nextval) { next = nextval; }
Link<E> next() { return next; } // Daj go next
Link<E> setNext(Link<E> nextval) // Setiraj go next
{ return next = nextval; }
E element() { return element; } // Daj go element
E setElement(E it) { return element = it; } // Setiraj go element
}
34
Линеарни податочни структури
List ADT - Имплементација со SLL (LList)
Имплементација на листата дефинирана со интерфејсот List преку SLL класата LList. LList
мора да ги имплементира сите методи од преземениот интерфејс:
// Konstruktori
LList(int size) { this(); }
LList()
{
curr = tail = head = new Link<E>(null); // prazna lista vodac
cnt = 0;
}
...
35
Линеарни податочни структури
List ADT - Имплементација со SLL (LList)
Имплементација на листата дефинирана со интерфејсот List преку SLL класата LList. LList
мора да ги имплементира сите методи од преземениот интерфејс:
public void clear()
{
head.setNext(null);
curr = tail = head = new Link<E>(null);
cnt = 0;
}
public void moveToStart() { curr = head; }
public void moveToEnd() { curr = tail; }
public int length() { return cnt; }
public void next()
{
if (curr != tail)
curr = curr.next();
}
public E getValue()
{
assert curr.next() != null : "Nema elementi";
return curr.next().element(); // curr pokazuva na prethodnikot
} 36
Линеарни податочни структури
List ADT - Имплементација со SLL (LList)
Имплементација на листата дефинирана со интерфејсот List преку SLL класата LList. LList
мора да ги имплементира сите методи од преземениот интерфејс:
// Insert "it" na tekovnata pozicija (12)
public void insert(E it)
{
curr.setNext(new Link<E>(it, curr.next())); // stavi go po curr
if (tail == curr)
tail = curr.next(); // ako bil tail pomesti go tail po nego
cnt++;
}
// Append "it" na krajnata pozicija (tail)
public void append(E it)
{
tail = tail.setNext(new Link<E>(it, null));
cnt++;
}
37
Линеарни податочни структури
List ADT - Имплементација со SLL (LList)
Имплементација на листата дефинирана со интерфејсот List преку SLL класата LList. LList
мора да ги имплементира сите методи од преземениот интерфејс:
38
Линеарни податочни структури
List ADT - Имплементација со SLL (LList)
Имплементација на листата дефинирана со интерфејсот List преку SLL класата LList. LList
мора да ги имплементира сите методи од преземениот интерфејс:
Имплементација на листата дефинирана со интерфејсот List преку SLL класата LList. LList
мора да ги имплементира сите методи од преземениот интерфејс:
41
Линеарни податочни структури
Примена на SLL - Freelists
42
Линеарни податочни структури
Примена на SLL - Ретки вектори и матрици
Голема матрица со димензии m n (од редна величина 10.000+) побарува O(mn) мемориски
локации ( прецизноста во бајти)! При типичен број ненулти елементи O(m+n) едвај 0.01% од
локациите би биле искористени. Мултилистата се реализира со креирање листи за сите m
редови и n колони, и со нивно преклопување (вкрстување).
Јазлите на мултилистата ги содржат ненултите вредности, индексите (координатите) и
поинтери за меѓуповрзувањето!
43
Линеарни податочни структури
Примена на SLL - Претставување на полиноми
P ( x) 3x15 8 x 7 2 x 6
44
Линеарни податочни структури
Кружна (circular) поврзана листа - CSLL
head tail
Кај кружната листа е овозможено циркуларно движење што дава поголема флексибилност
за некои апликации.
Јазлите на двојно поврзаната листа (DLL) имаат информационен дел и два линка (поинтери)
за поврзување со претходникот и следбеникот.
Празната DLL има два неподаточни краеви (head и tail / trailer) заради избегнување на
специјалните случаи кај методите на имплементациите и со тоа олеснување на insert, append и
remove на елементи. Неподаточните краеви се нарекуваат и ЧУВАРИ (sentinels).
Секој јазол освен ПРВИОТ и ПОСЛЕДНИОТ има и ПРЕТХОДНИК и СЛЕДБЕНИК.
DLL поради можноста за двонасочно движење е поедноставна и преферирана за
имплементација во однос на SLL, иако има погабаритен код во методите и зафаќа повеќе
простор поради додатниот поинтер по јазол.
Кај SLL имплементацијата поставувањето на curr на ПРЕТХОДНИКОТ на ТЕКОВНИОТ јазол
овозможува O(1) вметнување и бришење. Кај DLL поради двонасочното врзување тоа не е
потребно, но може да се задржи како принцип!
Бројот на врски, т.е. типот на листата би требало да се апстрахира позади List интерфејсот!
46
Линеарни податочни структури
Операции со DLL
Комплексност O(1)!
47
Линеарни податочни структури
Операции со DLL
Комплексност O(1)!
48
Линеарни податочни структури
Операции со DLL - Заклучок
49
Линеарни податочни структури
Имплементација на DLL јазол (DLink)
Објектот на DLink има еден податочен елемент и два поинтера кон соседните јазли:
class DLink<E>
{
private E element;
private DLink<E> next;
private DLink<E> prev;
// Konstruktori
DLink(E it, DLink<E> p, DLink<E> n)
{ element = it; prev = p; next = n; }
DLink(DLink<E> p, DLink<E> n)
{ prev = p; next = n; }
// Get i Set metodi
DLink<E> next() { return next; }
DLink<E> setNext(DLink<E> nextval) { return next = nextval; }
DLink<E> prev() { return prev; }
DLink<E> setPrev(DLink<E> prevval) { return prev = prevval; }
E element() { return element; }
E setElement(E it) { return element = it; }
} 50
Линеарни податочни структури
List ADT - Имплементација со DLL (DList)
Remove методата прво ја чита вредноста од јазолот за бришење. Потоа prev на дотогашниот
следбеник се пренасочува на curr (дотогашниот претходник), и next на curr се пренасочува на
новиот дотогашниот следбеник. Конечно, се ажурира бројот на јазли:
52
Линеарни податочни структури
Кружна двојно поврзана листа - CDLL
head tail
53
Линеарни податочни структури
Споредба на перформансите на линеарните структури
54
Линеарни податочни структури
Примена
Линеарните податочни структури наоѓаат како директна примена во ефикасно динамичко
сместување на податоците; така и индиректна - како поддршка на други структури:
• стекови (stacks, LIFO)
• редови (queues, FIFO)
• приоритетни редови (priority queues)
• речници (dictionaries)
• итн.
НИЗИТЕ се обично првиот избор доколку обемот на податоци е релативно мал и однапред
предвидлив. Дигресија:
• доколку меморијата не е проблем, тогаш алоцирање со голема резерва
• ако е критична брзината на вметнување, тогаш несортирана низа
• ако е критично пребарувањето, тогаш сортирана низа со бинарно пребарување
• бришењето е секако бавно, а изминување (сериско процесирање) само кај сортирани
• динамичките имплементации (како Vector во Java) имаат застој при копирањето
ПОВРЗАНИТЕ ЛИСТИ мора да се користат секогаш при непредвидлив обем на податоците или
при фреквентни вметнувања и бришења. Дигресија:
• вметнувањето е брзо кај несортирани листи
• бришењето и пребарувањето се бавни поради што се користат при релативно мал обем
на податоци (но бришењето е побрзо отколку кај низите)
• покомплицирани за имплементација од низите, но едноставни во однос на дрвата или
hash табелите 55
Линеарни податочни структури
Стекови (stacks) - LIFO
57
Линеарни податочни структури
Stack ADT - Имплементација со низа (AStack)
58
Линеарни податочни структури
Stack ADT - Имплементација со низа (AStack)
Имплементација на стекот:
Имплементација на стекот:
/** Stavi go "it" vo stekot */
public void push(E it)
{
assert top != maxSize : "Stekot e poln!";
listArray[top++] = it;
}
/** Izvadi go vrvniot element */
public E pop()
{
assert top != 0 : "Stekot e prazen!";
return listArray[--top];
}
/** Procitaj go vrvniot element */
public E topValue()
{
assert top != 0 : "Stekot e prazen!";
return listArray[top-1];
}
/** Daj ja dlabocinata na stekot */
public int length() { return top; } 60
Линеарни податочни структури
Stack ADT - Имплементација со листа (LStack)
top
tail
Водач јазол е непотребен бидејќи не постои специјални случај за празна листа (0 елементи).
Единствен поинтер е top кај кого push и pop вметнуваат/вадат јазли O(1)!
PUSH прво го ажурира линкот на новиот елемент да покажува на врвот од стекот, а потоа го
ажурира top да покажува на новиот јазол!
POP прво ја копира вредноста на врвниот елемент во temp променливата, додека ltemp го
чува линкот кон истиот. Конечно, top се ажурира спрема наредниот јазол од стекот. Вредноста
на извадениот елемент е проследена за процесирање, а самиот елемент може да се врзе за
free-листа за следниот push.
61
Линеарни податочни структури
Stack ADT - Имплементација со листа (LStack)
Имплементација на стекот:
/** Konstruktori */
public LStack() { top = null; size = 0; }
public LStack(int size) { top = null; size = 0; }
Имплементација на стекот:
/** Izvadi go vrvniot element */
public E pop()
{
assert top != null : "Stekot e prazen!";
E it = top.element();
top = top.next();
size--;
return it;
}
64
Линеарни податочни структури
Примена на стекови
65
Линеарни податочни структури
Примена на стекови
66
Линеарни податочни структури
Примена на стекови
initialize stack
open InFile, OutFile
element data
while (!EOF)
data = read (InFile)
push (data)
close InFile
while (!emptyStack)
data = pop (stack)
write (OutFile, data)
close OutFile
67
Линеарни податочни структури
Примена на стекови
a{b(c[d]e)f}
68
Линеарни податочни структури
Примена на стекови
69
http://csis.pace.edu/~wolf/CS122/infix-postfix.htm
70
Линеарни податочни структури
Редови (queues) - FIFO
71
Линеарни податочни структури
Queue ADT
Ако се одбере back[0] и front[n-1], тогаш dequeue O(1), но enqueue O(n) поради
неопходното поместување налево!
Ако се одбере back[n-1] и front[0], тогаш enqueue O(1), но dequeue O(n) поради
неопходното поместување налево!
Но, се јавува голем проблем - front/rear континуирано се движат кон крајот на низата и во
еден момент редот ќе пријави дека е преполн иако всушност не е - drifting queue!
73
Линеарни податочни структури
Queue ADT - Имплементација со низа (AQueue)
Решение се циркуларните редови!
Низата има споени краеви AQ[n-1]AQ[0].
Имплементација на редот:
/** Konstruktori */
AQueue() { this(defaultSize); }
AQueue(int size)
{
maxSize = size+1; // Extra pole za POLN/PRAZEN detekcija
rear = 0; front = 1;
listArray = (E[])new Object[maxSize]; // Kreiraj go listArray
}
75
Линеарни податочни структури
Queue ADT - Имплементација со низа (AQueue)
Имплементација на редот:
Имплементација на редот:
77
Линеарни податочни структури
Queue ADT - Имплементација со листа (LQueue)
Кој крај да биде влез, а кој излез? - Од анализата на листите следи дека втората варијанта е
максимално ефикасна O(1)!
head head
back front tail
tail
front back
За оваа реализација одбрано е enqueue да става јазол на tail од листата (rear), а dequeue да
вади јазол кај header-от (front).
78
Линеарни податочни структури
Queue ADT - Имплементација со листа (LQueue)
Имплементација на редот:
/** Konstruktori */
public LQueue() { init(); }
public LQueue(int size) { init(); } // Ignore size
79
Линеарни податочни структури
Queue ADT - Имплементација со листа (LQueue)
Имплементација на редот:
80
Линеарни податочни структури
Queue ADT - Имплементација со листа (LQueue)
Имплементација на редот:
Со комбинирање на овие методи deck-от може да функционира и како стек и како ред!
82
Линеарни податочни структури
Приоритетен ред - Priority Queue
enqueue dequeue
83
Линеарни податочни структури
Примена на редови
84
Линеарни податочни структури
Queue ADT - Компаративна дискусија
85