Friday, March 30, 2012

Рекурсив Функц - 2 Дасгалууд

За өмнөх пост гайгүй бичигдсэн юм шиг байгаа юм. Одоо Рекурс дээр нэмэлт хэдэн юм хэлээд, дасгал бодлогууд өгье.
Програмчлалын тэмцээн уралдаан, ер нь аль ч тохиолдолд болж өгвөл Рекурсээс татгалзах хэрэгтэй. Рекурс нь өөрийгөө дуудах бүртээ санах ойд тухайн функцын хэрэглэж байгаа санах ойн хэмжээг дахин яг хуулбарлаж үүсгэдэг. Энэ нь компьютерийн стак мемориг дүүргэж Ажиллах үеийн алдаа болгон програмыг зогсоодог(RTE - Run Time Error). Таны зохиосон алгоритм хугацааны үнэлгээг Рекурсыг ашигласнаар O(LogN), эсвэл O(N) хүртэл буурч байвал хэрэглэхэд тохиромжтой.

Мөн нэмж хэлэхэд Фибаночийн дарааллыг олох Рекурсив функц санах ойд ямар мод үүсгэж байгааг бид харсан. Ажигласан бол өмнө олцон байсан утгуудыг маш олон удаа дахин олж байгаа. Хэрвээ бид тухайн ямар нэгэн утгыг олцон гэж тэмдэглээд, зөвхөн олоогүй түвшинлүү л рекурс дуудалтыг явуулаад байвал Рекурс маань илүү төгс болох байх. эгэхээр ийм зүйлүүдээс татгалзахын тулд memo[] зэрэг өөрт нь тохирсон өгөгдөлийн бүтцийг тодорхойлж эдгээр хий дэмий үйлдлүүдээс бултаж болно. Жишээ болгож Фибаночийн дарааллыг олох функцыг бичие.
#include<cstdio>
#include<cstdlib>
using namespace std;
int n;
int memo[11];//i-r Gishuuniig oloogui bol -1, olson bol utgiig ni hadgalna
int fib(int k){
if(memo[k]!=-1)return memo[k];
memo[k] = fib(k-1);
memo[k] += fib(k-2);
return memo[k];
}
int main(){
scanf("%d", &n);
for(int i=0; i<=n; i++)memo[i]=-1;
memo[0]=0;
memo[1]=1;
fib(n);
for(int i=1; i<=n; i++)
printf("%d ", memo[i]);
system("pause");
return 0;}

Ерөнхийдөө дээрх жишээнд Динамик Програмчлал ашиглаж байгаа ба өөрөө дундаас дээд түвшнийх биш гэж үздэг бол энэнд санаа зовж оролдох хэрэггүй :).

Дараах дасгалуудыг бүгдийг нь Рекурсив функцээр хийгээрэй.


Дасгал 1:

N ширхэг тоо өгөгдөв, тоонуудыг Массивд хадгалаад урвуу дарааллаар нь хэвлэнэ үү.


Input:

5

69 87 45 21 47

Output:

47 21 45 87 69



Дасгал 2:
Массивт байгаа тоог дараах байдлаар хэвлэнэ үү.

[0] [n-1]

[1] [n-2]

.........

.........

[(n-1)/2] [n/2]




Input:

5

1 5 7 8 9

Output:

1 9

5 8

7 7




Дасгал 3:

Массивт байгаа тоонуудаас сондгой тоонуудыг хасаад массивт үлдсэн тонуудыг хэвлэ. Нэмэлт массив ашиглаж болохгүй, таны програм зөвхөн гараас тоонуудаа массивт хадгалаад Рекурсив функцээ дуудсны дараа main() функцээс хариунуудаа хэвлэнэ.



Input:

6

1 54 88 6 55 7

Output:

54 88 6




Дасгал 4:

N өгөгдөхөд олон гишүүнтийг хэвлэ

1 + x + x^2 + ................. + x^n-1




Input:

5

Output:

1 + x + x^2 + x^3 + x^4




Дасгал 5:

x n өгөгдөхөд дээрх олон гишүүнтээ бодно уу.

Жишээ нь n x=2 and n=5, 1 + x + x2 + ................. + xn-1 = 31



Input:

2 5

Output:

31




Дасгал 6:

Мэдээж n!

Input:

5

Output:

120



Дасгал 7:

n-р фибаночи.


Input:

6

Output:

8



Дасгал 8:

Өгөгдсөн тоо анхных мөн эсэхийг шалга.


Input:

49

999983

1

Output:

not prime

prime

not prime



Дасгал 9:

Өгөгдсөн 2 тооны хамгийн их ерөнхий хуваагчийг ол.


Input:

25 8895

Output:

5



Дасгал 10:



Өгөгдсөн 2 тооны хамгийн бага ерөнхий хуваагдагчийг ол. Дараах Ё-г ашиглахгүй шүү. lcm(a,b) = (a x b) / gcd(a,b);


Input:

23 488

Output:

11224



Дасгал 11:

Массивт байгаа тоонуудаас хамгийн ихийг олох функц бич.

Input:

5

7 4 9 6 2

Output:

9



Дасгал 12:

11-р дасгалын хувьд 2 дахь хамгийн том тоог олно уу.


Input:

5

5 8 7 9 3

Output:

8



Дасгал 13:



