web-dev-qa-db-fra.com

Fusionner le temps de tri et la complexité de l'espace

Prenons cette implémentation de Merge Sort comme exemple

void mergesort(Item a[], int l, int r) {
if (r <= l) return;
int m = (r+l)/2;
mergesort(a, l, m);  ------------ (1)
mergesort(a, m+1, r); ------------(2)
merge(a, l, m, r);

a) La complexité temporelle de ce tri par fusion est O (nlg (n)). La parallélisation (1) et (2) apportera-t-elle un gain pratique? Théoriquement, il semble qu'après les avoir parallélisés, vous vous retrouveriez également dans O (nlg (n). Mais pouvons-nous pratiquement obtenir des gains?

b) La complexité spatiale de ce type de fusion est ici O (n). Toutefois, si je choisis d'effectuer un tri sur la fusion sur place à l'aide de listes chaînées (je ne suis pas sûr que cela puisse être fait raisonnablement avec des tableaux), la complexité de l'espace deviendra O (lg (n)), car vous devez tenir compte de la taille du cadre de pile de récursivité. ? Peut-on traiter O(lg(n)) comme constante car il ne peut pas être supérieur à 64? J'ai peut-être mal compris cela à quelques endroits. Quelle est exactement la signification de 64?

c) http://www.cprogramming.com/tutorial/computersciencetheory/sortcomp.html indique que le tri par fusion nécessite un espace constant à l'aide de listes chaînées. Comment ? Ont-ils traité O(lg(n) constant?

d) [Ajouté pour plus de clarté] Pour le calcul de la complexité d'espace, est-il juste de supposer que le tableau ou la liste en entrée est déjà en mémoire? Lorsque je fais des calculs de complexité, je calcule toujours l’espace "Extra" dont j’ai besoin, outre l’espace déjà pris par entrée. Sinon, la complexité de l’espace sera toujours O(n) ou pire.

20
Frank Q.

a) Oui, dans un monde parfait, vous devez vous connecter en fusionnant des tailles n, n/2, n/4 ... (ou mieux, 1, 2, 3 ... n/4, n/2 , n - ils ne peuvent pas être parallélisés), ce qui donne O (n). C'est toujours O (n log n). Dans un monde pas si parfait, vous n'avez pas un nombre infini de processeurs et la commutation de contexte et la synchronisation ne compensent pas les gains potentiels.

b) La complexité de l'espace est toujours Ω (n) car vous devez stocker les éléments quelque part. Une complexité d'espace supplémentaire peut être O(n) dans une implémentation utilisant des tableaux et O(1) dans des implémentations de liste liée. Dans la pratique, les implémentations utilisant des listes nécessitent de l'espace supplémentaire pour les pointeurs de liste. Par conséquent, à moins que la liste ne soit déjà en mémoire, cela ne devrait pas avoir d'importance.

edit si vous comptez les images de la pile, alors il s’agit de O (n) + O (log n), donc encore O(n) dans le cas de tableaux. Dans le cas de listes, c’est la mémoire supplémentaire O (log n).

c) Les listes n'ont besoin que de quelques pointeurs modifiés pendant le processus de fusion. Cela nécessite une mémoire supplémentaire constante.

d) C'est la raison pour laquelle, dans l'analyse de la complexité par fusion-tri, les gens mentionnent un «besoin d'espace supplémentaire» ou des choses du genre. Il est évident que vous devez stocker les éléments quelque part, mais il est toujours préférable de mentionner «mémoire supplémentaire» pour tenir les puristes à distance.

16
soulcheck

La complexité temporelle de MergeSort est O(nlgn), qui est une connaissance fondamentale. Fusionner La complexité de l'espace de tri sera toujours O(n), y compris avec les tableaux. Si vous dessinez l’arborescence de l’espace, vous aurez l’impression que la complexité de l’espace est O (nlgn). Toutefois, comme le code est un code Depth First, vous ne développerez toujours que le long d'une branche de l'arborescence. Par conséquent, l'utilisation totale de l'espace requis sera toujours délimitée par O(3n) = O (n). 

Par exemple, si vous dessinez l’arbre spatial, il semble que c’est O (nlgn)

                             16                                 | 16
                            /  \                              
                           /    \
                          /      \
                         /        \
                        8          8                            | 16
                       / \        / \
                      /   \      /   \
                     4     4    4     4                         | 16
                    / \   / \  / \   / \
                   2   2 2   2.....................             | 16
                  / \  /\ ........................
                 1  1  1 1 1 1 1 1 1 1 1 1 1 1 1 1              | 16

