Bioinformatyk.eu nowy serwis o bioinformatyce i programowaniu

16 maj, 2009

Testowanie złożoności obliczeniowej 2 algorytmów w Pythonie

Zamieszczony przez: Justi w: Python

Postanowiłam sprawdzić w praktyce, jak się mają teoretyczne wyliczenia złożoności obliczeniowej algorytmów do rzeczywistych wyników uzyskiwanych w kompilatorze.

Do testów wybrałam język Python oraz kompilator Eclipse.

Problem:
Znaleźć minimalny i maksymalny element w tablicy n-elementowej.

Można to zrobić na 2 sposoby:

1) przechodząc po kolei po każdym elemencie tablicy zadecydować, czy przypisać go do min lub max. Złożoność tego algorytmu to ok. 2n
2) porównywać ze sobą 2 kolejne elementy i zadecydować który przypisać do min a który do max. Złożoność tego algorytmu to ok. 3*n/2 = 1,5n

Jak wynika ze wstępnych założeń, algorytm drugi powinien być o ok. 25% szybszy niż pierwszy.

Zapis w Pythonie:

Algorytm 1:

def algorytm1(tab):

        min = 0
        max = 0
        t1 = time.clock()

        for i in range(0,ilosc,1):
            if (tab[i] > max):
                max = tab[i]
            if (tab[i] < min):
                min = tab[i]
        t2 = time.clock()

        return t2-t1

Algorytm 2:

def algorytm2(tab):

        min = 0
        max = 0
        t1 = time.clock()

        for i in range(0,ilosc,2):
            if(tab[i] > tab[i+1]):
                if (tab[i] > max):
                    max = tab[i]
                if (tab[i+1] < min):
                    min = tab[i+1]
            else:
                if (tab[i+1] > max):
                    max = tab[i+1]
                if (tab[i] < min):
                    min = tab[i]
        t2 = time.clock()

        return t2-t1

Obie funkcje zwracają różnicę czasu pomiędzy momentem swojego startu i zakończenia.

Tablice testową program tworzy z losowo wybranych liczb typu całkowitego z przedziału od 1 do 1000. Wielkość tablicy przekazywana jest jako parametr tej funkcji.

def genTab(ilo):

        tablica = []

        for i in range(ilo):
            tablica.append(round(random.random() * 1000))

        return tablica

Cały program do ściągnięcia:

  zlozonosc.py (1,4 KiB, 1 468 hits)

Program zwraca procentową różnicę czasu działania między algorytmem drugim a pierwszym. Jest to średnia z x prób (ostatnia pętla).

Wyniki zaprezentowane są na wykresie:
procentowy_wzrost_predkosci_algorytmu_drugiego_do_pierwszego_w_zaleznosci_od_wielkosci_tablicy_testowej

Jak widać, różnica w szybkości działania obu algorytmów jest tym bardziej widoczna, im większy jest rozmiar danych, (n-> nieskończoności).

Jeszcze wykres średniej procentowej wzrostu:
sredni_procentowy_wzrost_szybkosci_dzialania_alg_2_do_alg_1

A gdyby nieco zoptymalizować kod algorytmu numer 2?

Dopisałam w nim 2 linijki kodu

            zm1 = tab[i]
            zm2 = tab[i+1]

po to, aby nie odwoływać się za każdym razem do samej tablicy (która może być stosunkowo duża), zamiast tego zrobić to raz, na samym początku.

Wyniki są ciekawe:
wzrost_szybkosci_algorytmu_drugiego_po_optymalizacji

Dla dużych wartości n szybkość działania algorytmu drugiego względem pierwszego wzrosła z ok 20% do ok. 27,5% :)

Program po optymalizacji do ściągnięcia:

  zlozonoscZopt.py (1,4 KiB, 1 094 hits)

Autorem artykułu jest Justi

10 odpowiedzi na "Testowanie złożoności obliczeniowej 2 algorytmów w Pythonie"

1 | patik

17. Maj 2009 o 21:58

Avatar

jestem pod wrazeniem : O

2 | Justi

17. Maj 2009 o 23:15

Avatar

ja też.. założenia Billa są zgodne z prawdą ;)

Cieszę się że ktoś jednak czyta moje wypociny ;)

3 | frankie

19. Maj 2009 o 16:40

Avatar

ojacie… szkoda, że nie chodzę na algorytmy bo części nie rozumiem…
muszę tego chyba empirycznie doświadczyć ;)

4 | marek

11. Grudzień 2009 o 16:00

Avatar

Próbowałeś zoptymalizować algorytm1:

def algorytm1b(tab):

min = 0
max = 0

t1 = time.clock()
for i in tab[:ilosc]:
if (i > max):
max = i
if (i < min):
min = i
t2 = time.clock()
return t2-t1