Шугаман хайлтыг хийх функц бич, Оролтын формат N, N ширхэг тооны дараа Q буюу хэдэн ширхэг тоо хийх ёстойг илэрхийлнэ, үүний дараа Q ширхэг К тоо байх ба хэрэв К тоо дээрх Массивт байгаа бол хэддэх байрлалд байгааг ол.(0..N-1) гэж дугаарлаарай.



Input:

5

2 9 4 7 6

2

5 9

Output:

not found

1



Дасгал 14:

2тын хайлтыг бичнүү. Дээрх бодлоготой адилхан оролттой, мэдээж Массив эрэмбэлэгдсэн байгаа.


Input:

5

1 2 3 4 5

2

3 -5

Output:

2

not found



Дасгал 15:

Өгөгдсөн тооны хөрвөсөн тоог олох функц зохионуу. Зохиосон функц чинь int төрөл буцаах ёстой шүү.


Input:

123405

Output:

504321



Дасгал 16:

15-р дасгалыг тэмдэгт мөрийн хувьд хийнэ үү. Нэмэлт массив ашиглах ёсгүйг анхаараарай.

Input:

helloo

Output:

oolleh



Дасгал 17:

Бүгд Фибаночийн дарааллыг олох Рекурсив функц санах ойд ямар мод үүсгэж байгааг харсан билээ. Тэгвэл тухайн модонд дараах нэвтрэлтүүдийг хий.





Оролт:

5

Гаралт:

Inorder: 1 3 2 5 2 4 1 3 2

Preorder: 5 3 1 2 4 2 3 1 2

Postorder: 1 2 3 2 1 2 3 4 5



Бодолт

Thursday, March 29, 2012

Рекурс

Альваа юмны тодорхойлолтыг хэлж, цээжлэх шиг амархан юм байхгүй байх. Тэр тусмаа Рекурсив функц. Тодорхойлолт нь их энгийн "Өөрөө өөрийгөө дууддаг функцыг Рекурсив функц гэнээ" гэж. Рекурс бол маш хүчтэй техник, for давталтаар хийж болдог бүх алгоритмыг Рекурсээр хийж болно, харин Рекурсив функцын хийж байгаа зарим хүнд алгоритмыг for давталтаар хийх боломжгүй (Яг ч тийм бишилдээ, гэхдээ ядаж л маш хэцүү).

Бид 10 жилдээ математик дээр ч Рекуррэнт дараалал, функц хангалттай үзэж бодсон болохоор би илүү дутуу доторхойлолт хэлэлгүйгээр рекурсив програм бичихэд хэрэг болох ойлголт, анхааруулгуудыг бичие.

Дараах асуултыг өөртөө тавиад бодож үзээрэй.

- Рекурсив функц нь өөрөө өөрийгөө дуудна гэж юу гэсэн үг вэ?

Бид ямар нэгэн N-р түвшний бодлогыг бодохын тулд тухайн функцээр өөрөөр нь N-1 түвшний бодлогыг бодуулж байгаа учраас өөрийг нь дуудаж байгаа юм. Энэ үйлдэлийг РЕКУРСИВ ДУУДАЛТ гэж нэрлэе.

- Тэгвэл хэзээ өөрийгөө дуудахаа болих вэ?

Бид тухайн бодлогын хамгийн доод түвшин мөн хамгийн суурь баталгаатай хариуг мэдэж байгаа үедээ тухайн хариуг суурь хариу болгож буцаагаад дээшээ илгээснээр Рекурсив дуудалт зогсож тухайн түвшин болгондоо хариугаа олох боломжийг олгодог.

Тэгэхээр бидэнд буцах утга буюу Рекурсив дуудалтыг зогсоох үйлдэл хэрэгтэй.

- Тэгвэл Рекурсив дуудалт зогссны дараа юу хийх вэ?

Эндээс эхлэн Рекурсив буцах үйлдэл хийгдэнэ. Факториал олох Рекурсив функц N-н факториалыг олохын тулд N-1-н факториалыг олохыг шаардаж fac(N-1)-г дууддаг. Бид fac(N-1)-г олцон гэж үзье. Тэгвэл бид N-н факториалыг олж чадна гэсэн үг. Бид N-н факториалыг олж чадна гэдэг чинь N+1-г ч олж чадна гэсэн үг. Энэ үйлдэлийг л РЕКУРСИВ БУЦАЛТ гэж байгаа юм.

Тэгвэл Рекурив Функц-д дараахь 3-н зүйл зайлшгүй хэрэгтэй.

1. Рекурсив Дуудалт

2. Рекурсив Дуудалт Зогсоох

3. Рекурсив Буцалт

Тэгэхээр бид Ганц Рекурсив Функцээр хоёр зүйл хийж чадаж байгаа биз!. Рекурсив Дуудалт хийх үед нь ямар нэг үйлдэл хийж болно, мөн Рекурсив Буцаад явж байхад нь дахин өөр үйлдэл хийж чадахнээ. Рекурсив ийм л боломж олгож байгаа учраас Дуудалт, Буцалт хоёрыг нь ашиглаад уран гоё бодолт, алгоритмууд хийгдэж байгаа юм.

Одоо энгийн for цикл-тэй харьцуулж Рекурсив функц хэрхэн бичихийг үзүүлэх гэж оролдоё. Эндээс Рекурсив дуудалт болон буцалтыг ойлгохыг хичээгээрэй.

Энгийн цикл:
for(int i=0; i<n; i++){
Энд юу дуртайгаа хий
}

