1 / 46

Абстрактни типове с две противоречащи една на друга операции

Абстрактни типове с две противоречащи една на друга операции. Кр. Манев, 09.2008. Абстрактни типове. Абстрактен тип наричаме: Множество от (математически дефини-рани) обекти ;

skule
Download Presentation

Абстрактни типове с две противоречащи една на друга операции

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Абстрактни типове с две противоречащи една на друга операции Кр. Манев, 09.2008

  2. Абстрактни типове • Абстрактен тип наричаме: • Множество от (математически дефини-рани) обекти; • Множество от операции. Операциите могат да бъдат както вътрешни – ар-гументите им са само обекти от множеството, така и външни – аргументите им са както обекти от множеството, така и други обекти.

  3. Имплементаци на абстрактните типове • Всеки абстрактен тип може да се имплементира по няколко различни начина: • Обектите от множеството представяме в някаква структура от данни. Ако желаем, можем да оформим структурата от данни като тип (typedef)или обект; • Всяка операция имплементираме с програмна функция/метод – функциите, типа и начина на задаване на аргументите, типа на резултата и т.н. наричаме интерфейс.

  4. Програмиране с абстрактни типове • Когато трябва да решим приложна задача с помощта на имплементирания абстрактен тип, добре е да спазваме простото правило: всеки достъп до обект на типа да става само през интерфейса. • Резултатът ще бъде - винаги можем да подменим имплементацията на интерфейса без да променяме кода, който решава приложната задача.

  5. Абстрактен тип Стр. от данни Стр. от данни Стр. от данни Имплементации Функции Функции Функции Интерфейс Приложна програма Програмиране с абстрактни типове

  6. Абстрактният типразбиване (на множество) • Дефиниция: A = {a1, a2,…, aN} емножество с Nелемента.R={S1, S2,…, SK},SiA наричаме разбиване на A, ако: • Si  ,i= 1, 2,…,K ; • Si Sj = ,1 i, j K, i  j; •  i= 1, 2,…,KSi=A . {{0, 3, 6, 9}, {1, 4, 7}, {2, 5, 8}}е разбиване на {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}.

  7. Операции с абстрактния типразбиване (на множество) • Нека R={S1, S2,…, SK}еразбиване на A = {a1, a2,…, aN}. Дефинираме следните операции: • find(R, ai)– резултат Sj,ai Sj; • join(R, Si , Si) – резултат ново разбиване:R’={S1, …, Si-1,Si+1,…, Sj-1,Sj+1 ,…, SK, S}, S = Si  Si; • init(R, N)– резултат R={{1}, {2},…, {N}}, и т.н. За нашия пример, find(R, 0)= S1, find(R, 1)= S2, join(R, S1, S2)={{0, 1, 3, 4, 6, 7, 9},{2,5,8}}.

  8. Задача • Да разгледам сега една задача, която е подходяща за решаване с помощта на абстрактния тип разбиване: • Даден е графG(V,E) с тегла c:ERна ребрата. Да се построи Минимално покриващо дърво T(V,E’) на G, т.е. такова покриващо дърво, че сумата от теглата на ребрата му да е минимална.

  9. Алгоритъм на Крускал • Сортираме ребрата на графа в нарастващ ред на теглата – и нека той е e1, e2,…, eM • От всеки връх vна графа правим отделно дърво Tv({v},) • for(i=1;i<=M;i++) {/*ei=(v,w)*/ if (v и w са в различни дървета) свързваме v и w с ребро;}

  10. Интерфейс за разбиване • Нека сме дефинирали структура от данни, подходяща за представяне на АТ разбиване и сме я оформили като тип - Part. Eлементите на множеството - Elem,частите –Sub. • Дефинираме следните функции: • Sub find(Part *R,Elem a) • Part join(Part *R,Sub S1,Sub S2) • void init(Part *R, int N)

  11. Имплементация на алгоритъма на Крускал int N,M,g[MM][3],t[MN][2];Part P; void Kruskal() {int j=1;Sub s1,s2; Sort(g); init(&P,N); for(int i=1;i<=M;i++) if (s1=find(&P,g[i][0])!= s2=find(&P,g[i][1])) { join(&P,s1,s2); t[j][0]=g[i][0]; t[j++][1]=g[i][1]; } }

  12. Сложностпо време на алгоритъма на Крускал • Некаозначим с Tinit(n),Tfind(n)иTjoin(n)сложостите по време на имплементациите на функциите. • Тогава за сложността TKr(N,M) на имплeмeнтацията на алг. на Крускал: TKr(N,M) = O(M.logM)+Tinit(n) + + 2.M. Tfind(n) + (N – 1). Tjoin(n)

  13. Имплементация на прост интерфейс за разбиване • typedef {int N,int P[MN];} Part; • Elem= Sub=int. • За всеки елемент a, в P[a]помним елемента с най-малка стойност в неговата част на разбиването – лидер на частта: Дефинираме следните функции: • void init(Part *R, int N) {R->N=N; for(int i=1;i<=N;i++)R->P[i]=i;}

  14. Имплеметация на прост интерфейс за разбиване • int find(Part *R,int a) { return R->P[a];} • void join(Part *R,int S1,int S2) { int L=min(S1,S2);K=max(S1,S2); for(int i=1;i<=R->N;i++) if(R->P[i]==K) R->P[i]==L; }

  15. Сложностпо време на имплементацията • Tinit(N)= O(N), • Tfind(N)= O(1), • Tjoin(N) = O(N). • Тогава за сложността TKr(N,M) на имплeмeнтацията на алг. на Крускал: TKr(N,M) = O(M.logM)+O(N) + 2.M. O(1) + (N – 1). O(N) = O(M.logM + N2) – неприятно за графи с малко ребра

  16. Друга имплементация на интерфейс за разбиване • typedef {int N,int P[MN];} Part; • Elem= Sub=int. • За всеки елемент a, в P[a]помним елмента, корен (лидер) на дърво с всички ел. на частта: Дефинираме следните функции: • void init(Part *R, int N) {R->N=N; for(int i=1;i<=N;i++)R->P[i]=i;}

  17. Имплеметация на прост интерфейс за разбиване • int find(Part *R,int a) { int x=a; while(R->P[x]!=x) x=R->P[x]; return x;} • void join(Part *R,int S1,int S2) { R->P[S2]=S1;}

  18. Сложностпо време на новата имплементацията • Tinit(N)= O(N), • Tfind(N)= O(N), • Tjoin(N) = O(1). • Тогава за сложността TKr(N,M) на имплeмeнтацията на алг. на Крускал: TKr(N,M) = O(M.logM)+O(N) + 2.M. O(N) + (N – 1). O(1) = O(M.logM + M.N) – неприятно за графи с много ребра

  19. Как да подобрим втората имплементация • Балансиране на дървото по височина: за целта добавяме в структурата масив h[] и за всяко дърво помним текущата му височина в h[i]. (h[i]=0 в началото) void join(Part *R,int S1,int S2) { if(h[S2]<h[S1])R->P[S2]=S1; else R->P[S1]=S2; if (h[S2]==h[S1]) h[S2]++; }

  20. Как да подобрим втората имплементация • Повдигане на дърветата: за целта променяме малко функцията find: int find(Part *R,int a) { int x=a,y=a; while(R->P[x]!=x) x=R->P[x]; while(R->P[y]!=x) {z=R->P[y]; R->P[y]=x; y=z;} return x;}

  21. Сложностпо време на имплементацията • Tinit(N)= O(N), • Tfind(N)= O(log* N)  O(1), • Tjoin(N) = O(1). • Тогава за сложността TKr(N,M) на имплeмeнтацията на алг. на Крускал: TKr(N,M) = O(M.logM)+O(N) + 2.M. O(log* N) + (N – 1). O(1) = O(M.logM + M + N) – не зависи същестено от съотнешението на брой върхове и брой ребра.

  22. МОБИФОНИ IOI’2001 В равнината е зададена квадратна мрежа с размери SS,S <=1024. Всяко квадратче е покрито от клетка на мобилен оператор и в него във всеки момент се намират определен брой мобилни телефони. Броят на телефоните в едно квадратче непрекъснато се мени. В определени моменти всяка от клетките докладва на централата за настъпилите изменения, а от време навреме, в централата си задават въпроси за броя на работещите в момента телефони в някаква правоъгълна подобласт на мрежата.

  23. МОБИФОНИ IOI’2001

  24. МОБИФОНИ IOI’2001 едномерен вариант

  25. Абстрактен тип • Дефинирамеабстрактен тип Net –редица от S клетки с операции: • void init(Net* M,int S) - инициализирамрежаM с размер S. • void chng(Net* M,int X,int A) - регистрира измененията; • int find(Net* M,int L,int R)- дава отговори.

  26. Решение scanf(”%d %d”,&code,&S); init(&M,S); while(1) {scanf(”%d”,&code);if(code==3) break; if(code==1) {scanf(”%d %d”,&X,&A);chng(&M,X,A);} else {scanf(”%d %d”,&L,&R); ptintf(”%d\n”,find(&M,L,R));} }

  27. Имплементация на АТ typedef int a[1025] NetM; void init(Net* M,int S) { int i;for(i=1;i<=S;i++) M->a[i] =0; } void chng(Net* M,int X,int A) { M->a[X] +=A; } int find(Net* M,int L,int R) {int i, res=0; for(i=L;i<=R;i++) res+=M->a[i]; return res;}

  28. Сложност • init – O(S) • chng - O(1) • find - O(S) • За целия алгоритъм O(S) + Q1. O(1) + Q2.O(S), където Qk е броят на заявките от тип k Много голям брой заявки от тип 2 ще компрометира решението.

  29. Имплементация на АТ typedef int a[1025] NetM; void init(Net* M,int S) { int i; for(i=1;i<=S;i++) M->a[i] =0; } void chng(Net* M,int X,int A) { int i; for(i=x;i<=S;i++) M->a[X] +=A; } int find(Net* M,int L,int R) {return M->a[R]- M->a[L-1];}

  30. Сложност • init – O(S) • chng - O(S) • find - O(1) • За целия алгоритъм O(S)+Q1. O(S)+ Q2. O(1), където Qk е броят на заявките от тип k Много голям брой заявки от тип 1 ще компрометира решението.

  31. Как да излезем от ситуацията • Изходът от ситуацията в такива задачи (абстр. тип с две “противоречиви” операции) е да се опитаме да направим двете операции сравнително бързи • Обичайното решение е да се замени линейната имплементация (списък) с дървовидна, така че сложността и на двете операции да е сравнима с височината на дървото.

  32. Индексно дърво • За всяко i = 1,2,…,S да пресметнем границите на интервала [i-2k(i)+1,i], където k(i)е броят на нулите отдясно в двоичното представяне на i. В d[i] ще държим сумата на числата в интервала [i-2k(i)+1,i]

  33. Индексно дърво 28 16 9 5 2 3 0 6 6 2 7 3 4 0 5 1

  34. Операциите typedef int a[1025] NetM; void init(Net* M,int S) { int i; for(i=1;i<=S;i++) M->a[i] =0; } void chng(Net* M,int X,int A) { while(X<=S)M->a[X]+=A;X+=k(X);} int find(Net* M,int L,int R) {int res=0; while(R>=1){res+=M->a[R];res-=k(R);} while(L>=1){res-=M->a[L];res-=k(L);} return res;}

  35. Намиране на k(i) • Стойностите на k(i) могат да бъдат табулирани предварително и след това да бъдат вземани от един масив. Това ще добави O(S. log S) стъпки. • По-бързо ще стане ако в началото на всяка от операциите се постави 1 в променлива eи докато e&i != 0се измества eна една позиция наляво (е<<=1). При това за следващата стойност на iне се налага ново зареждане на e, а може да се продължи с текукщата стойност.

  36. Сложност • init – O(S);chng - O(log S) • find - O(log S) • За целия алгоритъм O(S) + Q1. O(log S) + Q2.O(log S)=O(S + Q.log S), където Q е броят на заявките от тип k Реиението вече не зависи от броя заявки от различен тип.За двумерния случай, трябва да се приложи техниката в двете посоки. Направете го за домашно непременно.

  37. ХАМАЛИ – НЕТ, Шумен’08 Бали боклук трябва да бъдат извозени до сметищетопрез канализация - N шахти, 1 е сметището.Всяка шахта Uе свързана с тръба към точно една шахта V<Uи течението ще отнесе до Vвсеки предмет, пуснат вU. Бала, пусната в Uможе да стигне до всяка шахта, която се намира на пътя от Uдо 1, стига в шахтите по пътя да има достатъчно вода, за да не заседне балата в тях. В 10.00:00 сутринта, всички шахти са празни, започва да вали дъжд и шахтите започват да се пълнят с 10 куб.см вода в секунда. Хамалите хвърлят бала в шахта X и я чакат в шахта Y, разположена по пътя от X до 1. Максималният обем, който може да бъде пренесен така, е равен на минималнoто количество вода в куб.см., което се съдържа в шахта по пътя от Xдо Y.

  38. ХАМАЛИ – НЕТ, Шумен’08 Какъв обем може да бъде пренесен от X доY в зададен момент, ако конкурентите от „Цепи-Лепи” АД, не изпомпваха в здадени моменти зададено кол. вода от някои от шахтите. Вход: Първи ред: N и K; Втори ред: за всеки връх – бащатаСледват K заявки: 1 X Y T – колко обем може да мине от X до Yв момент T 2 T X V – конкурентите изпомпват от връх X, в момента T, V куб. см вода Изход. За всяка заявка от тип 1 – тъсеният обем

  39. АНАЛИЗ • Нужни са ни две операции с дървото • void chng(vertex X, int V) • int find(int T,vertex X, vertex Y) • Решение с O(1) за chng() и O(h) за find() e очевидно. При изродени дрвета h=O(N) и сложността на алгоритъма е O(Q.N), кдето Q e общия бройна операциите.Търси се по-добро. Проблемът е, че зададеният тип вече е дърво!!!

  40. АНАЛИЗ Да означим с • t(X) бащата на X, • p(X,Y) пътя от X до Y • W(X) – количеството вода изпомпена от X до момент t. • Тогава наличната водав X в момент t ще бъде C(X)=10.t – W(X) • Jump(X)=X’, като X’(X,1); • F(X) =max W(Y), Y(X,Jump(X))равносилно на търсения min C(Y), Y(X,Jump(X))

  41. Иплементация int t[MAXN],W[MAXN],F[MAXN]; void chng(int X,int V) {W[X]+=V; за (всеки A,Xp(A,Jump(A)) F(A)= max{F(A),W[X]; } int find(int T,int X,int Y) {int i=X,M=0; while(i!=Y) {if(Jump(i)p(i,B)) {M=max(M,F(i);i=Jump(i);} else {M=max(M,W(i);i=t(i);} return 10*T-M; }

  42. Сложност • Нека S e такова, че дължините на p(X,Jump(X)) са колкото може по-близки до Sбез да го надхвърлят – идеалът е точно S • Сложността на chng() е O(S), защото се обхожда един път от X до Jump(X) • Сложността на find() е O(N/S+S), защото се обхожда един път в дървото: първо на стъпки от по около S върха и после не повече от S стъпки по 1 връх

  43. Сложност Сложността на алгоритъма ще бъде • Q1.O(S)+Q2.O(N/S+S), където Q1 и Q2 са брй на операциите отпърви и втори вид • Ако S=sqrt(N), тогава алгоритъмът е много приличен O(Q.sqrt(N)) • Ако се преброят предварително операциите, може да се опита и по-добра стойност за S.

  44. Смятане на Jump(X) Depth[1]=1; Weight[…] = 1; Обхождаме дървото в пост-ордер и за всеки токущо обходен връхА { За(всеки наследникВнаА) { Weight(A)=Weight(A)+ Weight(B); Depth(B)=1+Depth(A); } }

  45. Смятане на Jump(X) Jump(1)= 1; С обхождане на дървото в дълбочина, начален връх 1 иа всеки връхА ≠ 1 {Jump(A)= Jump((А)); while(Weight(Jump(A))–Weight(A)>S) {Jump(A)= наследника наJump(A) в(А,Jump(A);} }

More Related