Böyük O, necə hesablayırsan?

ÇS dərəcəsi olan insanların əksəriyyəti Big O deməkdir . Bu bizə alqoritmanın nə dərəcədə təsirli olduğunu ölçməyə kömək edir və əgər bu problemin hansı kateqoriyada həll olunmağa çalışdığını bilsəniz, bu kiçik əlavə performansın hələ sıxılmasını tapa bilərik. 1

Ancaq alqoritmlərinizin mürəkkəbliyini necə hesabladığınızı və ya təxminən necə qiymətləndirirsiniz?

1 , amma onlar dedikləri kimi, həddi aşmayın , erkən optimallaşdırma bütün pisliyin köküdür və düzgün bir səbəb olmadan optimallaşdırma da bu adı layiqdir.

722
06 авг. set sven 06 aug. 2008-08-06 13:18 '08 saat 13:18 'da 2008-08-06 13:18
@ 23 cavab

Mən Data Strukturları və Alqoritmlər üzrə kurslar üçün yerli universitetimdə tədris köməkçisi oldum. Burada sadə mənada izah etmək üçün əlimdən gələni edəcəyəm, amma bu mövzuda tələbələrim bir neçə ay anlamaq üçün məcbur edəcəkdir. Daha ətraflı məlumat üçün Java-da 2-ci fəsildə məlumat strukturları və alqoritmlərinə baxın .


BigOh almaq üçün istifadə edilə bilən heç bir mexaniki prosedur yoxdur .

Bu xüsusiyyətin hesablama mürəkkəbliyini əldə etmək üçün "cookbook" kimi

 Number_Of_Steps = f(N) 

Beləliklə, biz f(N) , hesablama addımlarının sayını hesablamaq üçün bir funksiyaya sahibik. Bir funksiyaya daxil edilməsi, işləniləcək strukturun ölçüsüdür. Bu deməkdir ki, bu funksiya deyilir:

 Number_Of_Steps = f(data.length) 

N parametri data.length dəyərini data.length . İndi f() funksiyasının faktiki tərifinə ehtiyacımız var. Bu, hər bir maraqlı xəttin 1-dən 4-ə qədər olan nömrənin kodundan edilir.

BigOh hesablamaq üçün bir çox yol var. Bundan sonra, giriş məlumatlarının ölçüsündən asılı olmayan hər cümlə cədvəli ədədi addımlar atır C

Funksiyaya fərdi sayıda addımlar əlavə edəcəyik və nə bir lokal dəyişdirici nə də qaytarma operatoru data arrayının ölçüsündən asılı deyildir.

Bu, 1-ci və 4-cü xəttlərin hər birində addımların sayını C qəbul edir və funksiya təxminən belədir:

 f(N) = C + ??? + C 

Növbəti hissə isə bəyanatın dəyərini müəyyən etməkdir. Hesablama addımlarının sayını hesab edirik ki, operator üçün cədvəl N dəfə yerinə yetirilir. C , N dəfə əlavə olaraq eyni:

 f(N) = C + (C + C + ... + C) + C = C + N * C + C 

Bədən for neçə dəfə yerinə yetirilməsini hesablamaq üçün heç bir mexaniki qayda yoxdur, kodun nə baxdığını nəzərə alaraq hesablamalısınız. Hesablamaları asanlaşdırmaq for operator for dəyişən başlatma, şərt və əlavə hissələrini görmürük.

Faktiki BigOh-u almaq üçün biz funksiyanın asimptotik analizinə ehtiyac duyuruq. Bu təxminən aşağıdakı kimi edilir:

  • Bütün sabitləri C çıxarın C
  • f() standard form poliomiyanı almaq.
  • Polinom üzvlərini ayırın və böyümə sürətinə görə sıralayın.
  • N yaxınlaşdıqda infinity .

f() iki üzvümüz var:

 f(N) = 2 * C * N ^ 0 + 1 * C * N ^ 1 

Bütün sabitləri C və lazımsız hissələri çıxarırıq:

 f(N) = 1 + N ^ 1 