Энгийн циклийг Рекурсив функцээр бичвэл:
void FOR(int i, int n) {
if(i==n) return; // Рекурсив Зогсов
// Энд юу дуртайгаа хий
FOR(i+1, n); // Рекурсив Дуудалт
}

Урвуу цикл (өөр олигтой нэр санаанд ордоггүй, Буцах for цикл гэсэн ч болно.)
for(int i = n-1; i >= 0; i -= 1) {
// Энд юу дуртайгаа хий
}

Урвуу циклийн Рекурсив функц
void ROF(int i, int n) {
if(i==n) return; // Рекурсив зогсов
ROF(i+1, n); // Рекурсив Дуудалт
// Энд юу дуртайгаа хий
}

За хө тэгэхээр дээрх 2 Рекурсив Функцын ялгааг харж байна уу? Урвуу циклийг яаж хийж байгаа гэхээр Рекурсивийг буцах үед нь хийж байгаа байх нь. Бүр дэлгэрэнгүй болгох үүднээс N-с 1 хүртэл хэвлэдэх Рекурсив функцыг дэлгэрэнгүй тайлбарлая.
void rec(int i, int n){
if(n+1==i)return;//Рекурсив Зогсов
rec(i+1, n);//Рекурсив дуудалт
printf("%d ", i);//Рекурсив Буцах үед хэвлээд байхнээ.
}

Дээрх функц дараах алхамаар ажиллаж байна.
01|ДУУДАЛТ rec1 i=1
02| ДУУДАЛТ rec2 i=2
03| ДУУДАЛТ rec3 i=3
04| ДУУДАЛТ rec4 i=4
05| ДУУДАЛТ rec5 i=5
06| ДУУДАЛТ function6 with i=6
07| i Рекурсив зогсоох нөхцөлийг хангасан тул Буцаад явна
08| БУЦАЛТ rec5
09| print 5
10| БУЦАЛТ rec4
11| print 4
12| БУЦАЛТ rec3
13| print 3
14| БУЦАЛТ rec2
15| print 2
16| БУЦАЛТ rec1
17| print 1
18|дуусав!

За ер нь ойлгомжтой байх гэж бодож байна.
Бид C++ дээр факториал олох функцыг дараах байдлаар бичдэг.
int fac(int n){
if(n==0)return 1;
return fac(n-1)*n;
}

Бүр дэлгэрэнгүй бичвэл дараах байдлаар бичнэ гэсэн үг.
int fac(int n){
int ret = 1;//Тухайн n-н факториалыг хадгална
if(n==0)return ret;//Буцах нөхцөл
ret = fac(n-1)*n;//Рекурсив дуудалт
return ret;//Рекурсив буцах үйлдэл маань ердөөл n-н факториалыг л буцаах үйлдэл байна6
}



Одоо ийм зүйлийг ойлгосон байх. Рекурсив дуудалтын өмнө хийгдэж байгаа үйлдэл мэдээж Рекурсив дуудалтын өмнө хийгдэнэ, хари Рекурсив дуудалтын доор хийгдэж байгаа үйлдэлийг Рекурсив буцаад явах замдаа хийдэг байхнээ.
Одоо арай жаахан ахисан түвшинд Рекурсын ойлголтуудаас бичвэл, Рекурс нь санах ойд дуудагдах болгондоо мод үүсгэж байдаг. Жишээ нь бидний дээр бичсэн Факториал олох функц бол шууд шулуун мод үүсгэж байгаа. Харин фибаночи дараалал олох Рекурсив функц санах ойд хэрхэн ажиллахыг харъя.

int fib(int n){
if(n<1)return 1;
return fib(n-1)+fib(n-2);
}


Дээрх кодыг бүр энгийнээр бичвэл:
int fib(int n){
int ret = 1;
if(n<1)return ret;//Буцах нөхцөл
ret = fib(n-1);//Рекурсив дуудалт
ret += fib(n-2);//Рекурсив дуудалт, энэ нь санах ойд үүсгэгдэж байгаа модонд нэмж салаа үүсгэж байна.
return ret;//Буцалт
}

N=5 үйд дээрх функцын санах ойд үүсгэсэн мод дараах байдалтай:




Тэгэхээр Факториал олох функц санах ойд шууд шулуун мод л үүсгэж байгаа байх нь. Ингэж санах ойд шинэ салаа үүсгэж байна гэдэг нь бид Рекурсив Буцах үйлдэлийг ганц биш янзаар тодорхойлж чадах нээ. Энэ бол Рекурсын хамгийн том давуу тал. Дараагийн постоор Рекурсиваар бодох дасгал, мөн бодолтуудыг тавина.

Monday, March 26, 2012

Граф - 2 Нэвтрэлт

За анхан шатны хүмүүст Граф - 1 пост тусалсан боловуу. Одоо графын хамгийн суурь техник, мөн хамгийн хүчтэй 2 зүйлийг үзнэ. Түвшний нэвтрэлт, Гүний нэвтрэлт.
Энэ удаагийхаа постоор Түвшний нэлтрэлтийг тайлбарлана.

Түвшний Нэвтрэлт (BFS):

