【Structure de données et algorithme】 Tri rapide, valeur de référence aléatoire, ligne rapide à deux voies, ligne rapide à trois voies

Vous pouvez voir la démo d'animation de tri rapide dans la balise QUI à https://visualgo.net/zh/sorting.

Tri rapide

Complexité temporelle moyenne: O (nlogn), pire cas O (n ^ 2)
complexité spatiale: O (1)

Idée de base

Divisez et conquérez.

Recherchez un élément de référence et décomposez-le avec l'élément de référence. Le côté gauche est plus petit que l'élément de référence et le côté droit est plus grand que l'élément de référence.

Cela divise un tableau à trier en parties gauche et droite.
Suivez les étapes ci-dessus pour la gauche et la droite.

43 5 6 2 1
Nous avons choisi 4 comme valeur de référence.
1 3 245 6
Opérez sur [1 3 2] et [5 6].

Quick Row 1

Implémentation de base

// 返回 p, arr[l, p - 1] < arr[p]; arr[p + 1, r] > arr[p];
template<typename T>
int __partition(T arr[], int l, int r) {
    T v = arr[l];  // 选取第一个元素为基准值

    int j = l;
    for (int i = l + 1; i <= r; ++i) {
        if (arr[i] < v) {
            swap(arr[++j], arr[i]);
        }
    }

    swap(arr[l], arr[j]);
    return j;
}

template<typename T>
void __quickSort(T arr[], int l, int r) {
    if (l >= r) return;

	// 经过 __partition 后,[l, p-1] < arr[p],[p + 1, r] > arr[p]
    T p = __partition(arr, l, r);      
    __quickSort(arr, l, p - 1);
    __quickSort(arr, p + 1, r);
}

template<typename T>
void quickSort(T arr[], int n) {
    __quickSort(arr, 0, n - 1);
}

10 000 nombres aléatoires [0, 10 000] Trier:

归并排序        :       0.001776 s
快速排序        :       0.001609 s

Le tri par fusion et le tri rapide sont tous des ordres de grandeur O (nlogn).

Optimisation

Valeur de référence aléatoire

L'implémentation ci-dessus est problématique. Si le tableau à trier est proche de l'ordre, nous choisissons la première valeur comme valeur de référence et la divisons en deux parties, ce qui entraînera une partie particulièrement importante. Peut être vu par le test suivant:

10k nombres presque ordonnés [0, 10k] Trier:

归并排序        :       0.004252 s
快速排序        :       2.7637 s

À ce moment, la ligne rapide dégénère au niveau O (n ^ 2).
Quick Row 2
En examinant la raison, il a été constaté que c'était le problème du choix du numéro de référence.
Alors, comment choisir ce numéro de référence?

Choisissez au hasard un nombre comme valeur de référence

Sélectionnez un nombre au hasard pour la première fois, la probabilité qu'il soit le plus petit est 1 / n; la
deuxième fois est 1 / (n-1);

De cette façon, chaque fois qu'un petit nombre est sélectionné 1/n * 1/(n-1) * 1/(n-2) * .... Est une très faible probabilité.

template<typename T>
int __partition(T arr[], int l, int r) {
    swap(arr[l], arr[rand() % (r-l+1) + l]);
    T v = arr[l];

    // arr[l + 1, j] < v; arr[j + 1, i] > v;
    int j = l;
    for (int i = l + 1; i <= r; ++i) {
        if (arr[i] < v) {
            swap(arr[++j], arr[i]);
        }
    }

    swap(arr[l], arr[j]);
    return j;
}

// arr[l, r]
template<typename T>
void __quickSort(T arr[], int l, int r) {
    if (l >= r) return;
    srand(time(NULL));

    T p = __partition(arr, l, r);
    __quickSort(arr, l, p - 1);
    __quickSort(arr, p + 1, r);
}

Rangée rapide bidirectionnelle

