Efikasiteti kohor i programit sipas algoritmit përkatës. Konceptet e kompleksitetit dhe efikasitetit të algoritmeve dhe strukturave të të dhënave. Çfarë do të bëjmë me materialin e marrë?

Efikasiteti i algoritmitështë një veti e një algoritmi që lidhet me burimet llogaritëse të përdorura nga algoritmi. Algoritmi duhet të analizohet për të përcaktuar burimet e kërkuara nga algoritmi. Efikasiteti i algoritmit mund të konsiderohet si analog me produktivitetin e prodhimit të proceseve të përsëritura ose të vazhdueshme.

Për të arritur efikasitetin maksimal, ne duam të reduktojmë përdorimin e burimeve. Megjithatë, burimet e ndryshme (si koha dhe memoria) nuk mund të krahasohen drejtpërdrejt, kështu që cili nga dy algoritmet konsiderohet më efikas shpesh varet nga cili faktor është më i rëndësishëm, siç është kërkesa për shpejtësi të lartë, përdorimi minimal i kujtesës ose një masë tjetër e efikasiteti.

Ju lutemi vini re se ky artikull JO në lidhje me optimizimin e algoritmit, i cili diskutohet në artikujt optimizimi i programit, optimizimi i përpiluesit, optimizimi i ciklit, optimizuesi i kodit të objektit, dhe kështu me radhë. Vetë termi "optimizim" është mashtrues, sepse gjithçka që mund të bëhet bie nën ombrellën e "përmirësimit".

Sfondi

Rëndësia e efikasitetit me një theks në kohën e ekzekutimit u theksua nga Ada Lovelace në 1843 në lidhje me motorin mekanik analitik të Charles Babbage:

“Pothuajse në të gjitha llogaritjet, ka një zgjedhje të madhe konfigurimesh të mundshme për të përfunduar me sukses procesin, dhe konventa të ndryshme duhet të ndikojnë në zgjedhjen për qëllimin e kryerjes së llogaritjes. Gjëja kryesore është të zgjidhni një konfigurim që do të rezultojë në minimizimin e kohës së nevojshme për të kryer llogaritjen."

Kompjuterët elektronikë të hershëm ishin shumë të kufizuar si në shpejtësi ashtu edhe në memorie. Në disa raste, është kuptuar se ekziston një shkëmbim i kujtesës kohore, në të cilin një detyrë ose duhet të përdorë një sasi të madhe memorie për të arritur shpejtësi të lartë, ose të përdorë një algoritëm më të ngadaltë që përdor një sasi të vogël memorie pune. Në këtë rast, u përdor algoritmi më i shpejtë për të cilin memoria e disponueshme ishte e mjaftueshme.

Kompjuterët modernë janë shumë më të shpejtë se ata kompjuterë të hershëm dhe kanë shumë më tepër memorie (gigabajt në vend të kilobajt). Megjithatë, Donald Knuth thekson se efikasiteti mbetet një faktor i rëndësishëm:

"Në disiplinat e themeluara inxhinierike, një përmirësim prej 12% është lehtësisht i arritshëm dhe nuk është konsideruar kurrë pengues, dhe unë besoj se e njëjta gjë duhet të jetë e vërtetë në programim."

Rishikimi

Një algoritëm konsiderohet efikas nëse konsumi i burimit të tij (ose kostoja e burimit) është në ose nën një nivel të pranueshëm. Përafërsisht, "i pranueshëm" këtu do të thotë "algoritmi do të funksionojë për një kohë të arsyeshme në një kompjuter të disponueshëm". Për shkak se ka pasur një rritje të konsiderueshme në fuqinë përpunuese dhe kujtesën e disponueshme të kompjuterëve që nga vitet 1950, "niveli i pranueshëm" aktual nuk ishte i pranueshëm as 10 vjet më parë.

Prodhuesit e kompjuterëve lëshojnë periodikisht modele të reja, shpesh më të fuqishme. Kostoja e softuerit mund të jetë mjaft e lartë, kështu që në disa raste është më e lehtë dhe më e lirë të keni performancë më të mirë duke blerë një kompjuter më të shpejtë që është në përputhje me kompjuterin tuaj ekzistues.

Ka shumë mënyra për të matur burimet e përdorura nga një algoritëm. Dy matjet më të përdorura janë shpejtësia dhe memoria e përdorur. Matjet e tjera mund të përfshijnë shpejtësinë e transferimit, përdorimin e përkohshëm të diskut, përdorimin afatgjatë të diskut, konsumin e energjisë, koston totale të pronësisë, kohën e përgjigjes ndaj sinjaleve të jashtme, etj. Shumë nga këto matje varen nga madhësia e të dhënave hyrëse të algoritmit (d.m.th., nga sasitë që kërkojnë përpunim të të dhënave). Matjet mund të varen gjithashtu nga mënyra në të cilën paraqiten të dhënat (për shembull, disa algoritme klasifikimi nuk funksionojnë keq në të dhënat tashmë të renditura ose kur të dhënat renditen në rend të kundërt).

Në praktikë, ka faktorë të tjerë që ndikojnë në efektivitetin e algoritmit, siç është saktësia dhe/ose besueshmëria e kërkuar. Siç shpjegohet më poshtë, mënyra se si zbatohet një algoritëm mund të ketë gjithashtu një efekt të rëndësishëm në performancën aktuale, megjithëse shumë aspekte të zbatimit janë çështje optimizimi.

Analiza teorike

Në analizën teorike të algoritmeve, është praktikë e zakonshme të vlerësohet kompleksiteti i një algoritmi në sjelljen e tij asimptotike, domethënë, të pasqyrohet kompleksiteti i algoritmit në funksion të madhësisë së hyrjes. n Përdoret shënimi i madh O. Ky vlerësim është përgjithësisht mjaft i saktë për të mëdha n, por mund të çojë në përfundime të pasakta në vlera të vogla n(Kështu, renditja me flluskë, e cila konsiderohet e ngadaltë, mund të jetë më e shpejtë se renditja e shpejtë nëse ju duhet vetëm të renditni disa elementë).

Emërtimi Emri Shembuj
O(1) (\style ekrani O(1)\,) të përhershme Përcaktimi nëse një numër është çift apo tek. Përdorimi i një tabele kërkimi me madhësi konstante. Përdorimi i një funksioni hash të përshtatshëm për të zgjedhur një element.
O (log ⁡ n) (\displaystyle O(\log n)\,) logaritmike Gjetja e një elementi në një grup të renditur duke përdorur kërkimin binar ose pemën e balancuar, të ngjashme me operacionet në grumbullin binomial.
O(n) (\style ekrani O(n)\,) lineare Gjetja e një elementi në një listë të parregulluar ose pemë të pabalancuar (rasti më i keq). Shtimi i dy n-numrat bit duke përdorur bartje nga skaji në fund.
O (n log ⁡ n) (\displaystyle O(n\log n)\,) kuazilinear, logaritmikisht linear Llogaritni transformimin e shpejtë të Furierit, grupimin, renditjen e shpejtë (rasti më i mirë dhe mesatar), renditja e bashkimit
O (n 2) (\style ekrani O(n^(2))\,) katrore Duke shumëzuar dy n-numrat shifrorë duke përdorur një algoritëm të thjeshtë, renditje me flluska (rasti më i keq), renditje me guaskë, renditje e shpejtë (rasti më i keq), renditje përzgjedhëse, renditje futëse
O (c n) , c > 1 (\displaystyle O(c^(n)),\;c>1) eksponenciale Gjetja e një zgjidhjeje (të saktë) për problemin e shitësit udhëtues duke përdorur programimin dinamik. Përcaktimi nëse dy pohime logjike janë ekuivalente duke përdorur kërkimin shterues