Бидэнд дурын граф өгөгдсөн байгаа, мэдээж компьютертээ дүрсэлцэн байгаа. Тэгвэл графын Үндэс буюу ямар нэгэн оройг Графын анхны 1-р түвшин гэж үзээд Графаа доош зурсан мэтээр дүрслэе. Тэгвэл 2-р түвшин нь 1-р түвшний оройнуудын хөрш оройнууд, 3-р түвшин 2-р түвшин дэх оройнуудын хөрш оройнууд болно. Тэгвэл Түвшний нэвтрэлт нь энэ үүсгэсэн түвшин болгон дээр өөрийн боловсруулалтаа хийхийг хэлж байгаа юм.

Дээрх Графын хувьд

1-р түвшинд 1 дугаартай орой,

2-р түвшинд 2, 3, 4 дугаартай оройнууд,

3-р түвшинд 5, 6, 7, 8 дугаартай оройнууд,

4-р түвшинд 9, 10, 11, 12 дугаартай оройнууд байна.

Одоо Түвшний нэвтрэлт нийх алгоритмаа тайлбайрлая:
Энд би Дараалал гэдэг үгийг хэрэглэх ба Дараалал гэдэгт зүгээр массив ч байдаг юмуу, нэг тийм тоонууд хадгалах өгөгдөлийн бүтэц байгаа гээд ойлгоорой.

1. Дараалалд Графын 1-р түвшний оройг хий.

2. Дарааллын хамгийн эхний элементийг С гэе, мөн дарааллаасаа энэ С оройгоо хасна,

- Өмнө нь очоогүй, С-н бүх хөрш оройнуудыг Дараалалдаа араас нь нэмнэ.

3. Хэрэв дараалал хоосон байвал Түвшний нэвтрэлт нийгдэж дууссан,

4. Хэрэв дараалал хоосон биш байвал 2-р алхамаас дахин хий.

За ер нь хар үгээр шууд хэлвэл, Дараалалд тухайн түвшиндэх бүх оройн бүх хөршүүдийг хадгалаад, уг түвшний оройгоо дарааллаас хасаад яваад байх нь байна. Мэдээж орой болгоноо очсон бол очсон гэж тэмдэглэж явах ёстой. Доорх зурагт үзүүлснээр, хараар очсон оройгоо тэмдэглээд, саарлаар Дараалалд нэмэх оройнуудаа бэлдэж байна.

Дараалал гээд байгаа зүйл маань дараах үйлдлийг хийдэг байх хэрэгтэй.

Элемент нэмдэг (араас нь)

Элемент хасдаг (хамгийн урд байгаа элементийг хасна)

Тэгвэл энэ Дарааллыг массив төрлөөр хийвэл яах вэ?

Массив-д элемент нэмэх үйлдэл O(N)-д, за яахав бидэнд нэмэх үйлдэлийг Дарааллын араас нь нэмээд явах болохоор O(1)-д гэсэн үг.

Массиваас элемент хасах үйлдэл O(N)-д. N орой бүр дээр элемент хасах үйлдлээ O(N)-д хийх юм бол Түвшний Нэвтрэлтийг O(N^2)-д хийхнээ.

Одоо арай өөр өгөгдлийн бүтэц сонирхоё:

Дараалал (Queue):

Queue өгөгдлийн бүтэцийг нэг тийм машин явдаг хонгилтой зүйрлдээ!. Хонгилруу хамгийн эхэнд орсон машин хамгийн эхэнд л нөгөө талаар гарч ирдэг. Энэ яг л тийм зүйл хийдэг өгөгдлийн бүтэц. Тэгэхээр жишээ нь 3, 5, 6 гэсэн 3-н тоог Queue-рүү хийхэд хэзээ ч 5 гэсэн элементрүү хандуулж чадахгүй, Queue-н хийж чаддаг зүйл бол:

Элемент нэмэх O(1)

Элемент хасах (хамгийн урд байгаа элементийг хасах) O(1)

Хэмгийн урд байгаа элементийг авах O(1)

За тэгэхээр яг бидэнд хэрэгтэй өгөгдлийн бүтэц мөн байна. Queue-н тусламжтайгаар бид Түвшний нэвтрэлтээ O(N)-д хэрэгжүүлж чадахнээ.

Одоогийн бараг бүх програмчлалын хэлнүүд Queue өөр бусад хэрэгтэй өгөгдлийн бүтцүүдийг шууд ашиглахаар хэрэгжүүлээд сан нээгээд өгцөн байгаа учир Queue мэтийн өгөгдлийн бүтцүүдийг хэрэгжүүлэхэд санаа зовох хэрэггүй(Гэхдээ сонирхож, өөрөө бичиж үзэх л хэрэгтэй).

C++ хэл дээ queue сан байгаа.

Жишээ бодлого: N оройтой, M ирмэгтэй чиглэлгүй, жингүй, цикл агуулаагүй граф өгөгдөв. A оройгоос тодорхой тооны орой дамжин В орой руу хүрч чадах эсэхийг тогтоо?
А юмуу, В-гээс түвшний нэвтрэлт хийхэд бодогдоно.