Son dövrdə f() sonsuzluğa ( məhdudiyyətlər düşünür f() yaxınlaşanda, bu arqument BigOh olur və funksiya sum() BigOh dəyərinə malikdir:

 O(N) 

Bəzi çətin tapşırıqları həll etmək üçün bir neçə fənd var: mümkün olduğunda toplamalardan istifadə edin.

Məsələn, bu kod asanlıqla yekun istifadə edərək həll oluna bilər:

 for (i = 0; i < 2*n; i += 2) { // 1 for (j=n; j > i; j--) { // 2 foo(); // 3 } } 

Sual vermək lazım olan ilk şey foo() əmrinin əmri idi. Normal O(1) olmasına baxmayaraq, bu barədə professorlarınıza soruşmaq lazımdır. O(1) N ölçüsündən asılı olmayaraq sabit C (demək olar ki, əsasən O(1) deməkdir.

Cəzanın 1 nömrəli cəzası for bəyanat mürəkkəbdir. Endeks 2 * N N'de bittiği sürece, artırma iki tarafından gerçekleştirilir. Bu, ilk növbədə, yalnız N addımları əldə etmək deməkdir və hesabı ikiyə bölmək lazımdır.

 f(N) = Summation(i from 1 to 2 * N / 2)( ... ) = = Summation(i from 1 to N)( ... ) 

İki nömrəli cümlə daha da mürəkkəbdir, çünki bu mənanın dəyərindən asılıdır. Bir göz atın: İndeks mənə lazım olan dəyərləri alır: 0, 2, 4, 6, 8, ..., 2 * N, ikincisi isə yerinə yetirilir: N dəfə, N - 2 saniyə, N - 4 ... Üçüncü addım əvvəl N / 2, ikincisi isə heç vaxt icra edilməyəcəkdir.

Formula görə, bu, deməkdir:

 f(N) = Summation(i from 1 to N)( Summation(j = ???)( ) ) 

Yenidən sayda sayını sayın . Və müəyyənləşdirməklə, hər bir toplama həmişə birdən və birdən başlayaraq birdən çox və ya bərabər bir sıra ilə başlamalıdır.

 f(N) = Summation(i from 1 to N)( Summation(j = 1 to (N - (i - 1) * 2)( C ) ) 

( foo() O(1)C addımları atdığını varsayalım.

Bir problemimiz var: N / 2 + 1 dəyərini N / 2 + 1 , daxili yekun mənfi sayla bitir! Bu mümkün deyil və yanlışdır. Nəticəni yarı yarıya bölmək lazımdır, bir dönüş nöqtəsi olaraq, N / 2 + 1 sürən anı.

 f(N) = Summation(i from 1 to N / 2)( Summation(j = 1 to (N - (i - 1) * 2)) * ( C ) ) + Summation(i from 1 to N / 2) * ( C ) 

Əsas nöqtə i > N / 2 olduğundan daxili for icra edilməyəcək və biz C-nin cismində yerinə yetirilməsinin daimi mürəkkəbliyini götürürük.

İndi məbləğlər bəzi müəyyən qaydalara əsasən sadələşdirilə bilər:

  • Summation (w 1-dən N) (C) = N * C
  • (A) (+/-) Summation (w 1 dən N) (A (+/-) B) = Summation (w 1 dən N)
  • Cədvəl 1-dən N-ə qədər (w-dən C) = C * Summation (w 1-dən N) ( w )
  • Summation (w 1 dən N) (w) = (N * (N + 1)) / 2

Bəzi cəbrin tətbiqi:

 f(N) = Summation(i from 1 to N / 2)( (N - (i - 1) * 2) * ( C ) ) + (N / 2)( C ) f(N) = C * Summation(i from 1 to N / 2)( (N - (i - 1) * 2)) + (N / 2)( C ) f(N) = C * (Summation(i from 1 to N / 2)( N ) - Summation(i from 1 to N / 2)( (i - 1) * 2)) + (N / 2)( C ) f(N) = C * (( N ^ 2 / 2 ) - 2 * Summation(i from 1 to N / 2)( i - 1 )) + (N / 2)( C ) => Summation(i from 1 to N / 2)( i - 1 ) = Summation(i from 1 to N / 2 - 1)( i ) f(N) = C * (( N ^ 2 / 2 ) - 2 * Summation(i from 1 to N / 2 - 1)( i )) + (N / 2)( C ) f(N) = C * (( N ^ 2 / 2 ) - 2 * ( (N / 2 - 1) * (N / 2 - 1 + 1) / 2) ) + (N / 2)( C ) => (N / 2 - 1) * (N / 2 - 1 + 1) / 2 = (N / 2 - 1) * (N / 2) / 2 = ((N ^ 2 / 4) - (N / 2)) / 2 = (N ^ 2 / 8) - (N / 4) f(N) = C * (( N ^ 2 / 2 ) - 2 * ( (N ^ 2 / 8) - (N / 4) )) + (N / 2)( C ) f(N) = C * (( N ^ 2 / 2 ) - ( (N ^ 2 / 4) - (N / 2) )) + (N / 2)( C ) f(N) = C * (( N ^ 2 / 2 ) - (N ^ 2 / 4) + (N / 2)) + (N / 2)( C ) f(N) = C * ( N ^ 2 / 4 ) + C * (N / 2) + C * (N / 2) f(N) = C * ( N ^ 2 / 4 ) + 2 * C * (N / 2) f(N) = C * ( N ^ 2 / 4 ) + C * N f(N) = C * 1/4 * N ^ 2 + C * N 

Və BigOh:

 O(N²) 
 O(N²) 
1340
31 янв. Cavab verilir vz0 Yanvar 31 2011-01-31 18:33 '11 saat 18:33 'də 2011-01-31 18:33

Böyük O, alqoritmin zamanın mürəkkəbliyi üçün üst sərhəd verir. Əksər emal kümeleri (siyahıları) ilə birlikdə istifadə olunur, lakin başqa yerdə istifadə edilə bilər.

C kodunda necə istifadə edilən bəzi nümunələr.

Diyelim ki n elementlərinin bir sıra var

 int array[n]; 

Serialın ilk elementinə daxil olmaq istəyiriksə, O (1) olardı, çünki dizinin nə qədər böyük olmasından asılı olmayaraq, ilk elementi əldə etmək üçün həmişə eyni sabit vaxt tələb olunur.

 x = array[0]; 

Siyahıda bir sıra tapmaq istəsək:

border=0
 for(int i = 0; i < n; i++){ if(array[i] == numToFind){ return i; } } 

Bu, O (n) olacaqdır, çünki ən yaxşı şəkildə nömrəmizi tapmaq üçün bütün siyahıdan baxmalıyıq. Big-O hələ də O (n) dir, baxmayaraq ki, Big-O, alqoritmi yuxarı sərhədini (aşağı bağ üçün omeqa və sıx bir bağlanma üçün teta) təsvir edir, çünki ilk cəhdimizlə və dövrü bir dəfə atlaya bilərik.

Daxili döngələr olduğumuzda:

 for(int i = 0; i < n; i++){ for(int j = i; j < n; j++){ array[j] += 2; } } 

Bu O (n ^ 2), çünki xarici döngənin (O (n) hər keçişi üçün) yenidən bütün siyahıdan keçməliyik ki, n bizi kvadrat n qədər artırır.

Ancaq səthi cırıqlandırır, ancaq daha mürəkkəb alqoritmləri analiz etməyə davam edərkən, dəlilləri ilə kompleks riyaziyyat oynayır. Bu, ən azı baxmayaraq, əsasları sizə təqdim edir.

184
06 авг. Dshook tərəfindən verilmiş cavab 06 avqust. 2008-08-06 16:34 '08 at 4:34 pm 2008-08-06 16:34

Xüsusi probleminiz üçün Böyük Zamanın necə müəyyən ediləcəyini bilmək faydalı olsa da, bəzi ümumi hallarda sizin alqoritminizdə qərar qəbul etməyinizə çox kömək edə biləcəyini bilmək faydalıdır.

Aşağıda http://en.wikipedia.org/wiki/Big_O_notation#Orders_of_common_functions-dan götürülmüş ən ümumi hallardan bəziləri:

O (1) - ədədin tək və ya tək olmadığını müəyyən edir; sabit ölçülü axtarış masa və ya hash masası istifadə edərək

O (logn) - İkili axtarışı ilə sıralanan bir ardıcıllıqla bir maddə axtarın.

O (n) - sıralanmamış siyahıdakı bir maddə üçün axtarış; iki n-rəqəmli rəqəm əlavə edin

O (n 2 ) - sadə alqoritmlə iki n-rəqəmli sayının çarpılması; iki n × n matris əlavə; bubble sorting və ya daxil çeşidlənməsi

O (n 3 ) - Sadə alqoritm ilə iki n × n matrisin çarpılması

O (c n ) - dinamik proqramlaşdırma istifadə edərək, səyahət edən problemə həll tapmaq; iki mantıksal operatorun kobud qüvvə ilə bərabər olub olmadığını müəyyənləşdirir

O (n!) - Kobud qüvvə axtararaq səyahət edən problemin həlli

O (n n ) - O (n!) Əvəzinə tez-tez istifadə edilir Asimptotik mürəkkəblik üçün daha asan formulalar əldə etmək

83
05 сент. Giovanni Galbo tərəfindən verilmiş cavab 05 Sep. 2008-09-05 22:09 '08 at 10:09 pm 2008-09-05 22:09

Kiçik bir xatırlatma: big O simvolu asimptotik mürəkkəbliyi (məsələn, problemin ölçüsü sonsuza çatdıqda) göstərmək üçün istifadə olunur və bu, sabit bir şey gizlədir.

Bu, O (n) -dəki alqoritm ilə O (n 2 ) arasında ən sürətli olanı hər zaman ilk deyildir (baxmayaraq ki, ölçülü problem üçün n hər zaman belə bir dəyərdir, ilk alqoritm ən sürətlidır).

Gizli sabitin tətbiqə çox asılı olduğunu unutmayın!

Bundan əlavə, bəzi hallarda, icra mühiti ölçülü girişin deterministik funksiyası deyil. Məsələn, çeşidləmə sürətli sort ilə həyata keçirilir: n elementlərinin bir sıra sort üçün lazım olan vaxt sabit deyil, lakin dizinin ilkin konfiqurasiyasına asılıdır.

Müxtəlif vaxt çətinlikləri var:

  • Ən pis vəziyyət (adətən anlamaq üçün ən asan yol olsa da, həmişə çox mənalı deyil)
  • Orta ölçülü (adətən çox çətin olan)

  • ...

Yaxşı bir giriş R.Sedzhuik və P.Fleiolet alqoritmlərinin analizinə girişdir.

Dediğiniz kimi, premature optimisation is the root of all evil və mümkün olduqda profilin kodunu optimallaşdırmaq üçün həmişə istifadə olunmalıdır. Hətta alqoritmlərinizin mürəkkəbliyini müəyyənləşdirməyə kömək edə bilər.

38
23 авг. cavab OysterD 23 avqustda verilir . 2008-08-23 23:43 '08 at 11:43 pm 2008-08-23 23:43

Cavabları gördükdən sonra, mən hesab edirəm ki, əksəriyyətimiz alqoritm sırasını təxminən təxmin edir, onu nəzərdən keçirmək və onu hesablamaq əvəzinə ümumi mənada istifadə etmək, məsələn, universitetdə düşündüyü kimi master metodu . Bununla yanaşı, mən əlavə etməliyəm ki, hətta professor bizi (sonradan) bu barədə düşünməyə təşviq etdi, sadəcə saymaq üçün deyil.

Mən də bunu təkraredici funksiyalar üçün necə edildiyini əlavə etmək istərdim:

Bir növ funksiyanı ( şemanın kodunu ) düşünün:

 (define (fac n) (if (= n 0) 1 (* n (fac (- n 1))))) 

müəyyən bir ədəd faktöryorunu recursively hesablayır.

İlk addım, funksiya orqanının işi üçün xarakteristikasını yalnız bu vəziyyətdə müəyyən etməyə çalışmaqdır, bədəndə xüsusi bir şey edilmir, yalnız çarpma (və ya 1-ə qayıtmaq).

Beləliklə, bədən üçün performans : O (1) (sabit).

Sonra təkrarlanan çağırışların sayı üçün bunu cəhd edin və müəyyənləşdirin. Bu vəziyyətdə n-1 təkrarlanan çağırışlarımız var.

Beləliklə, recursive çağırışlar üçün performans O (n-1) ( n- n, biz qeyri-mühüm hissələr atmaq olduğundan sifariş n).

Sonra bu ikisini bir yerə qoyun və sonra bütün özünəməxsus funksiyanı yerinə yetirəcəksiniz:

1 * (n-1) = O (n)


Peter suallarınıza cavab verəcək ; Burada təsvir etdiyim metod həqiqətən yaxşı işləyir. Amma bu hələ bir təxmin deyil, tam riyazi olaraq doğru bir cavab olduğunu unutmayın. Burada təsvir olunan metod universitetin tədris üsullarından biridir və doğru xatırlayıramsa, bu nümunədə istifadə edilən faktöryeldən daha qabaqcıl alqoritmlər üçün istifadə edilmişdir. Əlbəttə ki, bütün bunlar funksiyanın cismini və təkrarlanan çağırışların sayını nə qədər yaxşı qiymətləndirə bilər, ancaq bu, digər üsullarla bağlıdır.

25
07 авг. Cavab Sven 07 aug tərəfindən verilir . 2008-08-07 11:10 '08 saat 11:10 'da 2008-08-07 11:10

Sizin dəyəriniz bir polinomsa, sadəcə onun sürətini olmadan daha yüksək sifariş üzvünü tutun. Məsələn:

O ((n / 2 + 1) * (n / 2) = O (n 2/4 + n / 2) = O (n 2/4) = O (n 2 )

Bu sonsuz satırlar üçün işləmir, ağla. Ümumiyyətlə, bəzi ümumi hallar üçün aşağıdakı bərabərsizliklər tətbiq olunmasına baxmayaraq, bir resept yoxdur:

O (N N) O (N) O (N log N) O (N2) <O (N k ) <O (e n ) <O (n!)

23
31 янв. Marcelo Cantos tərəfindən verilmiş cavab 31 yanvar 2011-01-31 16:30 '11 saat 16:30 'da 2011-01-31 16:30' da

Mən bu barədə informasiya haqqında düşünürəm. Hər hansı bir problem müəyyən bir bit öyrənməkdir.

Sizin əsas vasitə qərar nöqtələri və onların entropiyası konsepsiyasıdır. Həll nöqtəsinin entropiyası, verdiyiniz orta məlumatdır. Məsələn, bir proqramda iki filiala malik olan bir qərar nöqtəsi varsa, entropiya bu filialın olasılığının tersini 2-ci tarixdə hər bir dalın ehtimalının cəmidir. Bu həllini tamamlayaraq öyrənmək nədir.

Məsələn, eyni şəkildə iki filialı olan bir if entropiya 1/2 * log (2/1) + 1/2 * log (2/1) = 1/2 * 1 + 1/2 * 1 = 1. Beləliklə, onun entropiyası 1 bitdir.

Məsələn, N elementlərindən bir masa axtarırsınız, məsələn N = 1024. Bu, 10 bitlik bir problemdir, çünki log (1024) = 10 bit. Buna görə də, əgər eyni dərəcədə ehtimal olunan nəticələrə sahib olan IF hesabatları ilə axtarış edə bilərsinizsə, bu, 10 qərar qəbul etməlidir.

İkili axtarışla nə əldə edirsiniz?

Satırlı bir axtarış etdiyini düşünürəm. Birinci maddəyə baxır və istədiyinizi istədiyini soruşur. Olasılıklar 1/1024, yəni 1023/1024, bu vəziyyət qeyri-mümkündür. Bu həllin entropiyası 1/1024 * log (1024/1) + 1023/1024 * log (1024/1023) = 1/1024 * 10 + 1023/1024 * 0 haqqında = 0.01 bit təşkil edir. Çox az şey öyrəndin! İkinci həll daha yaxşı deyil. Buna görə linear axtarışlar çox yavaş. Əslində, öyrənmək üçün lazım olan bit sayda üstəlikdir.

İndeks edirsiniz. Cədvəl birdən çox qutuda əvvəlcədən sıralanır və birbaşa bütün qeydlərdən birbaşa masa girişlərinə endirmək üçün istifadə edin. 1024 sığınacaq varsa, entropiya bütün 1024 mümkün nəticələri üçün 1/1024 * log (1024) + 1/1024 * log (1024) + ... təşkil edir. Bu endeksleme əməliyyatı üçün 1/1024 * 10 dəfə 1024 nəticə və ya 10 bit entropiya. Bu səbəbdən indeks axtarışı sürətli.

İndi çeşidlənməsi barədə düşün. N maddələriniz var və bir siyahı var. Hər bir maddə üçün, maddə siyahıda olduğu yerləri tapmaq və sonra siyahıya əlavə etmək lazımdır. Beləliklə, çeşidləmə təxminən N dəfə əsas axtarış mərhələlərinin sayını çəkir.

Beləliklə təxminən eyni dərəcədə ehtimal olunan nəticələrə malik ikili həllərə əsaslanan sortinqlər O (N log N) addımları olaraq qəbul edilir. O (N) sıralama alqoritmi indeks axtarışına əsaslansa mümkündür.

Mən alqoritmik performans ilə demək olar ki, bütün problemləri bu şəkildə görə bilərik.

18
10 марта '09 в 16:24 2009-03-10 16:24 Mike Dunlavey'ye 10 Mart 2009-cu il saat 16: 04-da cavab verib 2009-03-10 16:24

Əvvəldən başlayaq.

Birincisi, bəzi sadə məlumat əməliyyatlarında O(1) vaxtında, yəni girişin ölçüsündən asılı olmayan zaman prinsipi qəbul edin. C-də bu ibtidai əməliyyatlar ibarətdir

  • Aritmetik əməliyyatlar (məsələn, + və ya%).
  • Müqayisə əməliyyatları (məsələn, <=).
  • Quruluşa giriş əməliyyatları (məsələn, A [i] kimi dizinin indeksləşdirilməsi və ya operatoru istifadə edən göstərici).
  • Bir dəyişənə bir dəyər çıxarmaq kimi sadə bir tapşırıq.
  • Kitabxana funksiyalarını çağırmaq (məsələn, scanf, printf).

Bu prinsipin əsaslandırılması tipik bir kompüterin maşın təlimatlarının (ilk addımlar) ətraflı öyrənilməsini tələb edir. Təsvir edilən əməliyyatlardan hər biri az sayda maşın əmrləri ilə həyata keçirilə bilər; tez-tez bir və ya iki təlimat tələb olunur. Nəticədə, C-də bir neçə ifadələr O(1) vaxtında, yəni girişdən asılı olaraq bəzi sabit məbləğdə icra edilə bilər. Bunlar sadədir

  • İşlədilən işlək operatorları ifadələrində funksiyanı çağırmazlar.
  • Oxunan ifadələr.
  • Arqumentləri hesablamaq üçün funksiya çağırışına ehtiyac olmayan ifadələr yazmaq.
  • Keçid operatorları müdaxiləni davam etdirir, goto və ifadə bir funksiya çağırışı içində olmayan bir ifadəni qayıtır.

C-də bir çox for-loops bir indeks dəyişən müəyyən bir dəyər başlamaq və loop ətrafında hər dəfə bu dəyişən artması ilə formalaşır. Bu for-loop konturu müəyyən bir limitə çatdıqda bitir. Məsələn, for-loop

 for (i = 0; i < n-1; i++) { small = i; for (j = i+1; j < n; j++) if (A[j] < A[small]) small = j; temp = A[small]; A[small] = A[i]; A[i] = temp; } 

indeks dəyişənini i istifadə edir. Bu loop ətrafında mənə hər dəfə artırır və n - 1 çatdıqda iterasiya dayandırır.

Bununla yanaşı, indiyədək indeks dəyişəninin artması göstərilən məbləğə bölünən yekun və ilkin dəyərlər arasındakı fərq, loopun ətrafında neçə dəfə keçdiyimizi göstərir . Atlama təlimatını istifadə edərək heç bir döngü çıxış yolu olmadıqda bu hesab dəqiqdir; bu yineleme sayının yuxarı həddi.

Məsələn, for-loop i ((n − 1) − 0)/1 = n − 1 times -0 ((n − 1) − 0)/1 = n − 1 times -i təkrarlayır, çünki 0-i i-nin başlanğıc dəyəri, n-1 isə mənə çatan ən yüksək dəyərdir (yəni, -1, döngü dayanır və i = n-1 ilə yineleme yoxdur) və 1 döngünün hər təkrarlanmasına i əlavə olunur.

Ən sadə halda, hər bir iteration üçün dövr bədənində sərf olunan zaman eynidırsa, bədənin böyük üst sərhədini dövrün ətrafında olan zamanla artıra bilərik . Sözsüz ki, döngü endeksini başlamaq üçün O (1) vaxtını əlavə etməliyik və əvvəlcə loop indeksini limiti ilə müqayisə etmək üçün O (1) vaxtını vermək lazımdır. Lakin, sıfır dövrü dəfə edə bilsəniz, dövrü inisizləşdirmə vaxtı keçirməyə və limitini bir dəfə sınamaqla, ən azı üzv, toplama qayda ilə düşə bilər.


İndi bu nümunəni nəzərdən keçirin:

 (1) for (j = 0; j < n; j++) (2) A[i][j] = 0; 

Biz bilirik ki, simli (1) O(1) vaxtını alır. Aşağıdakı həddi (1) olan yuxarı həddindən çıxaraq və sonra əlavə etməklə müəyyən edə biləcəyimizdən bəlli oluruq. Çünki bədən, xətti (2), O (1) , можно пренебречь время для увеличения j и время для сравнения j с n, оба из которых также O (1). Таким образом, время выполнения строк (1) и (2) является <сильным > произведением n и O (1), которое O(n) .

Аналогично, мы можем связать время работы внешнего цикла, состоящего из строк (2) - (4), который равен

 (2) for (i = 0; i < n; i++) (3) for (j = 0; j < n; j++) (4) A[i][j] = 0; 

Мы уже установили, что цикл линий (3) и (4) принимает время O (n). Таким образом, мы можем пренебречь временем O (1) для увеличения я и проверить, является ли я < n в каждая итерация, заключающаяся в том, что каждая итерация внешнего цикла принимает O (n) время.

Инициализация я = 0 внешнего цикла и (n + 1) -й тест условия i < n также принимают O (1) раз и можно пренебречь. Наконец, заметим, что мы идем вокруг внешнего цикла n раз, принимая O (n) время для каждой итерации, давая общую O(n^2) время выполнения.


Более практичный пример.

2019

16
ответ дан ajkumar25 02 февр. '14 в 18:30 2014-02-02 18:30

Если вы хотите оценить порядок своего кода эмпирически, а не анализировать код, вы можете придерживаться серии значений n и времени вашего кода. Запланируйте тайминги в масштабе журнала. Если код O (x ^ n), значения должны падать на линию наклона n.

У этого есть несколько преимуществ перед изучением кода. Во-первых, вы можете увидеть, находитесь ли вы в диапазоне, где время выполнения приближается к его асимптотическому порядку. Кроме того, вы можете обнаружить, что какой-то код, который, по вашему мнению, был порядком O (x), действительно является порядком O (x ^ 2), например, из-за времени, затраченного на вызовы библиотеки.

9
ответ дан John D. Cook 11 дек. '08 в 23:49 2008-12-11 23:49

В основном то, что составляет 90% времени, - это просто анализ циклов. У вас есть одиночные, двойные, тройные вложенные циклы? Вы выполняете время O (n), O (n ^ 2), O (n ^ 3).

Очень редко (если вы не пишете платформу с обширной базовой библиотекой (например,.NET BCL или С++ STL), вы столкнетесь с чем-либо, что сложнее, чем просто смотреть на ваши циклы (для операторов, в то время как, goto и т.д.)

7
ответ дан Adam 14 авг. '08 в 18:35 2008-08-14 18:35

Знакомство с алгоритмами/структурами данных, которые я использую, и/или быстрый анализ анализа итерации. Трудность заключается в том, что вы вызываете библиотечную функцию, возможно, несколько раз - вы часто можете быть не уверены в том, что вы вызываете функцию ненужно порой или какую реализацию они используют. Возможно, функции библиотеки должны иметь меру сложности/эффективности, будь то Big O или какая-либо другая метрика, которая доступна в документации или даже IntelliSense .

6
ответ дан Matt Mitchell 06 авг. '08 в 13:59 2008-08-06 13:59

Менее полезно вообще, я думаю, но для полноты есть также Big Omega Ω , который определяет нижнюю границу на сложность алгоритма и Big Theta Θ , которая определяет как верхнюю, так и нижнюю границу.

6
ответ дан Martin 23 сент. '08 в 10:26 2008-09-23 10:26

Разбейте алгоритм на части, для которых вы знаете большую нотацию O, и объединитесь с большими операторами O. Это единственный способ узнать.

Для получения дополнительной информации просмотрите страницу Википедии по этому вопросу.

6
ответ дан Lasse Vågsæther Karlsen 06 авг. '08 в 14:34 2008-08-06 14:34

Нотация Big O полезна, потому что она легко работает и скрывает ненужные сложности и детали (для некоторого определения ненужного). Одним из хороших способов разработки сложностей алгоритмов разделения и покорения является метод tree. Скажем, у вас есть версия quicksort с медианной процедурой, поэтому вы каждый раз разбиваете массив на идеально сбалансированные подмассивы.

Теперь создадим дерево, соответствующее всем массивам, с которыми вы работаете. В корне у вас есть исходный массив, у корня есть два дочерних элемента, которые являются подмассивами. Повторите это, пока у вас нет одиночных массивов элементов внизу.

Поскольку мы можем найти медиану в O (n) времени и разделить массив на две части в O (n) времени, работа, выполняемая в каждом node, равна O (k), где k - размер массива, Каждый уровень дерева содержит (не более) весь массив, поэтому работа на уровне равна O (n) (размеры подмассивов складываются до n, и поскольку у нас есть O (k) на уровень, мы можем добавить это), В дереве есть только уровни log (n), так как каждый раз, когда мы вдвое уменьшаем ввод.

Поэтому мы можем ограничить верхнюю часть работы O (n * log (n)).

Однако Big O скрывает некоторые детали, которые мы иногда не можем игнорировать. Рассмотрим вычисление последовательности Фибоначчи с помощью

 a=0; b=1; for (i = 0; i <n; i++) { tmp = b; b = a + b; a = tmp; } 

и просто предположим, что a и b - BigIntegers в Java или что-то, что может обрабатывать сколь угодно большие числа. Большинство людей сказали бы, что это алгоритм O (n), не дрогнув. Причиной является то, что у вас есть n итераций в цикле for и O (1) работают в цикле.

Но числа Фибоначчи велики, n-е число Фибоначчи экспоненциально по n, поэтому его сохранение займет порядка n байтов. Выполнение добавления с большими целыми числами будет занимать O (n). Таким образом, общий объем работы, выполненной в этой процедуре, составляет

1 + 2 + 3 +... + n = n (n-1)/2 = O (n ^ 2)

Итак, этот алгоритм работает в квадратичное время!

5
ответ дан Pall Melsted 08 авг. '08 в 16:53 2008-08-08 16:53

Мы должны забыть о небольших эффективности, скажем, около 97% время: преждевременная оптимизация - это корень всего зла.

что вся цитата кстати. так что dosnt означает никогда не оптимизировать, прежде чем перейти на стадию оптимизации.

5
ответ дан Annerajb 03 дек. '17 в 1:18 2017-12-03 01:18

Для 1-го случая внутренний цикл выполняется ni раз, поэтому общее количество исполнений представляет собой сумму для i , идущую от 0 до n-1 (потому что ниже, не ниже или равно ) ni . Наконец, вы получите n*(n + 1) / 2 , поэтому O(n²/2) = O(n²) .

Для второго цикла i находится между 0 и n , включенными для внешнего цикла; то внутренний цикл выполняется, когда j строго больше n , что невозможно.

5
ответ дан Emmanuel 31 янв. '11 в 17:36 2011-01-31 17:36

Что касается "как вы вычисляете" Big O, это часть Теория сложности вычислений . Для некоторых (многих) особых случаев вы можете использовать некоторые простые эвристики (например, умножать количество циклов для вложенных циклов), особенно. когда все, что вам нужно, - это оценка верхней границы, и вы не возражаете, если она слишком пессимистична, - я думаю, вероятно, это ваш вопрос.

Если вы действительно хотите ответить на свой вопрос по любому алгоритму, лучше всего использовать теорию. Помимо упрощенного анализа "наихудшего случая" я нашел "Амортизированный анализ" , очень полезный на практике.

5
ответ дан Suma 10 марта '09 в 18:02 2009-03-10 18:02

В дополнение к использованию метода master (или одной из его специализаций), я тестирую свои алгоритмы экспериментально. Это не может доказать, что какой-либо конкретный класс сложности достигается, но он может обеспечить подтверждение того, что математический анализ является подходящим. Чтобы помочь с этим подтверждением, я использую инструменты покрытия кода в сочетании с моими экспериментами, чтобы убедиться, что я занимаюсь всеми делами.

Как очень простой пример, вы говорите, что хотите выполнить проверку работоспособности скорости сортировки списка .NET framework. Вы можете написать что-то вроде следующего, а затем проанализировать результаты в Excel, чтобы убедиться, что они не превышают кривую n * log (n).

В этом примере я измеряю количество сравнений, но также разумно изучить фактическое время, необходимое для каждого размера выборки. Однако тогда вы должны быть еще более осторожны в том, что вы просто измеряете алгоритм и не включаете артефакты из вашей тестовой инфраструктуры.

 int nCmp = 0; System.Random rnd = new System.Random(); // measure the time required to sort a list of n integers void DoTest(int n) { List<int> lst = new List<int>(n); for( int i=0; i<n; i++ ) lst[i] = rnd.Next(0,1000); // as we sort, keep track of the number of comparisons performed! nCmp = 0; lst.Sort( delegate( int a, int b ) { nCmp++; return (a<b)?-1:((a>b)?1:0)); } System.Console.Writeline( "{0},{1}", n, nCmp ); } // Perform measurement for a variety of sample sizes. // It would be prudent to check multiple random samples of each size, but this is OK for a quick sanity check for( int n = 0; n<1000; n++ ) DoTest(n); 
4
ответ дан Eric 05 сент. '08 в 21:33 2008-09-05 21:33

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

Иногда сложность может возникать из того, сколько раз называется чем-то, как часто выполняется цикл, как часто выделяется память и т.д. является другой частью ответа на этот вопрос.

Наконец, большой O можно использовать для случаев наихудшего случая, наилучшего случая и амортизации, где, как правило, это худший случай, который используется для описания того, насколько плохим может быть алгоритм.

3
ответ дан JB King 14 окт. '08 в 23:16 2008-10-14 23:16

То, что часто упускается из виду, - это ожидаемое поведение ваших алгоритмов. Он не меняет Big-O вашего алгоритма , но он относится к утверждению "преждевременная оптимизация..."

Ожидаемое поведение вашего алгоритма - очень подавленное - насколько быстро вы можете ожидать, что ваш алгоритм будет работать с данными, которые вы, скорее всего, увидите.

Например, если вы ищете значение в списке, оно O (n), но если вы знаете, что большинство списков, которые вы видите, имеют ваше значение вверх, типичное поведение вашего алгоритма выполняется быстрее.

Чтобы действительно прибить его, вам нужно будет описать распределение вероятностей вашего "входного пространства" (если вам нужно отсортировать список, как часто этот список уже будет сортироваться, как часто это полностью как часто это чаще всего сортируется?) Не всегда возможно, что вы это знаете, но иногда вы это делаете.

3
ответ дан Baltimark 10 марта '09 в 17:30 2009-03-10 17:30

Для кода A внешний цикл будет выполняться в течение n + 1 раз, время "1" означает процесс, который проверяет, все ли соответствует требованиям. И внутренний цикл выполняется n раз, n-2 раза.... Таким образом, 0 + 2 +.. + (n-2) + n = (0 + n) (n + 1)/2 = O (n²).

Для кода B, хотя внутренний цикл не включается и не выполняет foo(), внутренний цикл будет выполняться в течение n раз, зависит от времени выполнения внешнего цикла, которое равно O (n)

1
ответ дан laynece 31 янв. '11 в 23:07 2011-01-31 23:07

Я не знаю, как программно решить это, но первое, что люди делают, это то, что мы пробовали алгоритм для определенных шаблонов в количестве выполненных операций, скажем, 4n ^ 2 + 2n + 1, у нас есть 2 правила:

  • Если у нас есть сумма терминов, термин с наибольшей скоростью роста сохраняется, а остальные члены опущены.
  • Если у нас есть произведение нескольких факторов, то постоянные факторы опущены.

Если мы упростим f (x), где f (x) - формула для числа выполненных операций (4n ^ 2 + 2n + 1, описанная выше), получаем значение большого О [O (n ^ 2 ) в этом случае]. Но это должно было бы учитывать интерполяцию Лагранжа в программе, которую может быть трудно реализовать. И что, если реальное значение big-O было O (2 ^ n), и мы могли бы иметь что-то вроде O (x ^ n), поэтому этот алгоритм, вероятно, не был бы программируемым. Но если кто-то докажет, что я неправ, дай мне код.,.

1
ответ дан Gavriel Feria 11 мая '13 в 4:32 2013-05-11 04:32

отличный вопрос!

Если вы используете Big O, вы говорите о худшем случае (больше о том, что это значит позже). Кроме того, есть средняя тета для среднего случая и большая омега для лучшего случая.

Посмотрите этот сайт на прекрасное формальное определение Big O: https://xlinux.nist.gov/dads/HTML/bigOnotation.html

f (n) = O (g (n)) означает, что существуют положительные константы c и k, такие, что 0 ≤ f (n) ≤ cg (n) для всех n ≥ k. Значения c и k должны быть фиксированы для функции f и не должны зависеть от n.


Итак, теперь что мы понимаем под "наилучшими" и "худшими" сложностями?

Это, вероятно, наиболее ярко иллюстрируется примерами. Например, если мы используем линейный поиск для поиска числа в отсортированном массиве, тогда наихудший случай , когда мы решаем искать последний элемент массива, поскольку это выполните столько шагов, сколько есть элементов в массиве. лучший случай будет, когда мы будем искать первый элемент , поскольку мы будем делать это после первой проверки.

Точка всех этих сложностей приложных случаев заключается в том, что мы ищем способ отобразить время, в течение которого гипотетическая программа заканчивается с точки зрения размера конкретных переменных. Однако для многих алгоритмов вы можете утверждать, что нет определенного времени для определенного размера ввода. Обратите внимание, что это противоречит фундаментальному требованию функции, любой вход должен иметь не более одного выхода. Поэтому мы предлагаем множество функций для описания сложности алгоритма. Теперь, хотя поиск массива размера n может занять различное количество времени в зависимости от того, что вы ищете в массиве и в зависимости от n, мы можем создать информативное описание алгоритма, используя наилучший случайный случай, и наихудших классов.

Извините, что это так плохо написано и не хватает технической информации. Но, надеюсь, это облегчит обсуждение классов сложности времени. Как только вам станет комфортно с ними, становится проще разбираться в вашей программе и искать такие вещи, как for-loops, которые зависят от размеров массивов и рассуждений на основе ваших структур данных, какие входные данные приведут к тривиальным случаям и какой результат приведет к в худших случаях.

1
ответ дан Samy Bencherif 20 авг. '16 в 7:57 2016-08-20 07:57

Другие вопросы по меткам или Задайте вопрос