Testet e verifikimit: Matja e performancës

Për versionet e reja të softuerit ose për të ofruar krahasim me sistemet rivale, ndonjëherë përdoren standarde për të krahasuar performancën relative të algoritmeve. Nëse, për shembull, lëshohet një algoritëm i ri klasifikimi, ai mund të krahasohet me paraardhësit për t'u siguruar që algoritmi është të paktën po aq efikas në të dhënat e njohura sa të tjerët. Testet e performancës mund të përdoren nga përdoruesit për të krahasuar produkte nga prodhues të ndryshëm për të vlerësuar se cili produkt do t'i përshtatet më mirë kërkesave të tyre për sa i përket funksionalitetit dhe performancës.

Disa teste standarde ofrojnë analizë krahasuese të gjuhëve të ndryshme të përpilimit dhe interpretimit, të tilla si Koleksioni i PC Benchmark i Roy Longbottom, dhe Loja e standardeve të gjuhës kompjuterike krahason performancën e zbatimeve të detyrave tipike në disa gjuhë programimi.

Çështjet e zbatimit

Çështjet e zbatimit mund të ndikojnë gjithashtu në performancën aktuale. Kjo përfshin zgjedhjen e gjuhës së programimit dhe mënyrën në të cilën algoritmi është koduar në të vërtetë, zgjedhjen e përkthyesit për gjuhën e zgjedhur ose opsionet e përpiluesit të përdorur, madje edhe sistemin operativ të përdorur. Në disa raste, një gjuhë e zbatuar si përkthyes mund të jetë dukshëm më e ngadaltë se një gjuhë e zbatuar si përpilues.

Ka faktorë të tjerë që mund të ndikojnë në kohën ose përdorimin e kujtesës që janë jashtë kontrollit të programuesit. Kjo përfshin shtrirjen e të dhënave, duke detajuar, grumbullimi i mbeturinave , paralelizmi i nivelit të udhëzimeve dhe thirrja e nënrutinës .

Disa procesorë kanë aftësinë për të kryer operacione vektoriale, gjë që lejon një operacion të përpunojë operandë të shumtë. Mund të jetë ose jo e lehtë të përdoren veçori të tilla në nivel programimi ose kompilimi. Algoritmet e dizajnuara për llogaritje sekuenciale mund të kërkojnë ridizajnim të plotë për të akomoduar llogaritjen paralele.

Një çështje tjetër mund të lindë me përputhshmërinë e procesorit, ku udhëzimet mund të zbatohen ndryshe, kështu që udhëzimet në disa modele mund të jenë relativisht më të ngadalta në modele të tjera. Ky mund të jetë një problem për përpiluesin optimizues.

Matja e përdorimit të burimeve

Matjet zakonisht shprehen në funksion të madhësisë së hyrjes n.

Dy dimensionet më të rëndësishme janë:

  • Koha: Sa kohë zgjat algoritmi në CPU.
  • Kujtesa: Sa memorie pune (zakonisht RAM) nevojitet për algoritmin. Ka dy aspekte për këtë: sasia e memories për kodin dhe sasia e memories për të dhënat mbi të cilat funksionon kodi.

Për kompjuterët me bateri (si laptopët) ose për llogaritjet shumë të gjata/të mëdha (si superkompjuterët), një lloj tjetër matjeje është me interes:

  • Konsumi i drejtpërdrejtë i energjisë: Energjia e nevojshme për të drejtuar një kompjuter.
  • Konsumi indirekt i energjisë: Energjia e nevojshme për ftohje, ndriçim etj.

Në disa raste, nevojiten matje të tjera, më pak të zakonshme:

  • Madhësia e ingranazheve: Gjerësia e brezit mund të jetë faktori kufizues. Kompresimi mund të përdoret për të zvogëluar sasinë e të dhënave të transferuara. Shfaqja e një grafike ose imazhi (siç është logoja e Google) mund të rezultojë në transferimin e dhjetëra mijëra bajteve (48K në këtë rast). Krahasoni këtë me transmetimin e gjashtë bajteve në fjalën "Google".
  • Kujtesa e jashtme: Kërkohet memorie në një disk ose pajisje tjetër ruajtëse të jashtme. Kjo memorie mund të përdoret për ruajtje të përkohshme ose për përdorim në të ardhmen.
  • Koha e përgjigjes: Ky cilësim është veçanërisht i rëndësishëm për aplikacionet në kohë reale ku kompjuteri duhet t'i përgjigjet shpejt ngjarjeve të jashtme.
  • Kostoja totale e pronësisë: Parametri është i rëndësishëm kur synohet të ekzekutojë një algoritëm të vetëm.

Koha

Teoria

Ky lloj testi varet dukshëm edhe nga zgjedhja e gjuhës programuese, kompajlerit dhe opsioneve të tij, kështu që algoritmet e krahasuara duhet të zbatohen në të njëjtat kushte.

Kujtesa

Ky seksion trajton përdorimin e memories kryesore (shpesh RAM) të nevojshme nga algoritmi. Ashtu si me analizën e kohës së mësipërme, analiza e një algoritmi zakonisht përdor kompleksiteti hapësinor i algoritmit për të vlerësuar kujtesën e kërkuar të kohës së funksionimit në funksion të madhësisë së hyrjes. Rezultati zakonisht shprehet në termat "O" i madh.

Ekzistojnë katër aspekte të përdorimit të kujtesës:

  • Sasia e memories që kërkohet për të ruajtur kodin e algoritmit.
  • Sasia e memories që kërkohet për të dhënat hyrëse.
  • Sasia e memories që kërkohet për çdo dalje (disa algoritme, të tilla si renditjet, shpesh e riorganizojnë hyrjen dhe nuk kërkojnë memorie shtesë për daljen).
  • Sasia e memories që kërkohet nga procesi llogaritës gjatë llogaritjes (kjo përfshin variabla të emërtuar dhe çdo hapësirë ​​të grumbullit të kërkuar për thirrjet nënrutinë, të cilat mund të jenë të rëndësishme kur përdorni rekursion).

Kompjuterët elektronikë të hershëm dhe kompjuterët shtëpiak kishin kapacitete relativisht të vogla të memories së punës. Kështu, në vitin 1949 EDSAC kishte një memorie pune maksimale prej 1024 fjalësh 17-bit, dhe në vitin 1980 Sinclair ZX80 u lëshua me 1024 bajt memorie pune.

Kompjuterët modernë mund të kenë sasi relativisht të mëdha memorie (ndoshta gigabajt), kështu që ngjeshja e memories së përdorur nga një algoritëm në një sasi të caktuar memorie është më pak e nevojshme se më parë. Megjithatë, ekzistenca e tre kategorive të ndryshme të kujtesës është domethënëse:

  • Cache (shpesh statike RAM) - funksionon me shpejtësi të krahasueshme me CPU
  • Kujtesa kryesore fizike (shpesh RAM dinamik) - funksionon pak më ngadalë se CPU
  • Kujtesa virtuale (shpesh në disk) - jep iluzionin e memories së madhe, por punon mijëra herë më ngadalë se RAM.

