5. графи определение и основни понятия


Мрежа на дейностите. Метод на критичния път



страница9/9
Дата23.10.2018
Размер0.63 Mb.
#93968
1   2   3   4   5   6   7   8   9

5.10. Мрежа на дейностите. Метод на критичния път


Едно важно приложение на графите е планирането изпълнението на проект. Някои добре известни техники в тази област са CPМ (Critical Path Мethod – метод на критичния път) и PERT (Perfonmance Evaluation and Review Technique – оценка на изпълнението и рецензия). Един проект е съвкупност от дейности, някои от които са свързани с останалите. Например, когато строим къща, изграждането на покрива не мож да започне, преди да са завършени стените. Има два начина за представяне на дейности чрез граф: всяка дейност е връх или всяка дейност е ребро. Ще изберем да представяме дейностите чрез ребра. Завършването на някаква дейност ще разглеждаме като събитие и в графа то ще присъства като връх. Накратко графът, представящ изпълнението на проект, ще се състои от ребра (дейности) и върхове (събития) и ще бъде ориентиран и тегловен. Тегло на дадено ребро ще бъде времетраенето за реализацията на дейността, която то представя. На фиг. 36 е представен графът на дейности по създаването на някакво устройство, което се състои от два възела: възел А и възел Б.

Възел А се доставя от друг производител и доставката отнема 50 дни. Възел Б се изработва на място и това отнема 20 дни. След това възел Б се изпитва, което отнема 25 дни. Накрая, въз основа на резултатите от изпитването, се правят корекции, които отнемат още 15 дни. Докато възелът се изпитва и коригира, трябва да се напише и ръководство за експлоатация. Писането може да започне едва след като възел Б е изработен и отнема 60 дни.

Ф
иг. 36. Граф, представящ изпълнението на проект (мрежа на дейностите)

Ще си поставим задачата да определим кога най-рано и кога най-късно можем да започнем изпълнението на всяка дейност и съответно кога най-рано и кога най-късно ще завършим с изпълнението на всяка дейност.

Тази задача се решава чрез топологично сортиране. Затова и програмата за нейното решаване ще получим чрез целесъобразни изменения в програмата за топологично сортиране. Тук ще се ограничим само с програма за граф, представен с матрица на съседство.

Програмата ще се състои от три части.

I-ва част. Задачата на тази част е намирането на най-ранните моменти на настъпване на събитията в графа. Тази част получаваме от програмата за топологично сортиране, като направим следните промени:


  • добавяме нов масив за регистриране най-ранно настъпване на събитието nrns[]. Елемент nrns[i] съответства на i-ия връх и стойността му трябва да показва момента, в който най-рано може да настъпи събитие i, например nrns[1]=45 означава, че събитие 1 ще настъпи най-рано 45 дни след началото;

  • след като вземем връх (събитие) от опашката, не го извеждаме в топологичния ред, а изчисляване най-дългия път до него;

  • въведена е допълнителна променлива maxT за максималното време за реализация на всички дейности.

В края на I-та част от програмата елементите на масива nrns[] и променливата maxT вече имат стойности. За разглеждания пример те са:

nrns[0]=0,

nrns[1]=45,

nrns[2]=80,

nrns[3]=20,

maxT=80.


II-pа част. Задачата на тази част е намирането на най-късните моменти на настъпване на събитията в графа. Това става пак чрез топологично сортиране, но в обратен ред, т.е. със следните особености:

  • вместо масива с броя на предшествениците br_pred ще ползваме масив с броя на наследниците br_nasl. Броят на наследниците на всеки връх намираме като преброим ненулевите елементи в съответния ред в матрицата на съседство;

  • вместо насива nrns[] сега ще ползваме масив за регистрация на най-късното настъпване на събитията nkns[]. Елемент nkns[i] съответства на i-ия връх и стойността трябва да показва момента, в който най-късно настъпва събитие i, например nkns[1]=65 означава, че събитие 1 ще настъпи най-късно 65 дни след началото;

  • най-напред в опашката ще изпратим събитията (върховете) без наследници. След това ще ги вземем от опашката, ще отбележим, че моментът на тяхното настъпване е maxT, ще намaлим броя на наследниците на техните предшественици с 1 и ще изчислим времето до тяхното настъпване и т.н.

  • за тази втора част можем да копираме първата част, но трябва да отчетем, че сега наследниците и предшествениците са си разменили местата, а това налага да разменим i-тата и j-тата.