Après l'optimisation 1 ci-dessus, un tri rapide est déjà suffisant pour le tri dans certains scénarios.
Cependant, l'examen du problème doit être global.
Dans l'optimisation 1 ci-dessus, les données sont divisées en deux parties. Par défaut, la valeur égale à la valeur de référence est divisée en deux. Si le tableau à trier est très égal à la valeur de référence, par exemple, 100 000 nombres sont [0, 5 ] Nombre, il y aura des inégalités à gauche et à droite, ce qui entraînera de nombreux côtés et peu de côtés, de sorte que la vitesse de la rangée rapide sera également dégradée.

100 000 nombres aléatoires, plage [0, 5]:

归并排序	:	0.015571 s
快速排序	:	2.00576 s

Distribution inégale

Comment le résoudre?
Ligne rapide bidirectionnelle!

Le nombre égal à la valeur de référence est également réparti des deux côtés.

template<typename T>
int __partition2(T arr[], int l, int r) {
    swap(arr[l], arr[rand() % (r - l + 1) + l]);
    T v = arr[l];

    int i = l + 1, j = r;
    while (true) {
        while (i <= r && arr[i] < v) i++;
        while (j >= l + 1 && arr[j] > v) j--;
        if (i > j) break;
        swap(arr[i++], arr[j--]);
    }

    swap(arr[l], arr[j]);
    return j;
}

Ligne rapide bidirectionnelle, 100 000 nombres aléatoires, plage [0, 5]:

归并排序	:	0.015794 s
快速排序	:	0.026956 s

On peut voir que le tri et la fusion rapides dans les deux sens sont déjà dans un ordre de grandeur.

Ligne rapide à trois voies

Grâce à la sélection aléatoire des valeurs de référence ci-dessus et à la mise en file d'attente rapide bidirectionnelle, certains problèmes ont été résolus, mais ce n'est pas encore optimal.

100 000 nombres aléatoires dans la plage [0, 5]. Bien que le tri rapide bidirectionnel soit essentiellement divisé en deux parties, mais pour un grand nombre du même nombre que la valeur de référence, de nombreuses opérations répétées ont été effectuées.

Le tri rapide à trois voies consiste à diviser le tableau à trier en trois parties: [inférieur à la valeur de référence, égal à la valeur de référence et supérieur à la valeur de référence]. Le prochain cycle de tri n'a besoin que de trier les deux parties inférieures à la valeur de référence et supérieures à la valeur de référence. Une grande partie de l'opération est omise.

template <typename T>
void __quickSort3Ways(T arr[], int l, int r) {
    if (l >= r) return ;

    srand(time(NULL));
    swap(arr[l], arr[rand() % (r - l + 1) + l]);
    T v = arr[l];

    // partition
    int i = l + 1;
    int lt = l;
    int gt = r + 1;
    while (i < gt) {
        if (arr[i] < v) {
            swap(arr[++lt], arr[i++]);
        } else if (arr[i] > v) {
            swap(arr[i], arr[--gt]);
        } else {
            ++i;
        }
    }
    swap(arr[l], arr[lt]);

    __quickSort3Ways(arr, l, lt - 1);
    __quickSort3Ways(arr, gt, r);
}

template<typename T>
void quickSort3Ways(T arr[], int n) {
    __quickSort3Ways(arr, 0, n - 1);
}

File d'attente rapide à trois voies, 100 000 nombres aléatoires, plage [0, 5]:

归并排序	:	0.015375 s
双路快速排序	:	0.026594 s
三路快速排序	:	0.00316 s

Vous pouvez voir qu'il y a une amélioration significative des performances de la ligne rapide à trois voies!

Test de performance

100 000 nombres aléatoires, plage [0, 100 000]:

归并排序	:	0.022674 s
双路快速排序	:	0.03437 s
三路快速排序	:	0.037351 s

100 000 nombres aléatoires, plage [0, 5]:

归并排序	:	0.015524 s
双路快速排序	:	0.026816 s
三路快速排序	:	0.002967 s

100k nombres presque ordonnés, plage [0, 100k]:

归并排序	:	0.003909 s
双路快速排序	:	0.020788 s
三路快速排序	:	0.036348 s

EOF

98 articles originaux ont été publiés · 91 éloges · 40 000+ vues

Je suppose que tu aimes

Origine blog.csdn.net/Hanoi_ahoj/article/details/105495178
conseillé
Classement