Një algoritëm memoria e kërkuar e të cilit përshtatet në cache-in e kompjuterit është shumë më i shpejtë se një algoritëm që përshtatet në memorien kryesore, e cila, nga ana tjetër, do të jetë shumë më e shpejtë se një algoritëm që përdor hapësirën virtuale. Çështjet e ndërlikuara është fakti se disa sisteme kanë deri në tre nivele cache. Sisteme të ndryshme kanë sasi të ndryshme të këtyre llojeve të memories, kështu që efekti i kujtesës në një algoritëm mund të ndryshojë ndjeshëm nga një sistem në tjetrin.

Në ditët e para të llogaritjes elektronike, nëse një algoritëm dhe të dhënat e tij nuk përshtateshin në memorien kryesore, ai nuk mund të përdorej. Këto ditë, përdorimi i memories virtuale siguron memorie masive, por me koston e performancës. Nëse algoritmi dhe të dhënat e tij përshtaten në cache, mund të arrihet shpejtësi shumë e lartë, kështu që minimizimi i memories së kërkuar ndihmon në minimizimin e kohës. Një algoritëm që nuk përshtatet plotësisht në cache, por ofron lokaliteti i lidhjeve, mund të funksionojë relativisht shpejt.

Shembuj të algoritmeve efektive

Kritika për gjendjen aktuale të programimit

Programet po bëhen më të ngadalshëm më shpejt se sa kompjuterët po bëhen më të shpejtë.

May shprehet:

Në sistemet e përhapura, përgjysmimi i ekzekutimit të instruksioneve mund të dyfishojë jetëgjatësinë e baterisë dhe të dhënat e mëdha ofrojnë një mundësi për algoritme më të mira: Reduktimi i numrit të operacioneve nga N x N në N x log(N) ka një efekt të fortë për N të mëdha... Për N =30 miliardë, këto ndryshime janë të ngjashme me 50 vjet përmirësime teknologjike.

Konkurrenca për algoritmin më të mirë

Konkurset e mëposhtme ftojnë pjesëmarrjen në zhvillimin e algoritmeve më të mira, kriteret e cilësisë së të cilave përcaktohen nga gjyqtarët:

Shiko gjithashtu

  • Kodimi aritmetik është një lloj kodimi i entropisë me gjatësi kodi të ndryshueshme për kompresim efikas të të dhënave
  • Një grup shoqërues është një strukturë të dhënash që mund të bëhet më efikase kur përdoret pemë PATRICIA ose Vargjet Judy
  • Testi i performancës - një metodë për matjen e kohës krahasuese të ekzekutimit në raste të caktuara
  • Rasti më i mirë, më i keq dhe mesatar- konventat për vlerësimin e kohës së ekzekutimit për tre skenarë
  • Kërkimi binar është një teknikë e thjeshtë dhe efektive për të kërkuar një listë të renditur
  • Tabela e degëve

Qëllimet dhe objektivat e leksionit: hyrje në metodat për analizimin e kompleksitetit dhe efikasitetit të algoritmeve dhe strukturave të të dhënave

Çështjet kryesore: analiza eksperimentale dhe analitike e efektivitetit të algoritmeve.

Deklarata klasike e N. Wirth "Një program i mirë është uniteti i një algoritmi të mirëmenduar dhe strukturave efektive të të dhënave."

Analiza e Algoritmit
Konceptet e "algoritmit dhe strukturave të të dhënave" janë qendrore në fushën e teknologjisë kompjuterike, por për t'i quajtur struktura dhe algoritme të caktuara të të dhënave "me cilësi të lartë dhe efikase", duhet të përdoren teknika të sakta për t'i analizuar ato. Si kriter i cilësisë natyrale, është e natyrshme të theksohet, së pari, koha e ekzekutimit. Gjithashtu e rëndësishme është sasia e burimeve të harxhuara të memories dhe hapësirës së diskut, shpejtësia e aksesit të të dhënave (efikasiteti i strukturës së të dhënave). Vëmendje duhet t'i kushtohet gjithashtu besueshmërisë dhe besueshmërisë së vendimeve, stabilitetit të tyre.

Algoritmi nuk duhet të lidhet me një zbatim specifik. Për shkak të shumëllojshmërisë së mjeteve të programimit të përdorura, algoritmet që janë të ndryshëm në zbatim mund të prodhojnë rezultate që ndryshojnë në efikasitet.

Koha e ekzekutimit të një algoritmi ose operacioni në një strukturë të dhënash varet, si rregull, nga një sërë faktorësh. Mënyra më e thjeshtë për të përcaktuar kohën e nevojshme për ekzekutimin e një algoritmi është matja e kohës para dhe pas ekzekutimit të algoritmit.

Sidoqoftë, duhet të mbahet mend se kjo metodë e vlerësimit të kohës nuk është e saktë; para së gjithash, duhet kuptuar se në sistemet moderne operative disa detyra mund të ekzekutohen paralelisht dhe ekzekutimi i një rasti testimi mund të kombinohet me lloje të tjera. të veprimtarisë. Më tej, duhet kuptuar se një varësi e qëndrueshme mund të arrihet vetëm duke kryer teste të përsëritura, përndryshe, për shkak të ndikimit në rezultatin përfundimtar të punës së faktorëve të rastësishëm në varësi të specifikave të të dhënave fillestare, dhe faktorëve të tjerë, ekzekutimi koha e algoritmit do të jetë gjithashtu një ndryshore e rastësishme. Gjatë kryerjes së hulumtimit, është e nevojshme të ekzekutohet algoritmi me një grup të ndryshëm të dhënash fillestare; zakonisht vetë të dhënat gjenerohen në mënyrë të rastësishme, kështu që për shkak të grupeve të ndryshme të të dhënave, koha e shpenzuar gjithashtu do të ndryshojë.

Pasi të merret një grup vlerësimesh, mund të ndërtohet dhe të përafrohet një grafik.

Një analizë e tillë duhet të përdoret gjithmonë kur përdoren algoritme jo të parëndësishme; kjo është e ngjashme me rekomandimin për të zhvilluar një aplikacion, duke përdorur për korrigjimin jo një grup prove prej disa dhjetëra rekordesh ose elementësh, por të dhëna reale në tërësi, të cilat shmangin modifikimet apo edhe ripërpunimi i plotë i të dhënave të algoritmit ose strukturave nëse më pas rezultojnë të jenë jopraktike. Duke pasur një grup rezultatesh eksperimentale, ju mund të kryeni interpolim dhe ekstrapolim dhe të përcaktoni sjelljen e algoritmit në kushte reale.

Në përgjithësi, mund të themi se koha e ekzekutimit të një algoritmi ose metode të strukturës së të dhënave rritet me rritjen e madhësisë së të dhënave burimore, megjithëse varet edhe nga lloji i të dhënave, edhe nëse madhësia është e barabartë. Përveç kësaj, koha e ekzekutimit varet nga hardueri (procesori, frekuenca e orës, madhësia e memories, hapësira në disk, etj.) dhe softveri (mjedisi operativ, gjuha e programimit, kompajleri, interpretuesi, etj.) me të cilin kryhet implementimi, kompilimi dhe ekzekutimi i algoritmit. Për shembull, nëse të gjitha gjërat e tjera janë të barabarta, koha e ekzekutimit të një algoritmi për një sasi të caktuar të të dhënave burimore do të jetë më e vogël kur përdorni një kompjuter më të fuqishëm ose kur shkruani algoritmin si një program në kodin e makinës në krahasim me ekzekutimin e tij nga një makinë virtuale. duke e interpretuar atë në bytekode.