Бодолт:
/*
input:
N<=100 M<=100
1. x y
. ..
n. x y
a b
output: YES or NO
*/
/*
input:
5 3
1 2
2 3
4 2
1 5
output:
NO
input:
4 3
1 2
2 3
3 4
1 4
output:
YES
*/
#include<cstdio>
#include<queue>
#include<vector>
#define Max 101
using namespace std;
vector<int>v[Max];
bool visited[Max];//i-r oroi deer ochson esehiig temdeglene
queue<int>q;//daraallaa zarlaj baina, queue<TORORL>NER;
int n, m, a, b;
int main(int argc, char *argv[]) {
scanf("%d%d", &n, &m);
while(m--){
int x, y;
scanf("%d%d", &x, &y);
x--,--y;
v[x].push_back(y);
v[y].push_back(x);
}
scanf("%d%d", &a, &b);
a--,--b;
q.push(a);// Daraalald element nemj baina
while(q.size()){// end herev daraalal hooson bish baival TRUE, ugui bol FALSE butsaadag
int sz = q.size();
//end tuvshin yavj baigaa
while(sz--){
int temp = q.front();//daraalliin ehnii gishuund handaj baina O(1)
visited[temp]=1;//temp oroig ashiglatsan gej temdeglej baina
q.pop();//daraallaas element hasaj baina O(1)
for(int i=0; i<v[temp].size(); i++)//temp oroin horshuudiig avah gej bn
if(!visited[ v[temp][i] ])//medeej temp-n horsh oroi deer zochloogui baih yostoi
q.push(v[temp][i]);// daraalal element nemj baina O(1)
}
}
if(visited[b])printf("YES\n");
else printf("NO\n");
system("pause");
return 0;
}

За асуух юмаа доор бичээд асуугаарай. Хэрэв ойлгосон бол дээрх бодлогыг өргөтгөж, А В орой холбогдсон бол хоорондох зайг олоорой?

Saturday, March 24, 2012

К Урттай Дэд Модны Тоо

Сайн байцгаана уу.
Саяхан кодефорсес дээр болсон нэг тэмцээнд ирсэн модны бодлогын бодолтоо сонирхуулья.

Бодлого:

Өгөгдсөн модонд яг К урттай бүх дэд модны тоог ол. Модны орой 50000-аас хэтрэхгүй К нь 500-аас хэтрэхгүй.

Бодолт:

За ер нь модны бодлого ихэвчлэн Divide and Conquer, dp байдаг. Модны бодлогыг бодохдоо дурын оройгоос нь өлгөөд Rooted Tree болгож төсөөлөх хэрэгтэй. Аль ч оройгоос өлгөхөд модны чанар өөрчлөгдөхгүй. Тэгээд мэдээж Рекурсыг маш сайн ойлгож, эзэмшсэн байх ёстой.

dp[i][j]-ээр i орой дээр эхлэлтэй j урттай бүх дэд модны тоог тэмдэглэвэл хариу:

SUM( dp[i][k-j-1]*dp[u][j] ) энд i=1..N бүх орой, j=1..K, u= i-р оройн бүх хөрш орой. dp-ээ тэмдэглэхдээ анхаарах зүйл А Б гэсэн хөрш оройг тэмдэглэсэн бол Б А-г дахин тооцох ёсгүйг анхаараарай. Рекурсив функцыг доор сонирхуулья.

void dfs(int child, int dad){
for(int i=0; i<v[child].size(); i++){
int u = v[child][i];
if(u==dad)continue;
dfs(u, child);
for(int j=0; j<k; j++)
ret += (long long)( dp[child][k-j-1]*dp[u][j] );
for(int j=0; j<k; j++)
dp[child][j+1] += dp[u][j];
}
}

Wednesday, March 21, 2012

Граф - 1

За сайн байцгаана уу.
Одооноос ерөнхийдөө Төлөвлөгөө постыг даган блогоо хөтлөх болноо.

Эхлээд Графын тухай энгийн оршил бичие:
Граф бол ялан гуяа Комьютерийн шинжлэх ухааны маш том мөн маш чухал хэсэг юм. Графын бодлого тэмцээн уралдаануудад маш олон төрөл, өгөгдөлийн бүтэцтэйгээр орж ирж байна. Энгийнээс нь дурьдвал 2 хэмжээст хавтгайд 2 хотын хоорондох замыг олох бодлогоос эхлээд өөр өөрийн хэмжээтэй усны сувгаар холбогдсон байгууламжаар хамгийн ихдээ хэдэн литр усыг зөөж чадах вэ?(энэ бодлогыг Урсгалын - Maximum Flow Min Cut бодлого гэдэг ба нилээн туршлагатай, шаардлагатай өгөгдлийн бүтэц алгоритмыг тайлбарлсны дараа тайлбарлагдах бодлого) гэх хэцүү бодлого хүртлээ явна. Зөв өгөгдлийн бүтэц сонгох нь бодолтын ажиллах хугацаа, ашиглах санах ой зэрэгт нөлөөлөх хамгийн том зүйл, мөн кодоо цэвэрхэн бичих нь бас л туршлага, цаг хугацаа шаардагдах зүйл. Гэхдээ азаар одоогийн ихэнх програмчлалын хэлүүд ихэнх том том өгөгдөлийн бүтцүүдийг бэлдээд өгцөн байгаа, гэхдээ бид нар онолыг нь сайн ойлгоод эзэмших хэрэгтэй. Мэдээж сурахад тийм ч амар биш, гэхдээ энгийн сэтгээд жаахан цаг гаргаад суухад сурахад ямар ч асуудал байхгүй.

