« Complexité pratique contre complexité théorique » : différence entre les versions

De Wiki du LAMA (UMR 5127)
Aller à la navigation Aller à la recherche
Ligne 179 : Ligne 179 :
'''Multiplication naive:'''
'''Multiplication naive:'''
<pre>
<pre>
def multint(T,a):
val = multscalaireT(T,a)
res = 0
for i in range (0,len(val)):
res = res + val[i] * 10 ** (len(val)-1 - i)
return int(res)

def multscalaireT(T,a):
def multscalaireT(T,a):
return [T[i] * a for i in range (len(T))]
return [T[i] * a for i in range (len(T))]

Version du 12 mai 2021 à 15:02


Principe de la complexité

La complexité en algorithmique sert à montrer l'efficacité d'un algorithme, en fonction de plusieurs facteurs comme son temps d'exécution, l'espace qu'il occupe dans la mémoire... (ces facteurs sont en générale induits par le nombre d'opérations qu'effectue l'algorithme).
Afin de mesurer et de représenter cette complexité, on représente l'évolution du temps d'exécution d'une même fonction en fonction de la taille de ses arguments.
On va donc, la plupart du temps utiliser des tableaux en entrée des fonctions, et faire évoluer leurs tailles afin d'obtenir la courbe de leur complexité.
Cette courbe va ainsi nous éclairer sur le comportement asymptotique de l'algorithme.
Ce comportement asymptotique représente le comportement général de la fonction de la complexité (souvent impossible à calculer de manière exacte).
Cette approximation est généralement représentée par , ou f(n) représente une fonction telle que pour n ->, (où c(x) représente la fonction de la complexité dans le pire des cas: plus d'opérations possibles a effectuer).
Ce comportement asymptotique peut être analysé sous deux différents point de vue:
Le point de vue théorique, qui va analyser le comportement générale de deux complexité et déduire laquelle des deux est la moins complexe en prenant la valeur vers laquelle tend O(f(n)) pour .
Le point de vue pratique, qui, quand à lui va analyser le comportement générale de deux complexité en fonction d'une taille de tableau limité par l'utilisation régulière de la fonction ou capacité à traiter les information de l'ordinateur.

Etudes d'algorithmes

Afin de mesurer la complexité avec le langage python, on utilise généralement une fonction essentielle, la bibliotheque "timeit".

import timeit

def chronoquick(tab,nessais):
    '''Entrées: un tableau et un entier, Sortie: un entier''' 
    res = timeit.timeit("quickselectmed("+ str (tab)+")","from __main__ import quickselectmed" , number= nessais)
    return res

La fonction timeit.timeit permet de mesurer le temps d'exécution de la fonction entrée (ici quickselectmed(tab)) en fonction d'un nombre d'essais défini. Plus ce nombre d'essais est grand, plus le taux d'erreur sur la forme générale de la courbe sera réduit.

dichotomie

Le premier cas où nous allons analyser la complexité est un algorithme de dichotomie.

def entiersdicho(tab, entier):
    '''Entrée: tab un tableau, entier un entier
    Sortie: res un entier (position de l'entier dans le tableau'''
    b_inf = 0
    b_sup = len(tab)
    while b_inf <= b_sup and b_inf < len(tab) :
        mil =(b_sup + b_inf)//2
        if tab[mil] > entier :
            b_sup = mil
        elif tab[mil] == entier:
            return mil
        else:
            b_inf = mil + 1
    return -1


def cherchetab(tab):
    entier = randint(0,len(tab))
    res = entiersdicho(tab, entier)
    return res

La fonction "entiersdicho" permet de rechercher dans un tableau trié donné en paramètre, un élément de manière dichotomique.
On definit la valeur du milieu du tableau ((borne supérieure + borne inférieure)//2). On regarde ensuite si l'entier recherché est inférieur supérieur ou égal à la valeur du milieu :
Si la valeur du milieu est inférieur à l'entier, on recommence la recherche avec cette fois ci, la borne supérieur égale à la valeur précédente du milieu.
Si la valeur du milieu est supérieur à l'entier, on recommence la recherche avec cette fois ci, la borne inférieur égale à la valeur précédente du milieu.
Enfin si la valeur du milieu est égale à l'entier, on renvoi la valeur du milieu (position dans le tableau).

Dicho100000.PNG
Une fois que l'on trace la complexité de la fonction, on observe une forme logarithmique c'est à dire

tri de tableau: main/python

Un deuxième cas d'étude est la comparaison entre la complexité d'une fonction de tri par la méthode du "tri à bulle" et de la fonction de tri de python : sorted().

def echange(tab, i , j):
    '''Entrée: tableau, deux entier i et j, Sortie: tableau '''
    val_i=tab[i]
    val_j=tab[j]
    tab[i] = val_j
    tab[j] = val_i
    return tab

def triabulle(tab):
    '''Entrée : un entier, Sortie: un tableau'''
    fini = False
    while fini == False:
        nbechanges = 0
        for i in range (0,len(tab)-1):
            if (tab[i]> tab[i+1]):
                echange (tab, i , i+1)
                nbechanges = nbechanges + 1
        if nbechanges == 0:
            fini = True
    return tab

def tripython(tab):
    '''Entrée : un entier, Sortie: un tableau'''
    res = sorted(tab)
    return res

Ici, la fonction "triabulle" va parcourir le tableau jusqu'à tomber sur une valeure supérieure à la suivante. Dans ce cas elle va échanger ces deux valeurs. Si à la fin du tableau aucun élément n'a été échangé, la fonction renvoi le tableau désormais trié, sinon, elle refait un tour. Bulle500.PNG fonction "triabulle"
Python500.PNG fonction "sorted()" de python
En étudiant ces deux fonctions on remarque que leurs formes générales sont relativement identiques () mais que la fonction "sorted()" prends un temps bien inférieur à s'exécuter que "triabulle()". On peut donc conclure qu'au point de vue théorique, ces deux formules sont équivalente, mais au point de vue pratique, on va privilégier l'utilisation de "sorted()" car pour la même croissance en fonction de la taille du tableau, elle met beaucoup moins de temps à s'exécuter (ce qui est dû à la compilation de python bien plus efficace sur une fonction déjà incluse dans le langage).

mediane

def medianenaive(tab):
    '''Entrée: un tableau, Sortie: un entier'''
    tri = triabulle(tab)
    res= tri[len(tab)//2]
    return res

def medianepython(tab):
    '''Entrée: un tableau, Sortie: un entier'''
    return median(tab)

Compairaisonmednaive-medpy10essais.PNG

medianes des medianes, quickselect

quickselect :

def quickselectmed(tab):
    '''Entrée: un tableau, Sortie: un entier'''
    taille = len(tab)
    indicemed = taille//2
    valmed = cherchevalind(tab,indicemed)
    t = sorted(tab)
    return valmed, t[len(t)//2]

def cherchevalind(tab,indice):
    elmt = tab[randint(0,len(tab)-1)]
    inf = []
    sup = []
    for i in range(0, len(tab)):
        if tab[i] < elmt:
            inf.append(tab[i])
        elif tab[i]> elmt:
            sup.append(tab[i])
    if len(tab) - len(sup) > indice >= len(inf):
        return elmt
    elif len(inf) > indice:
        return cherchevalind(inf,indice)
    else:
        return cherchevalind(sup,indice - len(tab) + len(sup))

Medianes des medianes :

def medianemediane(tab):
    '''entree tab non trié, sortie int mediane '''
    res = 0
    medianespaquets = []
    nbpaquets = len(tab)//5
    for i in range (0, nbpaquets):
        paquet = tab[i: i + 5]
        medianespaquets.append(mediane(paquet))
    if len(tab) % 5 != 0:
        paquet = dernierpaquet(tab)
        medianespaquets.append(mediane(paquet))
    if len(medianespaquets)== 1 :
        return medianespaquets[0]
    res = medianemediane(medianespaquets)
    return res

def medmed(tab):
    taille = len(tab)
    indicemed = taille//2
    return cherchevalindmedmed(tab,indicemed)
    
def cherchevalindmedmed(tab,indice):
    elmt = medianemediane(tab)
    inf = []
    sup = []
    for i in range(0, len(tab)):
        if tab[i] < elmt:
            inf.append(tab[i])
        elif tab[i]> elmt:
            sup.append(tab[i])
    if len(tab) - len(sup) > indice >= len(inf):
        return elmt
    elif len(inf) > indice:
        return cherchevalind(inf,indice)
    else:
        return cherchevalind(sup,indice - len(tab) + len(sup))


Tabnontri1000medpymedmedquick.PNG Tabtri1000medpymedmedquick.PNG

Multiplication naive / Karatsuba

Multiplication naive:

def multint(T,a):
    val = multscalaireT(T,a)
    res = 0
    for i in range (0,len(val)):
        res = res + val[i] * 10 ** (len(val)-1 - i)
    return int(res)

def multscalaireT(T,a):
    return [T[i] * a for i in range (len(T))]

def multnaive(T1,T2):
    res = []
    for i in range (len(T1)):
        addition =([0] * i) + multscalaireT(T2,T1[i])
        res = plus(addition,res)
    return res

def plus(T1,T2):
    m = min(len(T1),len(T2))
    res = [T1[i] + T2[i] for i in range(m)]
    res.extend(T1[m:]+T2[m:])
    return res

Karatsuba

def karatsubanaive(nb1,nb2):
    '''Entrée: deux tableaux, Sortie: un tableau)'''
    taillenb1 = len(nb1)
    taillenb2 = len(nb2)
    if ( taillenb1 == 1):
        return multscalaireT(nb2,nb1[0])
    elif( taillenb2 == 1) :
        return multscalaireT(nb1,nb2[0])
        return multnaive(nb1,nb2)
    else :
        mil1=len(nb1)//2
        mil2=len(nb2)//2
        a=int(nombre1[:mil1])
        b=int(nombre1[mil1:])
        c=int(nombre2[:mil2])
        d=int(nombre2[mil2:])
        elmt1 = karatsubaprincipe(a,c)
        elmt2 = karatsubaprincipe(b,d)
        elmt3 = karatsubaprincipe(a,d)
        elmt4 = karatsubaprincipe(b,c)
        return resultatkaratsuban(elmt1,elmt2,elmt3,elmt4)


Multnaive1000.PNG


CONNETABLE Theo (Tuteur : HYVERNNAT Pierre)