Përfundimi është se kryerja e analizave empirike të algoritmeve nuk është vërtet e besueshme. Disavantazhet kryesore mund të reduktohen në tre pikat e mëposhtme:

1) eksperimentet mund të kryhen vetëm duke përdorur një grup të kufizuar të të dhënave fillestare; rezultatet e marra duke përdorur një grup tjetër nuk merren parasysh.

2) për të krahasuar efektivitetin e dy algoritmeve, është e nevojshme që eksperimentet për të përcaktuar kohën e ekzekutimit të tyre të kryhen në të njëjtin harduer dhe softuer;
3) për të studiuar eksperimentalisht kohën e ekzekutimit të algoritmit, është e nevojshme të kryhet zbatimi dhe ekzekutimi i tij.

Kështu, vijmë te nevoja e përdorimit të metodave të përgjithshme të analizës për analizimin e algoritmeve, gjë që lejon:

1) merr parasysh lloje të ndryshme të të dhënave hyrëse;

2) ju lejon të vlerësoni efektivitetin relativ të çdo dy algoritmesh, pavarësisht nga hardueri dhe softueri;

3) mund të kryhet sipas përshkrimit të algoritmit pa zbatimin ose eksperimentet e tij të drejtpërdrejta.

Thelbi i analizës së përgjithshme është se funksioni f=f(n1, .., nm) i është caktuar një algoritmi të caktuar. Në formën e tij më të thjeshtë, është një funksion i një ndryshoreje n1 - sasia e të dhënave hyrëse. Megjithatë, mund të ketë variabla të tjerë - për shembull, saktësia e llogaritjes ose besueshmëria e saj. Pra, për të përcaktuar nëse një numër i caktuar është i thjeshtë në rastin e numrave të mëdhenj (gjatësia e paraqitjes binare është më shumë se 200 bit), përdoret një metodë probabilistike, besueshmëria e së cilës mund të ndryshojë. Funksionet më të njohura janë lineare, fuqi dhe logaritmike. Prandaj, duhet të merrni kohë për të kujtuar bazat e punës me ta.

Kur ndërtoni algoritme, faza e parë ndodh duke përdorur jo një gjuhë programimi, por një përshkrim në gjuhën njerëzore. Përshkrime të tilla nuk janë programe, por në të njëjtën kohë janë më të strukturuara se teksti i zakonshëm. Në veçanti, përshkrimet e "nivelit të lartë" kombinojnë gjuhën natyrore dhe strukturat e zakonshme të gjuhëve të programimit, duke i bërë ato të arritshme por informuese. Përshkrime të tilla lehtësojnë analizën e nivelit të lartë të strukturës ose algoritmit të të dhënave. Përshkrime të tilla zakonisht quhen pseudokod. Duhet të theksohet gjithashtu se pseudokodi është shpesh më i dobishëm për analizë sesa kodi në një gjuhë programimi specifike.

Ndonjëherë ka nevojë për të vërtetuar deklarata të caktuara në lidhje me një strukturë ose algoritëm të caktuar të të dhënave. Për shembull, ju duhet të demonstroni korrektësinë dhe shpejtësinë e ekzekutimit të algoritmit. Për të vërtetuar rreptësisht pohimet, është e nevojshme të përdoret gjuha matematikore, e cila do të shërbejë si provë ose justifikim i pohimeve. Ka disa mënyra të thjeshta për ta vërtetuar këtë.

Ndonjëherë pohimet shkruhen në një formë të përgjithësuar: “Bashkimi s përmban një element x me vetinë v. Për të vërtetuar këtë pohim, mjafton të japim një shembull x “i përket” s, e cila ka këtë veti. Në një formë të tillë të përgjithësuar, si rregull, shkruhen deklarata të pamundura, për shembull: "Çdo element x i grupit s ka veti P". Për të vërtetuar gabimin e këtij pohimi, mjafton thjesht të japim një shembull: x “i përket” s, e cila nuk ka vetinë P. Në këtë rast, elementi x do të veprojë si kundërshembull.

Shembull: Thuhet se çdo numër i formës 2^n - 1 është i thjeshtë nëse n është një numër i plotë më i madh se 1. Pohimi është i gabuar.

Dëshmi: Për të provuar dikë që ka gabuar, duhet të gjesh një kundërshembull.

Ky është një kundërshembull: 2^4 - 1 = 15, 15= 3 * 5.

Ekziston një mënyrë tjetër, e bazuar në vërtetimin me kontradiktë (duke përdorur mohimin). Metodat kryesore në këtë rast janë kundërvënia dhe kontradikta. Përdorimi i metodave të kontrastit është i ngjashëm me pasqyrimin: për të vërtetuar se "nëse x është e vërtetë, atëherë y është e vërtetë", ne do të pohojmë të kundërtën, "nëse y është e gabuar, atëherë x është e rreme". Nga pikëpamja logjike, këto pohime janë identike, por shprehja e dytë, e cila është bashkëvendosje e së parës, është më e përshtatshme.

Shembull: Nëse a*b është një numër tek, atëherë a është tek ose b është tek.

Dëshmi: për të vërtetuar këtë pohim, merrni parasysh kundërvënien: “Nëse a është numër çift dhe b është tek, atëherë a*b është çift. Le të a = 2*x, për një numër të plotë x. Atëherë a*b = 2*i*b, dhe për këtë arsye prodhimi a*b është çift.

Kur përdorni metoda të provës me kontradiktë, është e dobishme të përdorni logjikën.

A ose b = kërkon që a ose b të ekzekutohet, ose të dyja a dhe b në të njëjtën kohë.
. a dhe b = kërkon që a dhe b të ekzekutohen njëkohësisht.
. a xor b = kërkon ekzekutimin e a, por jo b, ose b, por jo a.

Kur përdoret metoda e kontradiktës për të vërtetuar se një pohim q është i vërtetë, së pari supozohet se q është i gabuar dhe më pas tregon se një supozim i tillë çon në një kontradiktë (për shembull, 2 * 2<>4). Pasi kemi ardhur në një kontradiktë të tillë, mund të argumentojmë se një situatë në të cilën q është e rreme nuk ekziston, dhe, për rrjedhojë, q është e vërtetë.

Në shumicën e rasteve, deklaratat rreth përdorimit të kohës ose hapësirës së ekzekutimit të programit përdorin një parametër numër të plotë n (që përfaqëson "madhësinë" e problemit). Pastaj kur formulojmë një deklaratë x(n), atëherë për një grup vlerash n pohime të tilla janë ekuivalente. Meqenëse kjo deklaratë zbatohet për një grup "të pafund" numrash, është e pamundur të sigurohet një provë e drejtpërdrejtë e plotë. Në situata të tilla, përdoren metoda induksioni. Metoda e induksionit bazohet në faktin; se për çdo n > 1. Ekziston një sekuencë e kufizuar veprimesh që fillon me diçka që dihet se është e vërtetë dhe në fund të çon në një provë që q(n) është e vërtetë. Kështu, një vërtetim me induksion fillon me pohimin se q(n) është e vërtetë për n=1,2,3, etj. deri në një konstante k. Më pas vërtetojmë se “hapi” tjetër i induksioneve q(n+1), q(n+2) është gjithashtu i vërtetë për n > k.

Kur analizoni algoritmet, llogaritni numrin e operacioneve dhe kohën e ekzekutimit të tyre, nuk duhet të merren parasysh "detajet e vogla"; faktorët dhe konstantet konstante duhet të neglizhohen. Në praktikë, përdoret koncepti i një funksioni të madh RRETH. supozojmë se ka dy funksione f(n) dhe g(n), supozohet se f(n)<= O(g(n)) , т.е. функция О ограничивает сверху значения функции f, начиная с n=n0.