За Граф бол энгийн үгээр хотууд болон тэдгээрийн хоорондох замууд гэж ойлогож болно. Тэгэхээр зүгээр л хоорондоо холбоотой хотуудыг холбож дүрслэхийг энгийн граф. Мөн мэдээж А хотоос Б хот хүрэхэд С км явж хүрдэг байг. Тэгвэл графыг ийм хоорондох зайтай нь дүрсэлсэн бол Жинтэй Граф гэнэ. Тэгвэл дахиад графыг хот чиглэлтэй нь дүрслэхийг Чиглэлт Граф эсрэг тохиолдолд Чиглэлгүй Граф гэнэ. Жишээ нь А Б хот нь хөрш хотууд ба А-аас Б хүрэх зам байдаг ба харин Б хотоос А хүрэх шууд зам байдаггүй байж болно. Үүнийг л чиглэлт граф гэж байгаам. Тэгэхээр Чиглэлт Графын хувьд А->Б байхад Б->А байх албагүй, Чиглэлгүй Графын хувьд бол A->Б, Б->А байх нь тодорхой байхнээ.
За тэгэхээр биднар дараах төрлөөр графуудыг тодорхойлохнээ:

  • Чиглэлгүй Граф
    Энд 3-н оройтой 3-н ирмэгтэй Чиглэлгүй Граф байна.













  • Чиглэлт Граф
    Энд 3-н оройтой 3-н ирмэгтэй Чиглэлт Граф байна.












  • Холимог Граф - Зарим ирмэг нь чинтэй, чингүй, чиглэлтэй, чиглэлгүй байж болно. Гэхдээ нээх хэрэглэгдээд байдаггүй


  • Давхар Граф - энд А хотоос Б хот хүрэх 2-оос олон зам байж болно.


  • Жинтэй Граф


Энд би өргөн хэрэглэгдэж болох төрлийг л бичлээ.
Мөн энд тайлбарлах ёстой зүйл бол Цикл. Цикл гэдэг нь Ямар нэгэн хотоос гараад өгөгдсөн ирмэг-замуудыг ашиглаад анх гарсан хот дээрээ ирж чадаж байвал Цикл байна гэнэ.

Тэгвэл Графын хувьд зэрэг гэж юуг хэлэх вэ? Зэрэг гэдэг нь тухайн оройгоос гарсан болон тухайн орой дээр ирсэн ирмэгүүдийн тоог хэлнэ. Зэрэг нь 2 янз байх ба Дотоод зэрэг, Гадаад зэрэг. Дотоод зэрэг нь тухайн оройгоос гарсан ирмэгүүдийн тоо, харин дотоод ирмэгүүдийн тоо нь тухайн оройруу ирсэн чиглэлтэй ирмэгүүдийн тоог хэлнэ. Дээрх Графын хувьд орой болгоны хувьд Дотоод, Гадаад ирмэгүүдийг тэмдэглэсэн байна.
Одоо дараагийн асуудал Компьютерт Графыг яаж таниулах вэ? Мэдээж ямар нэгэн байдлаар дүрслэх хэрэг гарна. За дараах 2 үндсэн дүрслэлийг тайлбарлая.
Хөршийн Матриц:
Энд бид нар хамгийн энгийн өгөгдөлийн бүтэц төрөл болох Массив ашиглана. Дараах зурган дээр шууд тайлбарлая.

Ерөнхийдөө бол бид V[N][N] энд N-оройн тоо, хэмжээтэй массив ашиглана. V[i][j]-ээр i хотоос j хот хоорондоо холбогдсон эсэхийг тэмдэглэнэ. Тэгэхээр Чиглэлгүй Граф жингүй Граф бол V[i][j]=V[j][i]=1, Чиглэлтэй Жингүй Граф-д V[i][j]=1 байхад V[j][i]=1 байх албагүй. Харин Жинтэй граф байвал V[i][j] = w, энд w нь жин.
Дээрх Граф ямар Граф байна? Мэдээж Жинтэй Чиглэлт Граф. Одоо массивдаа ирмэг болон жинг хадлгалахыг харцгаая.

A B C
A 0 1 5
B -1 0 1
C -1 -1 0

Нээрээ нэмээд нэг юмыг тайлбарлая. Бидэнд өөрөөсөө өөрлүүгээ татсан ирмэгтэй граф байж болно. Үүнийг Гогцоо гэдэг юм. Зүгээр тэгээл ойлгочих хэхэ. Дээрх графын хувьд Гогцоо байхгүй. Мөн хэрэв та лавшруулж бодсон бол Жинтэй графын хувьд хоорондоо ирмэг байхгүй бол юу гэж тэмдэглэх вэ? гэж бодогдоно. Энд тооцох ёстой бол тухайн өгөгдсөн граф-д сөрөг жин өгөгдөх эсэхээс шалтгаална. Хэрэв Сөрөг жин байхгүй гэж өгөгдвөл хоорондоо ирмэггүй хотуудыг зүгээр л -1ээр тэмдэглэчих. Дээрх граф маань сөрөг жин байхгүй гэж үзсэн байна. Харин сөрөг жин байгаа гэсэн бол хоорондоо ирмэггүй хотуудыг маш том тоогоор буюу граф-н жингийн хязгаарлалтаас том тоогоор INF = 2^32-1 тэмдэглэ. Тэгэхээр бодлогын өгүүлбэрээ сайн уншиж, хязгаарлалтуудаас шалтгаалахнээ. Энэ аргын сул тал бол санах ойд N*N*(өгөгдлийн төрөл) шаардах ба хугацааны хувьд санах ойд зүгээр л дүрслэхэд л O(N^2) хугацаа шаардна.