Ten jest szybszy Odczytuje kolejne elementy tablicy a nie za każdym razem wyszukuje po indeksie.
Powoduje to że liczba porównań jest mniejsza.
Można co prawda optymalizować algorytm 2 ale jest on błędny gdyż działa wyłącznie dla parzystej długości danych (chyba że tak ma właściwie działać). Przypisanie początkowych wartości min i max też nie jest uniwersalne (w tym nawet przypadku nie ma liczb ujemnych zawsze min=0) co może mieć wpływ na wynik.i

5 | marek

11. Grudzień 2009 o 16:00

Avatar

Po wprowadzeniu początkowych wartości min i max oraz zamiany jednego if na elsif uzyskujemy:

def algorytm1c(tab):

t1 = time.clock()

min =max= tab[0]

for i in tab[1:ilosc]:

if (i > max):

max = i

elif (i < min):

min = i

t2 = time.clock()

return t2-t1

Uzyskujemy w ten sposób krótki i szybki algorytm który ma złożoność 2n jednak jego implementacja w języku Python daje znacznie leprze wyniki dla nie posortowanych tablic.

Niestety ponieważ twoja implementacja algorytm zoptymalizowanego nie działa poprawnie dla długości danych nieparzystej nie ma sensu ich porównywać.

6 | Rafał

11. Grudzień 2009 o 20:12

Avatar

Nie jestem przekonany co to wyższości algorytmu Marka nad algorytmem Justyny pod względem wydajności. Na pewno jest to ładniejszy i czytelniejszy. Nie wiem jak w Python’ie ale normalnie dostęp do elementu tablicy poprzez index ma złożoność stałą (przyjmuje się jednostkową). Inaczej byłoby gdybyśmy mieli słownik lub inną odmianę hashtable. Ich złożoność jest typu LOG. Zatem proponuję wykonać test porównawczy.

7 | marek

16. Grudzień 2009 o 12:59

Avatar

Chodzi mi o to że Justi optymalizował(a) //notka edytorska, jestem kobietą :D // implementację drugiego algorytmu a tymczasem pierwszy również można zoptymalizować. Co więcej poprawa drugiego algorytmu tak aby działał również dla parzystej długości danych mogłaby spowodować spadek wydajności (chociaż prawdopodobnie tak się nie stanie).

Nie wiem jaki wpływ na wyniki testu miałoby poprawne zapisanie warunków początkowych.
zamiast:

min = 0
max = 0

Należy napisać:

min =max= tab[0]

@Rafał: W moich testach ostatnia przedstawiona przeze mnie funkcja była najszybsza. Koszt dostępu do elementu tablicy jest stały ale w przypadku moich poprawek jest mniejszy.

Wniosek jest taki że algorytm jest lepszy gdy jego złożoność jest przynajmniej o rząd wielkości mniejsza. Np. zamiast złożoności n^2 ma złożoność n, zamiast n otrzymujemy log(n) itd..
Jeżeli przyrost wydajności wynosi 25% a algorytm jest bardziej skomplikowany nie ma sensu go zmieniać.

8 | marek

17. Grudzień 2009 o 11:23

Avatar

Racja, przepraszam, powinienem domyśleć się że nick Justi nie pasuje do faceta :) . Popełniłem również błąd (chociaż nie tak ważny) gdy napisałem „dla parzystej długości” a powinno być „dla nie parzystej długości”. Zauważyłem również dziwne zachowanie komentarzy. Otóż gdy próbowałem komentarz 4 i 5 wysłać jako jeden obcięło wszystko co było napisane po pierwszym listingu aż do końca drugiego, i dopiero komentarz do drugiego było widać. Ciekawe dlaczego?

9 | Justi

17. Grudzień 2009 o 11:52

Avatar

Dzięki za trafne uwagi, przetestuję Twoje rozwiązanie.

Odnośnie ucinanych komentarzy, myślę ze to domyślny parser WP czyni tutaj zamieszanie… Postaram się zmodyfikować domyślne ustawienia ;)

10 | Notacja asymptotyczna | Bioinformatyk.eu: serwis o bioinformatyce i programowaniu

21. Grudzień 2009 o 00:07

Avatar

[...] Dzisiaj będzie trochę teorii n.t. tzw. notacji asymptotycznej. jest ona używana do szacowania złożoności obliczeniowej algorytmów, dzięki czemu można w miarę dokładnie wyznaczyć takie parametry jak czas wykonywania się określonych instrukcji, rozmiar alokowanej pamięci itp. trochę na ten temat pisałam już tutaj. [...]

Formularz komentarza

Bioinformatyk.eu na Facebook'u

Ciekawa literatura

Reklama

Linki

O serwisie


Dynamiczny serwis tworzony przez studentów, dla studentów, który chcą dowiedzieć się czym jest i zajmuje się bioinformatyka. Naszym głównym celem jest zaopatrzenie Ciebie w niezbędną wiedzę, potrzebną do wypłynięcia w rozwijający się świat bioinformatyczny!