Për shembull, algoritmi për numërimin e numrit të elementeve të barabartë me zero në një grup përshkruhet nga O(n), ku n është numri i elementeve.

1) 20n3+7.2n2-21.78n + 5 përshkruhet si O(n3)

2)xn-2 + a(0) përshkruhet si O(xn).

2) 3*log(n) + log(log(n)) përshkruhet si O(log(n)).

3) 2100 përshkruhet si O(1)

4) 5/n përshkruhet si O(1/n).

Ju lutemi vini re se funksioni o(n) kufizon funksionin e kostos së kohës së synuar nga lart, por gjithmonë duhet të përpiqeni të zgjidhni një funksion të tillë O(n) që të ketë saktësi maksimale.

O më i famshëm funksionon në rend rritës:

Kur përdorni analizën asimptotike, kini kujdes që kur përdorni shënimin O, shpesh neglizhoni faktorët konstantë dhe konstantet e mbledhjes. Megjithatë, nëse kjo vlerë është mjaft e madhe, megjithëse forma e funksionit O(1) është më e preferueshme se algoritmi i përshkruar nga funksioni O(n), sigurisht që është algoritmi i dytë që do të fitojë zbatim praktik.

Në varësi të llojit të funksionit f(n), dallohen klasat e mëposhtme të kompleksitetit të algoritmeve.

Klasat e kompleksitetit të algoritmit në varësi të funksionit të kompleksitetit
Shiko f(n) Karakteristikat e klasës së algoritmeve
Shumica e udhëzimeve për shumicën e funksioneve ekzekutohen një ose më shumë herë. Nëse të gjitha instruksionet në një program e kanë këtë veti, atëherë koha e ekzekutimit të programit është konstante.
log N Kur koha e ekzekutimit të një programi është logaritmike, programi bëhet më i ngadalshëm me rritjen e N. Kohë të tilla ekzekutimi zakonisht shoqërohen me programe që reduktojnë një problem të madh në një grup nënproblemesh më të vogla, duke reduktuar madhësinë e problemit me një faktor konstant në çdo hap. Ndryshimi i bazës nuk ndikon shumë në ndryshimin e vlerës së logaritmit: n
N Kur koha e ekzekutimit të një programi është lineare, zakonisht do të thotë që çdo element hyrës i nënshtrohet pak përpunimit.
N log N Koha e ekzekutimit proporcionale me N log N ndodh kur një algoritëm zgjidh një problem duke e zbërthyer atë në nënprobleme më të vogla, duke i zgjidhur ato në mënyrë të pavarur dhe më pas duke kombinuar zgjidhjet.
N 2 Kur koha e funksionimit të një algoritmi është kuadratike, është e dobishme për përdorim praktik në zgjidhjen e problemeve relativisht të vogla. Koha kuadratike e ekzekutimit zakonisht shfaqet në algoritme që përpunojnë të gjitha çiftet e artikujve të të dhënave (ndoshta në një lak me fole të dyfishtë).
N 3 Një algoritëm i ngjashëm që përpunon treshe elementësh të dhënash (ndoshta në një lak me fole të trefishtë) ka një kohë ekzekutimi kub dhe është praktikisht i zbatueshëm vetëm për probleme të vogla.
2 N Vetëm disa algoritme me kohë ekzekutimi eksponenciale kanë aplikime praktike, megjithëse algoritme të tilla lindin natyrshëm kur përpiqen të zgjidhin një problem drejtpërdrejt, siç është forca brutale.

Bazuar në metodat matematikore për studimin e funksioneve asimptotike të kompleksitetit në pafundësi, janë identifikuar pesë klasa algoritmesh.

1. Një klasë algoritmesh të shpejta me kohë ekzekutimi konstante, funksioni i kompleksitetit të tyre është O(1). Gjendjen e ndërmjetme e zënë algoritme me kompleksitet O(log N), të cilët gjithashtu klasifikohen në këtë klasë.