Дараагийн арга бол маш үр дүнтэй аргуудын нэг.
Хөршүүдийг жагсаах арга:
Энд vector-г ашиглах нь тохиромжтой. Бид N-оройн тоо ширхэг Вектор ашиглах ба i Вектор тус бүр тухайн i оройн бүх хөрш оройнуудыг жагсааж хадгална. Дээрх жишээн хувьд бидэн 4-н Вектор хэрэг болох ба дараах байдлаар дүрслэнэ.

V[1] = {2, 3};

V[2] = {1};

V[3] = {1, 4};

V[4] = {1, 3};
Дээрх Граф маань чиглэлгүй, жингүй цикл агуулсан граф байлаа.
Одоо C++ дээрх кодоор сонирхуулаад энэ постоо дуусгая. Асууж тодруулах юмаа коммент бичээд асуучаарай, ойлгомжтой байлгахын тулд аль болох энгийн өөрийн үгээр тайлбарлахыг хичээлээ, мөн дараагийн пост маань Түвшний нэвтрэлт, түүнд хэрэглэгдэх өгөгдөлийн бүтцийн хамт байх болно.

// Chiglelgui Jingui N oroitoi M irmegtei Graph-g 
//Horshiin matrix ashiglan com-d durselj baina!
// Orolt daraahi helbertei:
// 3 4
// 1 2
// 2 3
// 3 4
// 1 4
#include<cstdio>
#include<cstdlib>
using namespace std;
int v[100][100];
int n, m;
int main(){
scanf("%d%d", &n, &m);
while(m--){
int a, b;
scanf("%d%d", &a, &b);
a--;--b;
v[a][b]=1;
v[b][a]=1;//Chigleltei Graph gesen bol ene mor hereggui!!
}
for(int i=0; i<n; i++){
for(int j=0; j<n; j++)
printf("%d ", v[i][j]);
printf("\n");
}
system("pause");
return 0;}


// Chiglelgui Jintei N oroitoi M irmegtei Graph-g 
//Horshiin matrix ashiglan com-d durselj baina!
// Orolt daraahi helbertei:
// 3 4
// 1 2 3
// 2 3 2
// 3 4 4
// 1 4 2
#include<cstdio>
#include<cstdlib>
using namespace std;
int v[100][100];
int n, m;
int main(){
scanf("%d%d", &n, &m);
while(m--){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
a--;--b;
v[a][b]=c;
v[b][a]=c;//Chigleltei Graph gesen bol ene mor hereggui!!
}
for(int i=0; i<n; i++){
for(int j=0; j<n; j++)
printf("%d ", v[i][j]);
printf("\n");
}
system("pause");
return 0;}


// Chiglelgui Jintei N oroitoi M irmegtei Graph-g 
//Horshiin jagsaalt ashiglan com-d durselj baina!
// Orolt daraahi helbertei:
// 3 4
// 1 2 3
// 2 3 2
// 3 4 4
// 1 4 2
#include<cstdio>
#include<cstdlib>
#include<utility>
#include<algorithm>
#include<vector>
using namespace std;
int n, m;
vector< pair<int, int> >v[101];
int main(){
scanf("%d%d", &n, &m);
while(m--){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
a--;--b;
v[a].push_back(make_pair(b, c));
v[b].push_back(make_pair(a, c));//Chigleltei Graph gesen bol ene mor hereggui!!
}
for(int i=0; i<n; i++){
printf("%d -> ", i+1);
for(int j=0; v[i].size(); j++)
printf("%d %d", v[i][j].first+1, v[i][j].second);
printf("\n");
}
system("pause");
return 0;}

Friday, March 16, 2012

Тэмцээн 3

За сайн байцгаана уу. Тэмцээнд оролцсон бүх хүмүүст баярлалаа. Мөн эхний 3-н байр болох
  1. Naranbayar
  2. arigato_dl
  3. Adya 3-т баяр хүргэе.
Инверс 1
Энэ хамгийн амархан бодлого байсан байх. Бүх боломжийг шалгахад хангалттай O(N^2).
Инверс 2
Энэ 2 дахь хамгийн амархан бодлого болох шиг боллоо. Бүх боломжийг шалгахад хугацааны хязгаарлалтыг давж чадахгүй. Энэ бодлого бол Divide and Conquer - н сонгодог бодлого юм. Divide and Conquer-аар бодсоноор O(NLogN)-д шийдэж чадах юм. Удахгүй блогтоо D&C-н талаар рекурс-тэй хамт маш дэлгэрэнгүй пост хийх бодолтой байгаа болохоор ингээд түр орхилоо. Тест сул байсан, үнэндээ би тестээ нэмж амжаагүй байтал тэмцээн эхэлциймаа, тэгээд нэмэх гэхээр алдаа гарчихлаа гээд болдоггүй.
5
Энэ ч бас бичихэд тийм ч амар бодлого биш байсанлдаа. Бодлогын хариу болох тоог баруунаас зүүн хүртэл нь цифр цифрээр нь үүсгээд явна. Анхаарах ёстой зүйл бол жишээ нь n=49 k=1 тэгвэл хариу 50, нөгөө талаас хэрэв К=2 байсан бол 55 байх ёстой. Энийг тооцоод их цэвэрхэн кодоо бичихэд тэгээд болчих байхаа.



