Esiste un algoritmo subcubico per il seguente problema?


11

Data una matrice simmetrica reale , esiste un algoritmo che calcola la somma nel complesso 1 \ leq i <j <k \ leq n con complessità temporale migliore di O (n ^ 3) ?n×nA=(aij)

i,j,kmax(aij,aik,ajk)
1i<j<knO(n3)

3
Nota che questo è difficile almeno quanto contare il numero di triangoli in un dato grafico. Se la tua matrice di input codifica un grafico in modo tale che "0" indica un bordo e "1" indica un bordo mancante, allora max(aij,aik,ajk)=0 se e solo se lì è un triangolo formato dai nodi i , j e k , ed altrimenti è 1 .
Jukka Suomela,

1
Penso che gli unici algoritmi significativamente subcubici noti per il conteggio dei triangoli siano basati sulla moltiplicazione di matrici veloci? Potrebbe essere difficile applicare queste tecniche qui in questo problema. Inoltre, se stai cercando qualcosa di pratico, qualsiasi cosa basata sulla moltiplicazione a matrice rapida non sarà utile.
Jukka Suomela,

Risposte:


3

Esiste un approccio abbastanza pratico che funziona nel tempo , dove è il numero di bit nella parola del processore. L'idea principale è che si ripetano gli elementi della matrice uno alla volta in ordine crescente (rompendo i legami arbitrariamente) e "accendendoli". Considera il momento in cui l'elemento più grande di qualche tripla è attivato. Per semplicità, supponiamo che l'elemento detto sia . È naturale aggiungere il valore del triplo alla risposta ora, quando l'ultimo elemento è acceso. Quindi dobbiamo contare il numero di possibili tale che eO(n3/w)waij,aik,ajkaijkaikajksono già accesi (che sarebbe il numero di triple, qui è l'elemento più grande, quindi sono stati completamente accesi proprio ora). Qui possiamo accelerare l' implementazione ingenua utilizzando l'ottimizzazione dei bit.aijO(n)

Per i dettagli è possibile fare riferimento alla seguente implementazione in C ++ 11 che dovrebbe funzionare per , (non è molto ottimizzato; tuttavia, batte comunque la somma ingenua per con un ampio margine almeno sulla mia macchina).n5000|aij|109n=5000

// code is not very elegant, 
// but should be understandable
// here the matrix a has dimensions n x n
// a has to be symmetric!
int64_t solve (int n, const vector<vector<int32_t>> &a)
{
        std::vector<boost::dynamic_bitset<int64_t>> mat
        (n, boost::dynamic_bitset<int64_t>(n));

        vector<pair<int, int>> order;
        for (int j = 1; j < n; j++)
        for (int i = 0; i < j; i++)
            order.emplace_back(i, j);
        sort(order.begin(), order.end(),
            [&] (const pair<int, int> &l, const pair<int, int> &r) 
            {return a[l.first][l.second] < a[r.first][r.second];});

        int64_t ans = 0;
        for (const auto &position : order)
        {
            int i, j;
            tie (i, j) = position;
            mat[i][j] = mat[j][i] = 1;
            // here it is important that conditions 
            // mat[i][i] = 0 and mat[j][j] = 0 always hold
            ans += (mat[i] & mat[j]).count() * int64_t(a[i][j]);
        }

        return ans;
}

Se consideri l'utilizzo di trucchi per l'ottimizzazione dei bit, puoi usare quattro metodi russi per lo stesso risultato qui, producendo un algoritmo , che dovrebbe essere meno pratico (perché è piuttosto grande sulla maggior parte dell'hardware moderno) ma è teoricamente migliore. Effettivamente, scegliamo e manteniamo ogni riga della matrice come una matrice di interi da a , dove l' -numero in l'array corrisponde ai bit della riga che vanno da inclusivo a esclusivo inO(n3/logn)wblog2nnb02b1iibmin(n,(i+1)b)0-indexation. Possiamo precalcolare i prodotti scalari di ogni due blocchi di questo tipo nel tempo . L'aggiornamento di una posizione nella matrice è veloce perché stiamo cambiando solo un numero intero. Per trovare il prodotto scalare delle righe e basta scorrere le matrici corrispondenti a quelle righe, cercare i prodotti scalari dei blocchi corrispondenti nella tabella e riassumere i prodotti ottenuti.O(22bb)ij

Il paragrafo precedente presuppone che le operazioni con numeri interi richiedano il tempo . È un'ipotesi abbastanza comune , perché di solito non cambia effettivamente la velocità comparativa degli algoritmi (ad esempio, se non usiamo tale ipotesi, il metodo della forza bruta funziona effettivamente nel tempo (qui misuriamo il tempo in operazioni a bit) se accetta valori interi con valori assoluti almeno fino a per alcune costanti (e altrimenti possiamo risolvere il problema con moltiplicazioni di matrice comunque); tuttavia il metodo dei quattro russi suggerito sopra utilizzanO(1)O(n3logn)aijnεε>0O(nε)O(n3/logn) operazioni con numeri di dimensione in quel caso; quindi esegue operazioni di bit , che è comunque migliore della forza bruta nonostante il cambiamento del modello).O(logn)O(n3)

La domanda sull'esistenza dell'approccio è comunque interessante.O(n3ε)

Le tecniche (ottimizzazioni di bit e metodo dei quattro russi) presentate in questa risposta non sono affatto originali e sono presentate qui per completezza dell'esposizione. Tuttavia, trovare un modo per applicarli non era banale.


In primo luogo, il tuo suggerimento sembra davvero essere utile in termini pratici, potrei semplicemente provarlo nel mio caso d'uso. Grazie! In secondo luogo, la complessità computazionale degli algoritmi è ancora per qualsiasi tipo numerico a larghezza fissa. Potresti approfondire l' approccio ? Non capisco come potremmo trovare il prodotto scalare e più veloce di (che sarebbe richiesto se accedessimo a tutti i loro elementi). O(n3)O(n3/logn)mat[i]mat[j]O(n)
user89217

Inoltre, il tuo codice non definisce matquale sembra essere importante. Capisco come potrebbe essere definito, ma mi chiedo se (mat[i] & mat[j]).count()funzionerebbe come desiderato con qualsiasi contenitore STL.
user89217

1
Per quanto riguarda mat- immagino che dobbiamo usare std::vector<boost::dynamic_bitset<int64_t>>.
user89217

Per quanto riguarda mat: sì, avevo in mente un bitset standard, ma boost::dynamic_bitsetin questo caso è ancora meglio, perché le sue dimensioni non devono essere costanti in fase di compilazione. Modifica la risposta per aggiungere questo dettaglio e chiarire l'approccio dei quattro russi.
Kaban, 5

1
Fantastico, questo mi sembra solido. Un punto minore: poiché il modello transdicotomo presuppone che possiamo eseguire operazioni su parole macchina in , non è necessario precalulare alcun prodotto scalare. In effetti, il modello presuppone che , quindi sia almeno buono come . E, come dici tu, precalcolare i prodotti scalari non ha alcun senso pratico (una ricerca di array sarà più lenta dell'operazione binaria). O(1)wlog2nO(n3/w)O(n3/logn)
user89217
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.