2. Një klasë algoritmesh racionale ose polinomiale, funksioni i kompleksitetit i të cilave përcaktohet në mënyrë polinomike nga parametrat hyrës. Për shembull, O(N), O(N 2, O(N 3).

3. Një klasë algoritmesh nëneksponencialë me një shkallë kompleksiteti O(N log N).

4.Klasa e algoritmeve eksponenciale me shkallë kompleksiteti O(2 N).

5.Klasa e algoritmeve mbieksponenciale. Ekzistojnë algoritme me kompleksitet faktorial, por në përgjithësi nuk kanë zbatim praktik.

Gjendja e kujtesës gjatë ekzekutimit të algoritmit përcaktohet nga vlerat që kërkojnë ndarje të zonave të caktuara. Në këtë rast, gjatë zgjidhjes së problemit, mund të përdoret një numër shtesë i qelizave. Me sasinë e memories që kërkon algoritmi A për hyrjen D, nënkuptojmë numrin maksimal të qelizave të memories të përdorura gjatë ekzekutimit të algoritmit. Kompleksiteti i kapacitetit të një algoritmi përcaktohet si vlerësim asimptotik i funksionit të kapacitetit të memories në rastin më të keq të algoritmit.

Kështu, kompleksiteti i burimeve të një algoritmi në rastet më të këqija, mesatare dhe më të mira përkufizohet si një çift i renditur i klasave të funksioneve të kompleksitetit të kohës dhe kapacitetit, të specifikuara nga shënimi asimptotik dhe që korrespondojnë me rastin në shqyrtim.

Konstruktet kryesore algoritmike në programimin procedural janë ndjekja, degëzimi dhe ciklimi. Për të marrë funksionet e kompleksitetit për rastet më të mira, mesatare dhe më të këqija me një dimension fiks të hyrjes, është e nevojshme të merren parasysh dallimet në vlerësimin e strukturave kryesore algoritmike.

  • Kompleksiteti i konstruksionit “Në vijim” është shuma e kompleksitetit të blloqeve që ndjekin njëri-tjetrin: f=f 1 +f 2 +...+f n .
  • Kompleksiteti i dizajnit të "Degëzimit" përcaktohet përmes probabilitetit të kalimit në secilin prej udhëzimeve, të përcaktuar nga kushti. Në të njëjtën kohë, kontrolli i gjendjes ka gjithashtu një kompleksitet të caktuar. Për të llogaritur kompleksitetin e rastit më të keq, mund të zgjidhet blloku i degëzimit që ka më shumë kompleksitet; për rastin më të mirë, mund të zgjidhet blloku me më pak kompleksitet. f nëse =f 1 +f atëherë p pastaj +f tjetër (1-p atëherë)
  • Kompleksiteti i konstruksionit "Loop" përcaktohet duke llogaritur kushtin e përfundimit të lakut (zakonisht i rendit 0(1)) dhe produktin e numrit të përsëritjeve të përfunduara të lakut me numrin më të madh të mundshëm të operacioneve të trupit të lakut. Nëse përdoren sythe të mbivendosur, kompleksiteti i tyre shumëfishohet.

Kështu, për të vlerësuar kompleksitetin e një algoritmi, mund të formulohet një metodë e përgjithshme për marrjen e funksionit të kompleksitetit.

  1. Zbërthimi i algoritmit përfshin identifikimin e strukturave bazë në algoritëm dhe vlerësimin e kompleksitetit. Në këtë rast, merret parasysh vijimi i strukturave kryesore algoritmike.
  2. Analiza rresht pas rreshti e intensitetit të punës për operacionet bazë gjuhësore nënkupton ose një analizë kumulative (duke marrë parasysh të gjitha operacionet) ose analizë operacionale (duke marrë parasysh kompleksitetin e secilit operacion).
  3. Përbërja e kundërt e funksionit të kompleksitetit bazuar në metodologjinë e analizës së strukturave algoritmike bazë për rastet më të mira, mesatare dhe më të këqija.

Një tipar i vlerësimit të efikasitetit të burimeve të algoritmeve rekurzive është nevoja për të marrë parasysh kostot shtesë të memories dhe mekanizmin për organizimin e rekursionit. Prandaj, kompleksiteti i zbatimeve rekursive të algoritmeve lidhet me numrin e operacioneve të kryera gjatë një thirrjeje rekursive, si dhe me numrin e thirrjeve të tilla. Gjithashtu merren parasysh kostot e kthimit të vlerave dhe transferimit të kontrollit në pikën e thirrjes. Kur vlerësoni kujtesën e kërkuar të stivës, duhet të keni parasysh që në një moment të caktuar kohor, nuk është një fragment rekursioni që ruhet në pirg, por një zinxhir thirrjesh rekursive. Prandaj, madhësia e stivës përcaktohet nga numri maksimal i mundshëm i thirrjeve të njëkohshme rekursive të marra.


Biblioteka e programuesit


"Nëse korrigjimi është një proces i heqjes së gabimeve, atëherë programimi duhet të jetë një proces i prezantimit të tyre"

E. Dijkstra

1.2. Pse të studiohen algoritmet? Efikasiteti i algoritmeve

Së pari, algoritmet janë komponentë jetik për zgjidhjen e çdo problemi në fusha të ndryshme të shkencës kompjuterike. Algoritmet luajnë një rol kyç në fazën aktuale të zhvillimit të teknologjisë. Këtu mund të kujtoni detyra të tilla të zakonshme si:

  • zgjidhja e ekuacioneve matematikore me kompleksitet të ndryshëm, gjetja e prodhimit të matricave, matricave të anasjellta;
  • gjetja e mënyrave optimale për transportin e mallrave dhe njerëzve;
  • gjetja e opsioneve optimale për shpërndarjen e burimeve ndërmjet nyjeve të ndryshme (prodhuesit, makineritë, punëtorët, përpunuesit, etj.);
  • gjetja e sekuencave në gjenom që përputhen;
  • kërkimi i informacionit në internetin global;
  • marrjen e vendimeve financiare në tregtinë elektronike;
  • përpunimi dhe analizimi i informacionit audio dhe video.

Kjo listë vazhdon dhe, në fakt, është pothuajse e pamundur të gjesh një fushë të shkencës kompjuterike dhe shkencës së informacionit ku nuk përdoren algoritme të caktuara.

Së dyti, algoritmet me cilësi të lartë dhe efikase mund të jenë katalizatorë për përparime në industri që janë, në shikim të parë, larg shkencës kompjuterike (mekanika kuantike, ekonomia dhe financa, teoria e evolucionit).

Dhe së treti, studimi i algoritmeve është gjithashtu një proces tepër interesant që zhvillon aftësitë tona matematikore dhe të menduarit logjik.

1.3. Efikasiteti i algoritmeve

Le të supozojmë se shpejtësia e një kompjuteri dhe sasia e memories së tij mund të rritet pafundësisht. A do të kishte nevojë për të studiuar algoritme atëherë? Po, por vetëm për të demonstruar se metoda e shkëputjes ka një kohë të kufizuar ekzekutimi dhe se jep përgjigjen e saktë. Nëse kompjuterët do të ishin pafundësisht të shpejtë, një metodë arbitrare e saktë për zgjidhjen e një problemi do të funksiononte. Natyrisht, atëherë më së shpeshti do të zgjidhej metoda që është më e lehtë për t'u zbatuar.

Sot kompjuterët janë shumë të fuqishëm, por shpejtësia e tyre nuk është e pafundme dhe as memoria e tyre. Kështu, në llogaritje, është një burim po aq i kufizuar sa sasia e memories që kërkohet. Këto burime duhet të përdoren me mençuri, gjë që lehtësohet nga përdorimi i algoritmeve që janë efikase për sa i përket përdorimit të kohës dhe burimeve të kujtesës.

Algoritmet e krijuara për të zgjidhur të njëjtin problem shpesh mund të ndryshojnë shumë në efikasitet. Këto dallime mund të jenë shumë më të dukshme se ato të shkaktuara nga harduer dhe softuer të ndryshëm.

Siç u përmend më lart, ky seksion do të fokusohet në detyrën e renditjes. Algoritmi i parë që do të merret në konsideratë, renditja e përfshirjes, kërkon kohë për të funksionuar, sasia e së cilës vlerësohet si c 1 n 2, ku n është madhësia e të dhënave hyrëse (Numri i elementeve në sekuencën që do të renditet), c 1 është pak konstante. Kjo shprehje tregon se si koha e ekzekutimit të algoritmit varet nga vëllimi i të dhënave burimore. Në rastin e renditjes së përfshirjes, kjo varësi është kuadratike. Algoritmi i dytë, merge sort, kërkon kohë, sasia e së cilës vlerësohet si 2 nLog 2 n. Në mënyrë tipike, konstanta e renditjes së përfshirjes është më e vogël se konstanta e renditjes së bashkimit, domethënë, c12 rritet më shpejt ndërsa n rritet se funksioni Ilog 2 n. Dhe për një vlerë n = n 0 do të arrihet një moment kur ndikimi i ndryshimit në konstante pushon së materiesi dhe në të ardhmen funksioni c 2 nLog 2 n do të jetë më i vogël se c 1 n 2 për çdo n > n 0.

Për ta demonstruar këtë, merrni parasysh dy kompjuterë - A dhe B. Kompjuteri A është më i shpejtë dhe ekzekuton algoritmin e renditjes, dhe kompjuteri B është më i ngadalshëm dhe ekzekuton algoritmin e renditjes së bashkimit. Të dy kompjuterët duhet të renditin një grup të përbërë nga një milion numra. Le të themi se kompjuteri A kryen një miliard operacione në sekondë, dhe kompjuteri B vetëm dhjetë milionë, ka A që funksionon 100 herë më shpejt se B. Për ta bërë dallimin më të dukshëm, le të themi se kodi për metodën e aktivizimit është shkruar nga më i miri programues në botë duke përdorur udhëzime për procesorin, dhe për të renditur n numra me këtë algoritëm ju duhet të kryeni 2n 2 operacione (d.m.th., C 1 = 2). Renditja e bashkimit në kompjuterin B u shkrua nga një programues fillestar duke përdorur një gjuhë të nivelit të lartë dhe kodi që rezulton kërkon 50nlog 2 n operacione (d.m.th., c 2 = 50). Kështu, për të renditur një milion numra, kompjuteri A do të duhej

dhe në kompjuterin B -

Prandaj, përdorimi i kodit, koha e ekzekutimit të të cilit rritet më ngadalë, edhe me një kompjuter të keq dhe një përpilues të keq, kërkon një renditje të madhësisë më pak kohë CPU! Për renditjen e 10,000,000 shifrave, avantazhi i renditjes së bashkimit bëhet edhe më i dukshëm: ndërsa renditja e përfshirjes kërkon rreth 2,3 ditë për një detyrë të tillë, atëherë për renditjen e bashkimit zgjat më pak se 20 minuta. Rregulli i përgjithshëm është që sa më i madh të jetë numri i elementeve për të renditur, aq më i madh është avantazhi i renditjes së bashkimit. Shembulli i mësipërm tregon se algoritmet, si programet kompjuterike, janë teknologjisë. Performanca e përgjithshme e sistemit varet po aq nga efikasiteti i algoritmit sa varet nga fuqia e harduerit.

Pra, konsiderohen opsione të ndryshme për makinat llogaritëse, nga makinat më të thjeshta Turing deri te një mjedis kompjuterik homogjen. Të gjitha ato mund të përdoren për të zgjidhur ato probleme për të cilat ekziston një algoritëm. Në bazë të këtyre modeleve ndërtohen modele më të specializuara të llogaritjes, përkatësisht: programet aritmetike jo të degëzuara, llogaritja bitwise, llogaritja binar vektoriale dhe pemët e vendimit.

Algoritmet kanë karakteristikat e mëposhtme:

a) kompleksiteti;