Хоёр Тэрэг
Энэ бодлогыг бодохын тулд хоёр тэргээ (r1, c1), (r2, c2), мөн A[][] массивд хөлгийг хадгалсан байгаа гэж үзье. Row[i]-д i-р мөрөнд байгаа тоонуудын нийлбэрийг хадгалая. Мөн Col[i]-д i-р багананд байгаа тоонуудын нийлбэрийг хадгалая.
  • Одоо хоёр тэргээ байрлуулахад дараах гурван тохиолдолд гарч болно. Хоёр тэрэг хоюулаа нэг мөрөнд байрлаж болно.
  • Хоёр тэрэг нэг багананд байрлаж болно.
  • Дээрх хоёрын аль нь ч биш
Энд 1-р тохиолдолд дараах томъёог хялбархаан гаргачих байх.
sum = Row[r] + Col[c1] + Col[c2] - 2*A[r][c1] - 2*A[r][c2]
Мөн 2-р тохиолдолд
sum = Col[c] + Row[r1] + Row[r2] - 2*A[r1][c] - 2*A[r2][c]
3-р тохиолдолд
sum = Row(r1) + Row(r2)+ Col(c1) – A[r2][c1] – 2*A[r1][c1] + Col(c2) – A[r1][c2] – 2*A[r2][c2]
Одоо гол шийдэх асуудал бол 3-р тохиолдолыг O(N^3)-д шийдэх ёстой. Энийг цикл дундаа индексээ ухаалгаар ашиглаад амархан шийдэх хэрэгтэйдээ :).
Модтой тоглоё 6
Энэ бодлого уг нь яаж л бол яаж л бодох бодлого :). BFS буюу түвшний нэвтрэлт хийвэл 3-аас илүү хөрштэй орой болгоноор нь дахин граф байгуулаад, дахин root-ээсээ түшвиний нэвтрэлт хийн гүнийг нь олоход бодлогын хариу гарна.
Эсвэл DFS буюу гүний нэвтрэлт хийвэл салаа зам таарах болгонд тэр салаа замын цэгээ өөр нэг "#" тэмдэгтээр солиод хүрэх газараа хүрсэн тохиолдолд бүх гекурсыг зогсоож #-г тоолоход бодлогын хариу болж чадна. Энэ яагаад гэхээр бидний ашиглаж байгаа граф маань мод учраас Баярын очих замын маршрут цор ганц байгаад оршино.

Кодер.мн дээрх санал:
Кодер бол одоо манай монголын хүүхдүүдийн, тэр тусмаа 10-н жилийхэнд тусалж чадах ганц сайт нь юм. Даанч энэ сайтыг хөгжүүлж байгаа хүн нь ерөөсөө анхаарал тавихгүй юмаа :). Кодерт янз бүрийн алдаа их байна, бас одоо шуудхан хэлэхэд үнэхээр сонирхолгүй, бодлого дэвшүүлж тэмцээн зохиож барихад модертороос шалтгаалахгүй хүндрэлүүд их биш ч байна. Гэхдээ магадгүй ингэж шүүмжилж байхыхаа оронд өөрөө сайт хийгээч гэх байхалдаа. Хэрэв тэгж бодвол сайт хийх цаг зав, хост гээд хийх хүн гарахгүй. Сайтын хөгжүүлэгчид үнэхээр завгүй байгаа бол ядаж сонирхолтой байгаа хүмүүстээ хөгжүүлэх эрхийг нь өгөөд явбал бага ч гэсэн хүмүүст тус дэм болно доо :).

Wednesday, March 14, 2012

Төлөвлөгөө

Тэгж байгаад графын алгоритмууд оруулах бодолтой байна. Үүнд:
  • Граф-н нэвтрэлтүүд, энд Түвшиний нэвтрэлт, гүний нэвтрэлтийг бичих ба Гүний нэвтрэлтийн хувьд Рекурсын талаар нилээн дэлгэрэнгүй заахыг хичээх болноо.
  • Цаашид үзэх алгоритм-н хугацааны үнэлгээг сайжруулахад хэрэг болох өгөгдлийн бүтэцүүдийг тайлбарлаж өгнө.
  • Графыг эмхэтгэх, энд Топологи сортыг тайлбарлах болно.
  • Богино замын алгоритм, Dijkstra, Bellman Ford, Жинтэй болон жингүй графуудын хувьд тус бүрд нь авч үзэх болно.
  • Хамгийн Бага Үнэлгээт мод,Крускал, Примын алгоритм
  • Граф дахь цикл олох
  • Articulation Point
  • Граф-н транзитив холбоо
  • Connected Component
  • Strongly Connected Component
  • Tarjan's algorithm
  • Модны бодлого бодох зөвлөгөө, зарим бодлогын бодолт.
  • Урсгал
  • ..
За одоогоор ингэж төлөвлөж байна. Би өөрөө дээрх зүйлүүдээс сурсан зүйлүүд болон сураагүй ойлгоогүй явж байгаа зүйлүүд ч байгаа. Гэхдээ өөрөө зүтгэж зовж сурсын болохоор, өөрийхөө хар үгээр аль болох энгийн маш ойлгомтой тайлбарлах болноо. Бичих явцадаа хэрэгтэй онол, жишээ бодлогын бодолт болон тодорхой дасгал өгөөд явсан нь дээр байх. Гэхдээ би хичээлийхаа хажуугаар хийж байгаа болохоор нилээн удаж байж пост хийх байх шүү. За тэгээд амжилт хүсье бас хамтдаа сураад явцгаая :).