P. 1
11_ArhetipAlgoritmi algoritmi_7lekcija

11_ArhetipAlgoritmi algoritmi_7lekcija

|Views: 20|Likes:
Published by Vladimir Filev
algoritmi_7lekcijaalgoritmi_7lekcija
algoritmi_7lekcijaalgoritmi_7lekcija

More info:

Categories:Types, School Work
Published by: Vladimir Filev on Jun 25, 2013
Copyright:Attribution Non-commercial

Availability:

Read on Scribd mobile: iPhone, iPad and Android.
download as PDF, TXT or read online from Scribd
See more
See less

04/07/2015

pdf

text

original

Структури на податоци и алгоритми

Аудиториски вежби
11
АРХЕТИП АЛГОРИТМИ

Постојат неколку архетипи (класи на алгоритми кои се реализирани на
заеднички принципи): алчни алгоритми (greedy), раздели и владеј алгоритми (divide
and conquer), динамичко програмирање (dynamic programming), алгоритми со случајни
броеви (randomized) и алгоритми кои се враќаат наназад од резултатот (backtracking).

11.1
“РАЗДЕЛИ И ВЛАДЕЈ” АЛГОРИТМИ

Оваа техника во принцип секогаш резултира во коректен алгоритам и се состои
од два чекора:
Раздели: Проблемот се дели на помали проблеми се додека не се дојде до
елементарни или базични случаи. Обично, се користи рекурзија.
Владеј: Решението на проблемот се добива од решенијата на поедноставните
проблеми со помош на така наречено „слевање“ на решението.
Од аспект на користење на рекурзија, може да се каже дека секој алгоритам кој
користи барем два вгнездени рекурзивни повици го користи принципот раздели и
владеј. Во принцип, разделените подпроблеми треба да се дисјунктни, односно да не се
преклопуваат. Типични класи проблеми кои се решаваат со помош на оваа техника се
скоро сите алгоритми кои работат со бинарни стебла, но и одредени класи геометриски
проблеми (најблиски точки во рамнина, најмал конвексен полигон кој опфаќа
множество точки), класи на пресметувачки проблеми (брза фуриева трансформација,
множење на матрици), одредени класи проблеми кои работат со графови.
Доколку техниката на развој на алгоритми „раздели и владеј“ се реализира со
два вградени рекурзивни повици, нејзината временска комплексност може во принцип
да се опише со следната итеративна формула T(n) = 2T(n/2) + O(n) која води до типична
временска комплексност на извршување на оваа класа на алгоритми од O(n log n).

Задача 1 Да се напише програма која ќе го најде максималниот елемент во една низа
со примена на раздели и владеј алгоритам.

Решение:
Алгоритмот ја дели низата на a[0..n] на две низи a[0..m] a[m+1..n], го наоѓа максимумот
на двате низи (рекурзивно), и го враќа поголемиот од тие два максимуми како
максимум на целата низа. Основен случај е кога низата има еден елемент, тој елемент е
максимумот. Ако големината на низата е парен број, таа е поделена на два еднакви
дела, ако е непарен големините на двете низи се разликуваат за 1.


Структури на податоци и алгоритми
Аудиториски вежби
static int FindMax( int a[], int left, int right )
{
int middle;
int max_l, max_r;

if ( left == right ) // samo 1 element
{
// osnovniot slucaj

return a[left];
}
else
{

// Resavanje na pomali problemi

middle = (left+right)/2; // Deleneje na dva dela

max_l = FindMax( a, left, middle);
// najdi go mx vo prvata polovina
max_r = FindMax( a, middle+1, right);
// Najdi go max vo vtorata polovina


// Use the solutions to solve original problem

if ( max_l > max_r )
return(max_l);
else
return(max_r);
}
}


Слична е и функцијата за наоѓање на максимален елемент кај бинарно дрво.