b) intensiteti i punës;

c) besueshmëria etj.

Ka shumë kritere për vlerësimin e kompleksitetit të algoritmeve. Më shpesh do të jemi të interesuar rendi i rritjes koha dhe kapaciteti i memories që kërkohet për të zgjidhur problemin me rritjen e sasisë së të dhënave hyrëse. Le të lidhim me secilën detyrë specifike një numër të caktuar të quajtur të tij madhësia. Për shembull, madhësia e një problemi të shumëzimit të matricës mund të jetë madhësia më e madhe e matricave të faktorëve; madhësia e një problemi në një grafik mund të jetë numri i skajeve të një grafi të caktuar, etj.

Koha e marrë nga një algoritëm në funksion të madhësisë së problemit quhet kompleksiteti kohor këtë algoritëm. Sjellja e këtij kompleksiteti në kufi me rritjen e madhësisë së problemit quhet kompleksiteti asimptotik kohor. Përcaktuar në mënyrë të ngjashme kompleksiteti kapacitiv Dhe kompleksiteti i kapacitetit asimptotik.

Një motivim i rëndësishëm për shqyrtimin e modeleve formale llogaritëse është dëshira për të zbuluar kompleksitetin llogaritës të problemeve të ndryshme në mënyrë që të përftohen kufijtë më të ulët për kohën e llogaritjes. Për të treguar se nuk ka asnjë algoritëm që mund të kryejë një detyrë të caktuar në më pak se një kohë të caktuar, kërkon një përkufizim të saktë dhe ndonjëherë shumë të specializuar të asaj që është një algoritëm. Një shembull i një përkufizimi të tillë janë makinat Turing.

4.1.1. Makinat e kornizës dhe kornizës*

Konsideroni dy makina:

1. Makinat me akses të rastësishëm në memorie (makina me adresë të barabartë me akses - RAM) modelojnë një kompjuter me një grumbullues, në të cilin udhëzimet e programit nuk mund të ndryshojnë vetë.

2. Modeli i programit të ruajtur është një makinë me akses të rastësishëm në memorie dhe aftësi për të modifikuar instruksionet (RAM*).

Fig.2.9 Struktura e makinave RAM (RAM*)

Për RAM-in, programi nuk është i shkruar në memorie, kështu që programi nuk modifikohet vetë. Një program është një sekuencë komandash të etiketuara. Ka instruksione aritmetike, instruksione I/O, instruksione adresimi indirekte dhe instruksione dege. Të gjitha llogaritjet kryhen në regjistrin r 0 (mbledhës), i cili, si çdo regjistër tjetër memorie, mund të ruajë një numër të plotë arbitrar. Çdo komandë përbëhet nga dy pjesë - një kod operimi dhe një adresë. Komandat PAM janë një nëngrup i komandave të gjuhës Asamble; ky nëngrup mund të zgjerohet sipas dëshirës, ​​por rendi i kompleksitetit të detyrave nuk do të ndryshojë.

Operandi mund të jetë një nga llojet e mëposhtme:

1. =i nënkupton vetë numrin e plotë i dhe quhet literal;

2. i- regjistroni përmbajtjen i (i duhet të jetë jo negative);

3. *i do të thotë adresim indirekt, domethënë vlera e operandit është përmbajtja e regjistrit j, Ku j- një numër i plotë që është në regjistër I; Nëse j<0, makina ndalon.

Ju mund të përcaktoni vlerën e programit R duke përdorur dy objekte: një hartë c nga një grup numrash të plotë jo negativë në një grup numrash të plotë dhe një "numërues komandash", i cili përcakton komandën tjetër që do të ekzekutohet. Funksioni c është shfaqja e memories, domethënë c (i) - numër i plotë i përfshirë në numrin e regjistrit I (përmbajtjen regjistrohen I).

Ne fillim с(i)=0 per te gjithe i0 , numëruesi i programit vendoset në instruksionin e parë në P, dhe shiriti i daljes është bosh. Pas ekzekutimit k skuadra e th nga R numëruesi kalon automatikisht në (k+1)-të (d.m.th., në komandën tjetër), nëse k-Ekipi im nuk ishte një ekip si JUMP, HALT, JGTZ dhe të ngjashme.

Programi RAM* ndodhet në regjistrat e memories. Çdo komandë RAM* zë dy regjistra memorie të njëpasnjëshme: regjistri i parë përmban kodin e funksionimit, i dyti - adresën. Seti i udhëzimeve për RAM* përkon me grupin përkatës për RAM-in në gjithçka, përveç adresimit indirekt, i cili përjashtohet: RAM* mund të simulojë adresimin indirekt duke ndryshuar instruksionet gjatë ekzekutimit të programit.

Përveç kontrollit që algoritmi i zbatuar nga studenti si zgjidhje është i aftë të japë përgjigjen e saktë të problemit duke pasur parasysh të dhëna fillestare të caktuara, gjatë kontrollit të zgjidhjes merret parasysh edhe koha e ekzekutimit të programit. Kjo nuk do të thotë se është thelbësore të shkruhen algoritme optimale për të gjitha detyrat pa përjashtim (të cilat shpesh mund të marrin shumë kohë për zbatimin dhe korrigjimin e tyre kompetent). Kjo thjesht do të thotë se në disa detyra individuale parametri i kohës mund të luajë një rol shumë të rëndësishëm. Mund të ndodhë që në ndonjë raund të Olimpiadës të mos ketë asnjë problem të vetëm në të cilin është i nevojshëm optimizmi. Megjithatë, mund të ndodhë edhe e kundërta.

Kështu, si nxënësit ashtu edhe mësuesit duhet të jenë në gjendje të krahasojnë algoritme të ndryshme bazuar në efektivitetin e tyre. Nxënësit e shkollës - në mënyrë që të zgjedhin mënyrën më të përshtatshme për të zgjidhur një problem në momentin e duhur, mësuesit - të zgjedhin me kompetencë detyrat dhe të kuptojnë se çfarë zgjidhje kishte në mendje autori i një problemi të veçantë kur vendosi saktësisht kufij kohorë të tillë.

Për të vlerësuar efektivitetin e algoritmit, përdoret një funksion kompleksiteti, i shënuar O (lexo "rreth i madh"). Në fakt, ka vlerësime të tjera, por në fazën kur një student sapo fillon të njihet me algoritme të ndryshme, ato nuk janë vërtet të nevojshme. Funksioni i kompleksitetit pasqyron modelin në të cilin koha e ekzekutimit të programit do të rritet në varësi të të dhënave burimore ose sasisë së tyre.

