Perché la stabilità è considerata un tratto desiderabile di un algoritmo di ordinamento?

1

L'argomento comune per la stabilità in un algoritmo di ordinamento in genere implica un esempio in cui una lista è ordinata secondo due criteri. Ad esempio:

1,4,5,7,2,6,8,9,15,65,24,27
sort by evenness/oddness and then by value
2,4,6,8,24,1,5,7,9,15,27,65

L'affermazione è che, scegliendo un algoritmo di ordinamento stabile, puoi ordinare questo elenco due volte - in base al valore e poi alla uniformità - e avrai quindi la lista ordinata come volevi.

Non potrei essere più in disaccordo con questa ideologia, però. Prima di tutto, l'ordinamento è fatto all'indietro (valore, uniformità, in cui l'uniformità è il criterio principale), che non è intuitivo. In secondo luogo, facendo ciò, si chiama sort () due volte .

Ora diamo un'occhiata ad alcuni documenti. Abbiamo C qsort (3) e JavaScript Array.prototype.sort . Entrambe queste funzioni, per quanto ne so, implementano algoritmi di ordinamento instabili ...

If two members compare as equal, their order in the sorted array is undefined.

e

If compareFunction(a, b) returns 0, leave a and b unchanged with respect to each other, but sorted with respect to all different elements. Note: the ECMAscript standard does not guarantee this behaviour, and thus not all browsers (e.g. Mozilla versions dating back to at least 2003) respect this.

... ed entrambi accettano una funzione come argomento. Questa funzione è ciò che io considero un comparatore: una funzione che prende due valori A e B e restituisce -1, 0 o 1 a seconda che A sia considerato rispettivamente "minore di", "uguale a" o "maggiore" di "B, in base a qualsiasi criterio arbitrario scelto dall'implementatore.

Detto questo, quello che ho trovato è che, indipendentemente da ciò che lancio alle rispettive funzioni di ordinamento, indipendentemente dal fatto che li implemento da me o che usi quello della libreria standard, è che la stabilità non ha assolutamente alcuna influenza sul risultato del ordina quando la funzione di ordinamento viene utilizzata correttamente.

Usiamo il qsort di C come esempio. qsort implementa l'ordinamento rapido ed è noto per essere instabile.

If two members compare as equal, their order in the sorted array is undefined.

Per chiarire, questo non significa che l'implementazione sia per sé instabile. Ciò che significa è che la stabilità è non garantita , quindi fare affidamento su quella semantica è una pessima idea. Che è abbastanza vicino.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define INT(p) \
    ( *((int *)(p)) )

#define ISEVEN(p) \
    (INT(p) % 2 == 0)

void
randomize(int *list, size_t len)
{
    for(size_t i = 0; i < len; ++i)
        list[i] = rand() % (len * 10);
}

void
printlist(int *list, size_t len)
{
    for(size_t i = 0; i < len; ++i)
        printf("%i, ", list[i]);

    putchar('\n');
}

int
by_even(void const *a, void const *b)
{
    return (ISEVEN(a) && !ISEVEN(b)) ? (-1) : (ISEVEN(b) && !ISEVEN(a));
}

int
by_value(void const *a, void const *b)
{
    return (INT(a) < INT(b)) ? (-1) : (INT(a) > INT(b));
}

int
by_even_and_value(void const *a, void const *b)
{
    return by_even(a, b) != 0 ? by_even(a, b) : by_value(a, b);
}

int
main(void)
{
    static size_t const listsz = 20;
    int list[listsz];

    srand(time(NULL));
    randomize(list, listsz);
    printlist(list, listsz);
    qsort(list, listsz, sizeof list[0], by_even_and_value);
    printlist(list, listsz);

    return 0;
}

E questo è l'output:

$ cc qsort.c
$ ./a.out
100, 111, 12, 122, 96, 50, 52, 96, 173, 125, 135, 173, 78, 144, 108, 60, 75, 116, 24, 180,
12, 24, 50, 52, 60, 78, 96, 96, 100, 108, 116, 122, 144, 180, 75, 111, 125, 135, 173, 173,

Quindi, inserendo tutti i criteri di ordinamento all'interno del comparatore e ordinando una volta mi ha dato la lista ordinata che volevo. Mi è bastato un solo ordinamento e il criterio poteva essere dato in ordine (anche primo, secondo valore).

Poiché ciò rende la stabilità apparentemente irrilevante per il risultato, perché dovremmo preoccuparci della stabilità di un algoritmo di ordinamento?

    
posta Braden Best 04.12.2018 - 07:29
fonte

2 risposte

8

The common argument for stability in a sorting algorithm typically involves an example where a list is sorted by two criteria. For example:

1,4,5,7,2,6,8,9,15,65,24,27
sort by evenness/oddness and then by value
2,4,6,8,24,1,5,7,9,15,27,65

The claim is that by choosing a stable sorting algorithm, you can sort this list twice--by value and then by evenness--and you will then have the list sorted as you had wanted.

Questo esempio sembra aver seriamente fuorviato per quanto riguarda ciò che "ordinamento stabile" implica.

Un esempio migliore potrebbe essere

Given a numerically ordered list of numbers: 1,2,4,5,6,7,8,9,15,24,27,65
When you sort this list by evenness/oddness with a stable sorting algorithm, then the sub-list of even and odd numbers will still be numerically ordered: 2,4,6,8,24,1,5,7,9,15,27,65.

Un algoritmo di ordinamento stabile non implica (o richiede) che la funzione sort() venga chiamata due volte. Ottenere l'input numerico ordinato non fa parte dell'algoritmo di ordinamento stabile, ma piuttosto uno strumento per mostrare la proprietà di stabilità dell'algoritmo di ordinamento.

Un algoritmo di ordinamento stabile afferma solo che gli elementi uguali secondo il comparatore sono mantenuti nello stesso ordine relativo dell'input. Questo può essere mostrato con input pre-ordinati secondo un diverso criterio o con strutture di dati più complesse dove non tutti i campi contribuiscono all'ordinamento, ma quelle strutture di dati più complesse rendono una presentazione più difficile in un esempio.

In pratica, un algoritmo di ordinamento stabile è molto utile quando è necessario riordinare i dati relativi all'utente, poiché la maggior parte degli utenti finali si aspetta questo tipo di comportamento.

    
risposta data 04.12.2018 - 09:19
fonte
6

Second of all, by doing this, you are calling sort() twice.

Non sono sicuro che questa sia la risposta più completa, ma a volte è utile a sort più volte in passaggi separati per i requisiti di fine utente. Potrebbe sembrare computazionalmente inefficiente, ma a volte ha senso dal punto di vista dell'utente.

Un esempio comune è quando vedi le GUI disposte come una griglia / tabella con colonne su cui puoi fare clic per ordinare i dati in base a un campo particolare, come questo:

Inquesticasinonènecessariamentecosìpraticocheall'utentevengarichiestodispecificarel'ordineprecisodipiùcolonnedaordinarecomechiaviesottochiaviesottocategorieecosìviaconunsingolopassaggiodiordinamentoutilizzandouncomparatorecheconfrontapiùcampicontemporaneamenteperprodurrel'ordinamentoeilsotto-ordinedesideratoecosìvia(ocheilsoftwarecerchidiricordarelecolonneprecedentisucuièstatofattoclicpergenerareilcomparatoreappropriato).Inquestocasopuòesseremoltopiùsempliceeseguireunpassaggiodiordinamentoseparatoconogniclicdiunacolonnaeutilizzareunordinamentostabileperminimizzarel'ordinerelativodegliordinamentiprecedentirichiestidall'utentefacendoclic.

Tendoapensareallaspeciestabilecomeauntipo"meno dirompente" che è spesso un po 'più costoso da calcolare, ma ha il vantaggio di mantenere un ordinamento relativo dei dati originali. E a volte è solo molto pratico in codice, almeno per farlo in passaggi separati invece di un passaggio di ordinamento uber con il comparatore appropriato che dà esattamente i risultati desiderati in una volta.

Quindi questo è un esempio in cui potrebbe essere più utile un ordinamento solo stabile per fornire risultati più prevedibili e minimamente disruptive rispetto a cercare di fare tutto in un unico passaggio di ordinamento solo per la natura del design user-end.

    
risposta data 04.12.2018 - 08:07
fonte

Leggi altre domande sui tag