où hauteur de l'arbre est O(logn) => La complexité de l'espace est O (nlogn + n) = O (nlogn). Cependant, ce n'est pas le cas dans le code actuel car il ne s'exécute pas en parallèle. Par exemple, dans le cas où N = 16, il s'agit de l'exécution du code de mergesort. N = 16. 

                           16
                          /
                         8
                        /
                       4
                     /
                    2
                   / \
                  1   1

remarquez comment le nombre d’espace utilisé est 32 = 2n = 2 * 16 <3n

Puis il fusionne vers le haut 

                           16
                          /
                         8
                        /
                       4
                     /  \
                    2    2
                        / \                
                       1   1

qui est 34 <3n . Puis il fusionne vers le haut

                           16
                          /
                         8
                        / \
                       4   4
                          /
                         2
                        / \ 
                       1   1

36 <16 * 3 = 48 

alors il fusionne vers le haut 

                           16
                          / \
                         8  8
                           / \
                          4   4
                             / \
                            2   2
                                /\
                               1  1

16 + 16 + 14 = 46 <3 * n = 48 

dans un cas plus grand, n = 64 

                     64
                    /  \
                   32  32
                       / \
                      16  16
                          / \
                         8  8
                           / \
                          4   4
                             / \
                            2   2
                                /\
                               1  1

qui est 64 * 3 <= 3 * n = 3 * 64 

Vous pouvez le prouver par induction pour le cas général. 

Par conséquent, la complexité de l'espace est toujours limitée par O(3n) = O(n), même si vous l'implémentez avec des tableaux tant que vous nettoyez l'espace utilisé après la fusion et que vous n'exécutez pas de code en parallèle mais de manière séquentielle. 

Exemple de ma mise en œuvre est donnée ci-dessous: 

templace<class X> 
void mergesort(X a[], int n) // X is a type using templates
{
    if (n==1)
    {
        return;
    }
    int q, p;
    q = n/2;
    p = n/2;
    //if(n % 2 == 1) p++; // increment by 1
    if(n & 0x1) p++; // increment by 1
        // note: doing and operator is much faster in hardware than calculating the mod (%)
    X b[q];

    int i = 0;
    for (i = 0; i < q; i++)
    {
        b[i] = a[i];
    }
    mergesort(b, i);
    // do mergesort here to save space
    // http://stackoverflow.com/questions/10342890/merge-sort-time-and-space-complexity/28641693#28641693
    // After returning from previous mergesort only do you create the next array.
    X c[p];
    int k = 0;
    for (int j = q; j < n; j++)
    {
        c[k] = a[j];
        k++;
    }
    mergesort(c, k);
    int r, s, t;
    t = 0; r = 0; s = 0;
    while( (r!= q) && (s != p))
    {
        if (b[r] <= c[s])
        {
            a[t] = b[r];
            r++;
        }
        else
        {
            a[t] = c[s];
            s++;
        }
        t++;
    }
    if (r==q)
    {
        while(s!=p)
        {
            a[t] = c[s];
            s++;
            t++;
        }
    }
    else
    {
        while(r != q)
        {
            a[t] = b[r];
            r++;
            t++;
        }
    }
    return;
}

J'espère que cela aide! =)

Bientôt Chee Loong, 

Université de Toronto 

49
Chee Loong Soon

a) Oui, bien sûr, la mise en parallèle du tri par fusion peut être très bénéfique. Cela reste inconnu, mais votre constante devrait être nettement inférieure.

b) La complexité de l'espace avec une liste chaînée devrait être O (n), ou plus précisément O(n) + O (logn). Notez que c'est un +, pas un *. Ne vous préoccupez pas beaucoup des constantes lorsque vous effectuez une analyse asymptotique.

c) Dans l'analyse asymptotique, seul le terme dominant dans l'équation compte beaucoup, alors le fait que nous ayons un + et non un * le rend O (n). Si nous dupliquions les sous-listes de part en part, je pense que ce serait un espace O(nlogn) - mais un type de fusion intelligent basé sur une liste chaînée peut partager des régions des listes.

1
user1277476

Performances de tri par fusion les plus défavorables: O (n log n) , Performances de tri par fusion dans le meilleur des cas: O (n log n) généralement, O(n) variante naturelle , Performances moyennes du tri par fusion: O (n log n) , Complexité de l'espace par rapport au tri par fusion: О (n) total, O(n) auxiliaire

1
Sourabh

Complexité de l'espace: Si les sous-tableaux/sous-listes sont créés à chaque niveau (logn levels * n espace requis à chaque niveau => logn * n) . Sinon, et si l'espace de pile est pris en compte, il sera connecté pour LinkedList et n (n + logn = n) pour Array . Complexité temporelle: nlogn pour les cas les plus graves et les plus graves

0
nikhil.singhal