III-та част. В тази част от масивите nrns[] и nkns[] получаваме за всяка дейност (ребро) от графа следните времена:

nrzap – най-ранно започване на дадена дейност


nrzav – най-ранно завършване на дадена дейност
nkzap - най-късно започване на дадена дейност
nkzav – най-късно завършване на дадена дейност

Резултатите се извеждат във вид на таблица в текстов файл. За нашия пример програмата извежда следната таблица:

i j d nrzap nrzav nkzap nkzav

0 2 50 0 50 30 80

0 3 20 0 20 0 20

1 2 15 45 60 65 80

3 1 25 20 45 40 65

3 2 60 20 80 20 80

В таблицата i e началото на реброто-дейност, j е краят на реброто-дейност, а d е теглото на реброто или броя дни за извършване на тази дейност. Останалите означения обяснихме по-горе.

Програма 5.19. Организация на изпълнението на проект

# include

# include

#include "graf_mat.h"


void clasGraph::OrgProekt()

{

unsigned i,j,k,n,t,maxT=0,



br_visit=0, // Брой на посетените върхове

*visit, // За масива, отчитащ посетеността на върховете

*br_pred, // За масива, отчитащ броя на предшествениците на всеки връх

*br_nasl, // За масива, отчитащ броя на наследниците на всеки връх

*nrns, // За масива най-ранно настъпване на събитие

*nkns; // За масива най-късно настъпване на събитие

if (!(visit=new unsigned[BrV])) exit(0);

if (!(br_pred=new unsigned[BrV])) exit(0);

if (!(br_nasl=new unsigned[BrV])) exit(0);

if (!(nrns=new unsigned[BrV])) exit(0);

if (!(nkns=new unsigned[BrV])) exit(0);

for (i=0;i

// 1. Търсене най-ранните моменти на настъпване на събитията от графа

for (j=0;j

for (i=0;i

if (br_pred[j]==0) Opashka.DobEl(j);//Връх без предшественици отива в опашката

}

while (Opashka.Br) { //Докато опашката не е празна,...



n=Opashka.Br;

for (i=0;i

Opashka.VzeEl(k);//... вземаме връх от опашката ...

visit[k]=1; //... маркираме го като посетен и...

br_visit++; //... го преброяваме, като посетен.

for (j=0;j

if (A[k][j]!=0 && visit[j]==0){ //На непосетените наследници на к-ия връх ...

br_pred[j]--; //... намаляваме входната степен и...

t=nrns[k]+A[k][j];

if (t>nrns[j]) nrns[j]=t;

if (t>maxT) maxT=t;

if (br_pred[j]==0) Opashka.DobEl(j); //ако стане 0, пращаме го в опашката

}

}

}



if (br_visit

// 2. Търсене най-късните моменти на настъпване на събитията от графа

for (i=0;i

for (j=0;j

for (i=0;i

if (br_nasl[j]==0) Opashka.DobEl(j);//Връх без наследници отива в опашката

}

while (Opashka.Br) { //Докато опашката не е празна,...



n=Opashka.Br;

for (i=0;i

Opashka.VzeEl(k);//... вземаме връх от опашката ...

visit[k]=1; //... маркираме го като посетен и...

br_visit++; //... го преброяваме, като посетен.

for (j=0;j

if (A[j][k]!=0 && visit[j]==0){ //На непосетените предшественици на j-ия връх ...

br_nasl[j]--; //... намаляваме входната степен и...

t=nkns[k]-A[j][k];

if (t

if (br_nasl[j]==0) Opashka.DobEl(j); //ако стане 0, пращаме го в опашката

}

}



}

// 3. Отпечатване на графика на операциите

int nrzap,nkzap,nrzav, nkzav,d;

fout.open("c:\\sdp\\graphs\\DopInf",ios::out);

fout<<"\n i j d nrzap nrzav nkzap nkzav\n";

for (i=0;i

for (j=0;j

if (A[i][j]!=0) {

d=A[i][j];

nrzap=nrns[i];

nrzav=nrzap+d;

nkzav=nkns[j];

nkzap=nkzav-d;

fout<

<
<
<
<
<
<} fout.close();

}
void main()

{

char ime_vh_fl[21];



cout<<"\nИме на входния файл: "; cin>>ime_vh_fl;

fin.open(ime_vh_fl,ios::in);

if (fin.bad()) {cout<<"Няма такъв файл.\n";getch();return;}

fin>>BrV; //Въвеждаме броя на върховете от текстовия файл

clasGraph Graph;

Graph.make_graf();

Graph.display_graf();

//Graph.display_grafTF();

Graph.OrgProekt();

cout<<"\nДопълнителна информация можете да намерите във файл \"DopInf.\"\n";

}
Интересен е например следният граф. Той има два върха (0 и 2) без предшественици т.е. възможно е изпълнението на дейностите от графа да започне едновременно от връх 0 и от връх 2. Той има и два върха без наследници.

Фиг. 37. Граф на дейности

Резултатите от изпълнението на програмата са следните:

i j d nrzap nrzav nkzap nkzav

0 1 11 0 11 0 11

0 4 19 0 19 4 23

1 3 13 11 24 11 24

2 5 15 0 15 4 19

2 6 12 0 12 28 40

2 7 14 0 14 26 40

3 7 16 24 40 24 40

4 6 17 19 36 23 40

5 6 18 15 33 22 40

5.11. Цикли


Нека да си припомним, че цикъл в граф е пътят, който започва от един връх и завършва в същия връх. Цикълът е прост, когато не съдържа вложени цикли. Представляват интерес хамилтоновият цикъл и ойлеровият цикъл.

5.11.1.Хамилтонов цикъл. Задача за търговския пътник


Хамилтонов цикъл в граф се нарича цикъл, който съдържа всички върхове от графа еднократно. Или хамилтоновият цикъл в граф е път, който минава през всички върхове от графа по един път. Графът, чийто върхове образуват хамилтонов цикъл, се нарича хамилтонов граф. Хамилтоновият граф може да бъде тегловен или безтегловен.

Съществуват две класически задачи, свързани с хамилтонови графи:



  • проверката дали даден граф е хамилтонов;

  • намиране хамилтоновия цикъл с най-малка дължина в тегловен хамилтонов граф (задача за търговския пътник).

Тук ще се спрем само на втората задача, т.е. на задачата за търговския пътник, която ще формулираме като задача за намиране на хамилтонов цикъл с най-малка дължина в тегловен неориентиран хамилтонов граф.

Тази задача има много общо със задачата за намиране на всички пътища между два зададени върха в безтегловен граф. Всъщност разликите са следните три:



  • графът е тегловен;

  • началният и крайният връх на търсените пътища в графа е един и същ;

  • трябва да се запомня не само текущия (търсения в момента) път, но и най-краткия от всички намерени до момента пътища;

  • т
    рябва да се изчислява дължината на всеки намерен път - хамилтонов цикъл и да се помни дължината на най-краткия от тях;

  • за решение на задачата се приема най-краткият път от всички намерени пътища-хамилтонови цикли;

  • тъй като търсеният път трябва да бъде хамилтонов цикъл, т.е. трябва да преминава през всички върхове, за начален връх може да се избере всеки връх от графа. За такъв ще изберем 0-ия връх.

Фиг. 38. Граф за илюстрация на задачата за търговския пътник

Ето защо, лесно можем да създадем алгоритъм и да напишем програма за намиране на хамилтонов цикъл с най-малка дължина, като модифицираме съответно алгоритъма и програмата за намиране на всички пътища между два зададени върха в безтегловен граф. Полезно е да си припомним, че алгоритъма и програмата за намиране на всички пътища между два зададени върха в безтегловен граф получихме като модифицирахме алгоритъма за обхождане на граф в дълбочина.

За да изясним алгоритъма ще използваме графа от фиг. 38.

1. По алгоритъма за обхождане на граф в дълбочина, ще получим първия хамилтонов цикъл 0-1-2-3-4-5-0 с дължина 31 и ще го приемем за минимален (най-кратък);

2. За да намерим друг път, ще отстъпим от 0-ия връх в 5-ия и ще обявим 0-ия за непосещаван. Ще отстъпим и от 5-ия връх в 4-ия и ще обявим 5-ия за непосещаван. Ще отстъпим и от 4-ия връх в 3-ия и ще обявим 4-ия за непосещаван. От 3-ия връх можем да продължим напред и да получим втория хамилтонов цикъл 0-1-2-3-5-4-0 със същата дължина.

3. За да намерим друг път, ще отстъпим последователно от 0-ия връх в 4-ия и ще обявим 0-ия за непосещаван. Ще отстъпим и от 4-ия връх в 5-ия и ще обявим 4-ия за непосещаван. Ще отстъпим и от 5-ия връх в 3-ия и ще обявим 5-ия за непосещаван. Ще отстъпим и от 3-ия връх в 2-ия и ще обявим 3-ия за непосещаван. От 3-ия връх можем да продължим напред и да получим третия хамилтонов цикъл 0-1-2-4-3-5-0 със същата дължина 28. Този път (хамилтонов цикъл) е по кратък, затова го приемаме за минимален.

4. По описания начин ще намерим четвъртия хамилтонов цикъл 0-1-4-2-3-5-0 с дължина 31, т.е. той не е по кратък от третия.

5. Пак по описания начин ще намерим петия хамилтонов цикъл 0-4-1-2-3-5-0 с дължина 33, т.е. и той не е по кратък от третия.

Минимален се оказа третият хамилтонов цикъл.

Програма 5.20. Намиране на хамилтънов цикъл с минимална дължина в тегловен граф, предстаен чрез матрица на съседство. Задача за търговския пътник

#include

# include

#include "graf_mat.h"

int tekSum, //Дължина на текущия цикъл

minSum; //Дължина на минималния цикъл

unsigned

*tekCycle, //За масива, съдържащ върховете на текущия цикъл

*minCycle, //За масива, съдържащ върховете на минималния цикъл

*visit; //За масива, регистриращ посетените върхове

//Рекурсивна член-функция за намиране минималния хамилтонов цикъл

void clasGraph::hamDFS(unsigned i, unsigned nivo)

{

unsigned k;



if (i==0 && nivo>0) { //Край на търсенето на пореден по-кратък цикъл

if (nivo==BrV) { //Намерен е по-кратък цикъл и ...

minSum=tekSum; // ...регистрираме него, като минимален

for (k=0;k

}

return;


}

if (visit[i]) return;

visit[i]=1;//Регистрираме i-ия връх като посетен и ...

for (k=0;k

if (A[i][k] && k!=i) {

tekCycle[nivo]=k;

tekSum+=A[i][k];

if (tekSum

tekSum-=A[i][k];

}

visit[i]=0;



}
void main()

{

char ime_vh_fl[21]; unsigned k;



cout<<"\nИме на входния файл: "; cin>>ime_vh_fl;

fin.open(ime_vh_fl,ios::in);

if (fin.bad()) {cout<<"Няма такъв файл.\n";getch();return;}

fin>>BrV; //Въвеждаме от текстовия файл броя на върховете на графа

clasGraph Graph; //Създава обекта Graph

Graph.make_graf(); //Запълва матрицата на съседство

Graph.display_graf(); //Извежда на екрана матрицата на съседство

if (!(tekCycle=new unsigned [BrV])) exit(0);

if (!(minCycle=new unsigned [BrV])) exit(0);

if (!(visit=new unsigned [BrV])) exit(0);

for (k=0;k

minSum=9999; tekSum=0; tekCycle[0]=1;

Graph.hamDFS(0,0);

cout<<"Mинималният хамилтонов цикъл е 0 ";

for (int i=0; i

cout<<"0 с дължина на пътя " <

}
Програма 5.21. Намиране на хамилтонов цикъл с минимална дължина в тегловен граф, представен чрез масив от списъци на съседство. Задача за търговския пътник

//Хамилтънов цикъл. Задача за търговския пътник.

# include

# include

#include "graf_sp.h"

int tekSum,//Дължина на текущия цикъл

minSum;//Дължина на минималния цикъл

unsigned


*tekCycle, //За масива, съдържащ върховете на текущия цикъл

*minCycle, //За масива, съдържащ върховете на минималния цикъл

*visit; //За масива, регистриращ посетените върхове

//Рекурсивна член-функция за намиране минималния хамилтонов цикъл

void clasGraph::hamDFS(unsigned i, unsigned nivo)

{

unsigned k;



if (i==0 && nivo>0) { //Край на поредото търсене на по-кратък цикъл

if (nivo==BrV) { //Намерен е по-кратък цикъл

minSum=tekSum;

for (k=0;k

}

return;


}

if (visit[i]) return;

visit[i]=1;

rebro *p=a[i].first;

while (p){

k=p->nom;

if (k!=i) {

tekCycle[nivo]=k;

tekSum+=p->teg;

if (tekSum

tekSum-=p->teg;

}p=p->next;

}visit[i]=0;

}
void main()

{

char ime_vh_fl[21]; unsigned k;



cout<<"\nИме на входния файл: "; cin>>ime_vh_fl;

fin.open(ime_vh_fl,ios::in);

if (fin.bad()) {cout<<"Няма такъв файл.\n";getch();return;}

fin>>BrV; //Въвеждаме от текстовия файл броя на върховете на графа

clasGraph Graph; //Създава обекта Graph

Graph.make_graf(); //Запълва матрицата на съседство

Graph.display_graf(); //Извежда на екрана матрицата на съседство

if (!(tekCycle=new unsigned [BrV])) exit(0);

if (!(minCycle=new unsigned [BrV])) exit(0);

if (!(visit=new unsigned [BrV])) exit(0);

for (k=0;k

minSum=9999; tekSum=0; tekCycle[0]=1;

Graph.hamDFS(0,0);

cout<<"\nMинималният хамилтънов цикъл е 0 ";

for (int i=0; i

cout<<"0 с дължина на пътя " <

}

5.11.2.Ойлеров цикъл


Ц
икълът е ойлеров, когато по всяко ребро се преминава еднократно. Той носи името на един от най-големите математици на всички времена Леонард Ойлер, който е дал много на инженерните науки, използвайки математичните си знания за решаване на задачи от реалния живот. Много често, когато познатата математика не достатъчна, за да реши възникналия проблем, той създава нови математични методи и по този начин развива и самата математика.

Ойлер преподавал в университета в Калининград, наричан тогава Кьонигсберг. Градът е разположен на двата бряга и на два острова на р. Прегел. Четирите части на града се свързвали помежду си чрез седем моста, както е показано на фиг. 39.

Фиг. 39. Скица на град Калининград

Гражданите на този град много обичали разходките из града и докато се разхождали се запитали, дали е възможно гражданин на този град да излезе от дома си, да мине по един път през всеки мост и да се прибере отново у дома си. Тази, на пръв поглед проста задача, се оказала такава главоблъсканица, че привлякла вниманието дори на именития Ойлер.

Ойлер представил четирите части от града с кръгчета, а мостовете - чрез линии (фиг.40) и веднага заключил, че описаната разходка не е възможна. За да е възможна, всека част на града (A, B, C и D) трябва да е свързана с останалите с четен брой мостове. Това е така, защото, когато разхождащият се навлезе в терирорията на някоя ч
аст по един мост, трябва да има още един мост, по който да излезе от нея. Има само две изключения от това правило: когато започва и когато завършва разходката.
Фиг. 40. Скица на Ойлер на град Калининград

Представяйки града и неговите мостове по този начин, Ойлер създал първия граф и дефинирал самото понятие граф. Наред с това, той веднага осъзнал, че с това поставя началото на нов дял от математиката – теорията на графите.



Ойлер имал славата на човек, който може да реши всяка задача, формулирана като математическа. Когато не съществуват математическите знания, нужни за решаването на дадаена задача, той ги създавал, какъвто е случаят с теорията на графите.




Сподели с приятели:
1   2   3   4   5   6   7   8   9




©obuch.info 2024
отнасят до администрацията

    Начална страница