Existe-t-il un algorithme sous-cubique pour le problème suivant?

11

n×nA=(aij)

i,j,kmax(aij,aik,ajk)
1i<j<knO(n3)
user89217
la source
3
Notez que c'est au moins aussi difficile que de compter le nombre de triangles dans un graphique donné. Si votre matrice d'entrée code pour un graphe tel que "0" indique un bord et "1" indique un bord manquant, alors si et seulement s'il y a est un triangle formé par les nœuds , et , et sinon c'est . max(aij,aik,ajk)=0ijk1
Jukka Suomela
1
Je pense que les seuls algorithmes significativement sous-cubiques connus pour le comptage de triangles sont basés sur une multiplication matricielle rapide? Il peut être difficile d'appliquer ces techniques ici dans ce problème. De plus, si vous cherchez quelque chose de pratique, tout ce qui est basé sur une multiplication matricielle rapide ne sera pas utile.
Jukka Suomela

Réponses:

3

Il existe une approche assez pratique qui fonctionne en temps , où est le nombre de bits dans le mot processeur. L'idée principale est de parcourir les éléments de la matrice un par un dans l'ordre croissant (rompre arbitrairement les liens) et de les "allumer". Considérons le moment où le plus grand élément de certains triples est activé. Pour simplifier, supposons que ledit élément soit . Il est naturel d'ajouter la valeur du triple à la réponse maintenant, lorsque le dernier élément est allumé. Nous devons donc compter le nombre de possibles tels que etO(n3/w)waij,aik,ajkaijkaikajksont déjà allumés (ce serait le nombre de triplets, ici est le plus grand élément, donc ils ont été complètement allumés tout à l'heure). Ici, nous pouvons accélérer la mise en œuvre naïve en utilisant l'optimisation des bits.aijO(n)

Pour plus de détails, vous pouvez vous référer à l'implémentation suivante en C ++ 11 qui devrait fonctionner pour , (il n'est pas très optimisé; cependant, il bat toujours la sommation naïve pour de grande marge au moins sur ma machine).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;
}

Si vous envisagez d'utiliser des optimisations de bits pour tricher, vous pouvez utiliser quatre méthodes russes pour le même résultat ici, produisant un algorithme , qui devrait être moins pratique (car est assez grand sur la plupart des matériels modernes) mais c'est théoriquement mieux. En effet, choisissons et gardons chaque ligne de la matrice sous la forme d'un tableau d' de à , où le -ième nombre dans le tableau correspond aux bits de la ligne allant de inclus à exclusif enO(n3/logn)wblog2nnb02b1iibmin(n,(i+1)b)0-indexation. Nous pouvons précalculer les produits scalaires de deux de ces blocs en temps . La mise à jour d'une position dans la matrice est rapide car nous ne modifions qu'un seul entier. Pour trouver le produit scalaire des lignes et parcourez simplement les tableaux correspondant à ces lignes, recherchez les produits scalaires des blocs correspondants dans le tableau et résumez les produits obtenus.O(22bb)ij

Le paragraphe ci-dessus suppose que les opérations avec des entiers prennent du temps . Cette hypothèse est assez courante , car elle ne modifie généralement pas la vitesse comparative des algorithmes (par exemple, si nous n'utilisons pas cette hypothèse, la méthode de la force brute fonctionne en fait en temps (ici nous mesurons le temps en opérations binaires) si prend des valeurs entières avec des valeurs absolues au moins jusqu'à pour une constante (et sinon nous pouvons résoudre le problème avec multiplications de la matrice de toute façon); cependant la méthode des quatre russes suggérée ci-dessus utilisenO(1)O(n3logn)aijnεε>0O(nε)O(n3/logn) opérations avec des nombres de taille dans ce cas; il fait donc des opérations sur bits, ce qui est encore mieux que la force brute malgré le changement de modèle).O(logn)O(n3)

La question de l'existence de l' approche reste cependant intéressante.O(n3ε)

Les techniques (optimisations de bits et méthode des quatre russes) présentées dans cette réponse ne sont en aucun cas originales et sont présentées ici pour compléter l'exposé. Cependant, trouver un moyen de les appliquer n'était pas anodin.

Kaban-5
la source
Premièrement, votre suggestion semble effectivement utile en termes pratiques, je pourrais simplement l'essayer dans mon cas d'utilisation. Merci! Deuxièmement, la complexité de calcul de vos algorithmes est toujours pour tout type numérique à largeur fixe. Pourriez-vous élaborer sur l' approche ? Je ne comprends pas comment nous pourrions trouver le produit scalaire de et plus rapidement que (ce qui serait nécessaire si nous accédions à tous leurs éléments). O(n3)O(n3/logn)mat[i]mat[j]O(n)
user89217
De plus, votre code ne définit pas matce qui semble être important. Je comprends comment cela pourrait être défini, mais je me demande si (mat[i] & mat[j]).count()cela fonctionnerait comme souhaité avec n'importe quel conteneur STL.
user89217
1
Concernant mat- je suppose que nous devons utiliser std::vector<boost::dynamic_bitset<int64_t>>.
user89217
Concernant mat: oui, je pensais au bitet standard en fait, mais boost::dynamic_bitsetc'est encore mieux dans ce cas, car sa taille n'a pas à être constante au moment de la compilation. Éditera la réponse pour ajouter ce détail et clarifier l'approche des quatre russes.
Kaban-5
1
Super, cela me semble solide. Un point mineur: puisque le modèle transdichotomique suppose que nous pouvons effectuer des opérations sur des mots machine dans , il n'est pas nécessaire de précalculer des produits scalaires. En fait, le modèle suppose que , donc est au moins aussi bon que . Et, comme vous le dites, le précalcul des produits scalaires n'a aucun sens pratique (une recherche de tableau sera plus lente que l'opération binaire). O(1)wlog2nO(n3/w)O(n3/logn)
user89217