Një shembull i një algoritmi, koha e ekzekutimit të të cilit varet nga të dhënat fillestare është algoritmi për gjetjen e të gjithë pjesëtuesve natyrorë të numrit N. Natyrisht, sa më i madh të jetë numri, aq më shumë hapa të ciklit do të jetë i nevojshëm për të kryer. Një shembull i një algoritmi, koha e ekzekutimit të të cilit varet nga sasia e të dhënave hyrëse do të ishte kërkimi i numrit më të madh në një grup. Sa më i gjatë të jetë grupi, aq më shumë operacione krahasimi duhen bërë për të përcaktuar se cili numër është më i madhi.


Funksionet kryesore janë:

l O(1) - një funksion i tillë kompleksiteti tregon se koha e funksionimit të programit është konstante për çdo të dhënë fillestare;

l O(N) - numri i operacioneve rritet në proporcion me N (këtu N mund të jetë ose një parametër i detyrës ose numri i elementeve në grup).

l O (log N) - numri i operacioneve rritet në proporcion me logaritmin e N (ky është pikërisht kompleksiteti i, për shembull, metodës së gjysmave kur kërkoni një element në një grup të renditur). Ndërsa N rritet me një rend të madhësisë, numri i veprimeve ndryshon me një. Baza e logaritmit zakonisht nuk specifikohet; ne jemi të interesuar në natyrën e rritjes (të shpejtë / të ngadaltë), dhe jo vlerën e saktë të kohës.

l O(N2) - numri i veprimeve rritet në raport me katrorin e N. Në përgjithësi, mund të jetë O(Nk) në varësi të kompleksitetit të problemit.

l O(N!) - numri i operacioneve rritet në raport me faktorin N.

Këtu ka një numër hollësish për faktin se jo të gjitha operacionet kryhen në të njëjtën kohë, kështu që kur vlerësohet kompleksiteti i kohës, përdoren ato operacione që kërkojnë më shumë kohë.

Më shpesh, kur përshkruhen algoritmet, një vlerësim i kohës së funksionimit të tyre jepet në formën e tij të pastër, domethënë pa marrë parasysh operacionet hyrëse/dalëse.

Shembull: le të vlerësojmë kompleksitetin e një programi që hyn në një grup nga tastiera dhe gjen elementin më të madh në të.

Le të shtojmë numrin e veprimeve N+(N-1)+1=2N. Kjo do të thotë, ekziston një konstante e tillë që për çdo N numri i operacioneve nuk e kalon CN. Prandaj, kompleksiteti i algoritmit është O(N).

Shembull: le të vlerësojmë kompleksitetin e një programi që hyn në një grup nga tastiera dhe gjen në të një element me një veti të caktuar (për shembull, të barabartë me një vlerë të caktuar).

Algoritmi përbëhet nga hapat e mëposhtëm:

Hyrja në një grup (operacionet hyrëse N) duke kërkuar për një element me një veti të caktuar (sa me fat: elementi mund të gjendet ose më afër fillimit të grupit ose në fund; nëse elementi nuk ekziston, atëherë duhet të bëni të gjitha krahasimet N për t'u siguruar për këtë) duke nxjerrë rezultatin .

Në rastin më të mirë, ky algoritëm do të kërkojë operacione N+2 (hyrje e të gjithë grupit, një krahasim i vetëm, dalje), në rastin më të keq (kur nuk ka një element të tillë - operacione 2N+1). Nëse N është një numër i madh, për shembull rreth 106, atëherë uniteti mund të neglizhohet. Prandaj, kompleksiteti i algoritmit është O(N).

Shembull: le të përcaktojmë funksionin e kompleksitetit të një algoritmi të enkriptimit të fjalëve me gjatësi L duke përdorur metodën e zëvendësimit. Le të ketë një tabelë në të cilën për çdo karakter të alfabetit shkruhet karakteri me të cilin duhet të zëvendësohet. Le të shënojmë numrin e shkronjave të alfabetit S.

Algoritmi përbëhet nga hapat e mëposhtëm:

Futja e një cikli fjalësh (një operacioni) përmes të gjithë karaktereve

1. për çdo karakter, gjeni zëvendësimin e tij në tabelë (nëse tabela nuk është e renditur dhe nuk ka ndonjë veçori që lehtëson kërkimin, atëherë në rastin më të keq ka operacione S për një karakter nëse elementi i kërkuar është në fund)


2. dalje e simbolit të gjetur

Fundi i ciklit

Numri i përgjithshëm i operacioneve është 1+(S+1)*L. Në rastin kur njësitë S dhe L mjaft të mëdha mund të neglizhohen, rezulton se funksioni i kompleksitetit të këtij algoritmi është O(S*L).

Shembull: le të përcaktojmë funksionin e kompleksitetit të algoritmit për konvertimin e numrit natyror N në sistemin e numrave binar (pa operacione të hyrjes dhe daljes së të dhënave).

Algoritmi përbëhet nga hapat e mëposhtëm:

Lloko derisa rezultati i pjesëtimit të një numri me 2 të jetë 0

1. pjesëtojeni numrin me 2 dhe mbani mend pjesën e mbetur

2. merr rezultatin e pjesëtimit si numër të ri

Fundi i ciklit

Numri i përgjithshëm i operacioneve nuk kalon 1+log2N. Prandaj, ky algoritëm ka një kompleksitet prej O(log N).

Nëse një program përbëhet nga disa pjesë me funksione të ndryshme kompleksiteti, atëherë O Funksioni më i madh i kompleksitetit do të "thithë" ato më të voglat. Për shembull, nëse bëni hyrjen e grupit O(N), renditjen O(N2) dhe daljen O(N) të grupit të renditur, atëherë mund të thoni se i gjithë programi ka kompleksitet O(N2).

Zbatimi praktik i njohurive për funksionet e kompleksitetit të algoritmeve është i dyfishtë. Së pari, për një detyrë të caktuar, mund të zgjidhet një algoritëm më optimal nëse ka të dhëna përkatëse për të në literaturë. Së dyti, duke ditur kohën e ekzekutimit të zgjidhjes së tij në një grup të dhënash fillestare, një student mund të vlerësojë afërsisht kohën e ekzekutimit të të njëjtit program në të dhëna që korrespondojnë me kufizimet maksimale për një problem të caktuar.

Pyetje

Këto detyra përdoren për vetë-testim në materialin e paraqitur dhe nuk janë të detyrueshme.

1. Përcaktoni funksionin e kompleksitetit të algoritmit për zgjidhjen e një ekuacioni kuadratik.

2. Përcaktoni funksionin e kompleksitetit të algoritmit për vizatimin e një shumëkëndëshi të rregullt bazuar në një numër të caktuar brinjësh.

3. Përcaktoni funksionin e kompleksitetit të algoritmit për futjen e një elementi në një grup në një pozicion të caktuar (me një zhvendosje paraprake të të gjithë elementëve me numra më të mëdhenj ose të barabartë me pozicionin e dhënë një nga një në të djathtë).

4. Përcaktoni funksionin e kompleksitetit të algoritmit për mbledhjen e dy numrave natyrorë në një kolonë (le të jetë A numri i shifrave të numrit të parë, B numri i shifrave të të dytit).

5. Përcaktoni funksionin e kompleksitetit të algoritmit për shumëzimin e dy numrave natyrorë në një kolonë.