static int MaxEl(Node p) {
int max,maxLevo,maxDesno;
if(p!=null){
max=p.data;
maxLevo=MaxEl(p.left);
maxDesno=MaxEl(p.right);
if (max<maxLevo) max=maxLevo;
if (max<maxDesno) max=maxDesno;
return max;
}
}



Комплексност:
T(n) = 2T(n/2) + n
2T(n/2) = 2(2(T(n/4)) + n/2) = 4T(n/4) + n → T(n) = 4T(n/4) + 2n
4T(n/4) = 4(2T(n/8)) + (n/4) = 8T(n/8) + n → T(n) = 8T(n/8) + 3n ...
kn n T n T
k k
) 2 ( 2 ) ( ако замениме k = log
2
n, се добива:
n n T n n T
2
2 log
log ) 1 ( ) (
2

T(n) = nT(1) + n log
2
n = n log
2
n + n
2 log log
2
log
1
2
b b
n
n k
n
n
k
Структури на податоци и алгоритми
Аудиториски вежби
Значи комплексноста е ) log ( log ) (
2
n n O n n cn n T


Ако n е степен од c, тогаш решението на диференцната равенка

е од облик


Задача 2 Да се напише процедура која ќе провери дали дадено бинарно дрво d е AVL
дрво користејќи ја методата раздели и владеј. Да се претпостави дека на располагање е
функција depth(d) која што ја дава височината на дрвото d.

Решение: Празно дрво е AVL дрво и тоа е основениот случај. Ако дрвото не е празно
подели го на лево и десно поддрво и проверуваме дали височините се разликуваат за 1.
Проверуваме рекурзивно со истата постапка дали двете подстебла се исто така AVL
стебла.

static int avl(Node d){
int L,D;
if (d=null) return 1;
if (abs(height (d.left)- height (d.right))>1) return 0;
L= avl(d.left);
D= avl(d.right);
return L&&D;
}

Задача 3 Да се напише процедура која ќе провери дали даден елемент x го има во
дадено бинарно стебло користејќи ја методата раздели и владеј.

Решение: Празно дрво е основен случај и тогаш одговорот е false. Ако дрвото не е
празно провери дали тековниот јазел го содржи елементот, и ако не го содржи, подели
го на лево и десно поддрво и провери дали го има елементот во нив.

static int Najdi(Node d, int X){
if (d==null) return 0;
else
if(d.data==X) return 1;
else return (Najdi(d.left,X)|| Najdi(d.right,X));
}

11.2
“АЛЧНИ” АЛГОРИТМИ

Постои класа на алгоритми кои претпоставуваат дека во секоја фаза на нивното
извршување се донесуваат одлуки кои во тој момент се чинат добри, иако можеби
Структури на податоци и алгоритми
Аудиториски вежби
носат некои идни (во тој момент непознати) нарушувања на исправноста на решението.
Поаналитички кажано, тие алгоритми секогаш го бараат локалното оптимално
решение. Кога алгоритмот ќе заврши се верува дека тој доколку можел, дошол и до
глобалниот оптимум. Ова секогаш не мора да биде точно. Доколку е точно, алгоритмот
креирал коректен резултат, во спротивно креирал коректно решение но, само за дел од
проблемскиот домен. Поради пристапот кој го „избира најдоброто што се нуди во
моментот“, оваа класа на алгоритми го добила името алчни алгоритми (greedy,
максималистички). Доколку не се бараат најкоректни можни резултати (поради
комплексноста на проблемскиот домен, поради потребата за реализација на брзи
алгоритми, поради неможноста да се добијаат сите влеани податоци), со помош на оваа
класа на алгоритми можат да се добијат приближно точни резултати.
Најчесто цитиран алгоритам за илустрација на класите на алчни алгоритми е
решението на проблемот со точно враќање на кусур со најмал број на банкноти и
монети. Според техниката на алчни алгоритми, за да се врати кусур од 269 денари,
најпрво се враќаат најголемите банкноти, па монети за да се намали вредноста на
парите што треба да се врати. Така, во конкретниот случај прво се враќаат две
банкноти од 100 денари, една од 50 денари, една од 10, па потоа една монета од 5
денари и две монети од два денари. На овој начин (намалувајќи ја сумата секогаш со
најголемите можни вредности) се добива најмалиот број на банкноти и монети кои
треба да се вратат.

Задача 4 Да се напише програма за враќање на кусур со најмал број на банкноти и
монети. Да се користи алчна стратегија опишана погоре. Притоа дадени се вредностите
1,2,5,10,50,100,500 денари како банкноти и монети.

public class Change {

private int[] denom;
private String name;

Change(String name, int[] denom) {
this.name = name;
this.denom = denom;
}

void giveChange(int changeInCent) {
System.out.println("\nChange for " + changeInCent + " in " +
name + ":");
for(int i = 0; i < denom.length; ++i) {
int nb = changeInCent / denom[i];
if(nb > 0)
System.out.println(nb + " " + denom[i]);
changeInCent %= denom[i];
}

}

public static void main(String[] args) {

int[] mk = {100, 50, 10, 5, 2, 1}; // MK
int[] us = {100, 25, 10, 5, 1}; // US

Change change1 = new Change("MK", mk);
Change change2 = new Change("USA", us);

change1.giveChange(456734);
Структури на податоци и алгоритми
Аудиториски вежби
change2.giveChange(456734);

change1.giveChange(12345);
change2.giveChange(12345);
}
}
Комплексноста на алгоритамот е T(n)=O(n), бидејќи во најлош случај, функцијата
GreedyCoins ќе се извршува n пати.

Задача 5 (За дома) Старите Египјани користеле нотација на дропки. Секоја дропка
може да се престави како сума на реципрочни вредности на природните броеви. На
пример, 2/3=1/2+1/6 или 7/13=1/2 + 1/26. Ваква репрезентација секогаш постои и се
добива со алчна страегија, секогаш да се зема најголемата реципрочна вредност на
број, помала од остатокот.




11.3
ДИНАМИЧКО ПРОГРАМИРАЊЕ


Рекурзивното решение на одредени проблеми често може да резултира во
неефикасен код, што се должи на повторување на рекурзивните повици. Често се
вршат повици на иста функција со исти аргументи, што може да се избегне ако
резултатите од пресметките се чуваат во табела. Техниката на градење на алгоритми
која ги сочувува резултатите на подпроблемите во табели за да се искористат по
потреба се нарекува „динамичко програмирање“. Пример на многу повторувања во
рекурзивните повици е пресметувањето на фибоначиевите броеви со:

public static int fibonacci(int n){
if(n <= 1) return 1;
else
return(fibonacci(n-1) + fibonacci(n-2));
}

T(n) = T(n - 1) + T(n - 2) + 2 = O(2
n
)

Динамичкото програмирање е варијација на рекурзивното програмирање, ако
рекурзијата е од таква природа да има повици кои се јавуваат повеќе пати.

1. Итератирвно DP – е кога не се складираат резултатите кои повеќе не се потребни.

public static int fibonacci(int n){
int fib_pret=1;
int fib_pret_pret=1;
for (int j=1;j<n;j++){
int fib_nov = fib_pret + fib_pret_pret;
fib_pret_pret = fib_pret;
fib_pret = fib_nov;
}
return fib_pret;
}
Структури на податоци и алгоритми
Аудиториски вежби

Оттука T(n)=O(n).

2. Memoization – е кога стандардна рекурзивна рутина се програмира да ги складира
потребните резултати.

public class Fibonacci
{ int []memo= {0,1}

public static int fibMem ( int n )
{
if ( n < 1 ) return 0; // Allows memo[k]==0 to flag uncomputed cell
if ( n >= memo.length )
{
int[] omem = new Int[n+1];
System.arraycopy(memo, 0, omem, 0, memo.length);
memo = omem;
}
if ( memo[n] == 0 )
memo[n] = fibMem(n-2) + fibMem(n-1);
return memo[n];
}


11.4
“BACKTRACKING” АЛГОРИТМИ

Backtracking алгоритмите или алгоритми со враќање наназад се подобрување на brute
force алгоритмите, кои го пребаруваат целиот простор на решенија. Овие алгоритми
претпоставуваат дека решенијата се претставени со вектори (v
1
, ..., v
m
) од вредности и
вршат изминување прво во длабочина на доменот на векторите се додека не се најде
решението. На почетокот алгоритамот почнува со празен вектор. во секоја фаза
парцијалниот вектор се проширува со нова вредност. Кога ќе се дојде до парцијален
вектор (v
1
, ..., v
i
) кој не може да биде парцијално решение, алгоритамот се враќа
наназад со отсранување на последно додадената вредност и продолжува со додавање
на друга вредност на векторот.


Задача 6 Пермутацијата може да се добие со селектирање на елемент од дадено
множество, и со пермутација на останатите елементи. Да се направи ваков алгоритам
со враќање наназад за генерирање на сите пермутации на една дадена низа.

Структури на податоци и алгоритми
Аудиториски вежби


public class Permute
{

static void swap(Object[] x, int j, int k)
{ Object temp = x[j]; x[j] = x[k]; x[k] = temp; }

static void permute(Object[] x, int idx)
{ int k;
if (idx == x.length)
{
for (k = 0; k < x.length; k++)
System.out.print(x[k]);
System.out.println();
}
else
{ Object hold;
for (k = idx; k < x.length; k++)
{ swap (x, idx, k);
permute(x, idx+1);
}

hold = x[idx]; // Belongs at the very end.
for (k = idx+1; k < x.length; k++)
x[k-1] = x[k];
x[k-1] = hold;
}
}
public static void main(String[] args)
{
Integer[] vect = { 1, 2, 3, 4 };

permute(vect, 0);
}
}

Функцијата permute за i=N-1, се извршува еднаш, за i=N-2 се повикува 2∙1 пати, за
i=N-3 се повикува 3∙2∙1 пати,..., за i=1 се повикува (N-1)∙...∙3∙2∙1=(N-1)! пати, и за i=0
се повикува N! пати. Оттука T(n)=n∙T(n-1)=O(n!).

// najdi go mx vo prvata polovina max_r = FindMax( a.left). right). return max. if(p!=null){ max=p. max_r. if (max<maxLevo) max=maxLevo. middle+1. maxDesno=MaxEl(p. // Najdi go max vo vtorata polovina // Use the solutions to solve original problem if ( max_l > max_r ) return(max_l).Структури на податоци и алгоритми Аудиториски вежби static int FindMax( int a[]. int right ) { int middle. int max_l.data. static int MaxEl(Node p) { int max.maxLevo. if (max<maxDesno) max=maxDesno. else return(max_r). if ( left == right ) { // osnovniot slucaj return a[left]. } } Комплексност: T(n) = 2T(n/2) + n 2T(n/2) = 2(2(T(n/4)) + n/2) = 4T(n/4) + n → T(n) = 4T(n/4) + 2n 4T(n/4) = 4(2T(n/8)) + (n/4) = 8T(n/8) + n → T(n) = 8T(n/8) + 3n . // Deleneje na dva dela // samo 1 element max_l = FindMax( a. middle).. int left. T (n) 2k T (n 2k ) kn ако замениме k = log2 n. maxLevo=MaxEl(p..right). left. } } Слична е и функцијата за наоѓање на максимален елемент кај бинарно дрво. се добива: n 1 log2 2 2k T (n) n T (1) n log2 n k log n T(n) = nT(1) + n log2 n = n log2 n + n 2logb n n logb 2 .maxDesno. } else { // Resavanje na pomali problemi middle = (left+right)/2.

You're Reading a Free Preview

Download
scribd
/*********** DO NOT ALTER ANYTHING BELOW THIS LINE ! ************/ var s_code=s.t();if(s_code)document.write(s_code)//-->