¿Existe un algoritmo subcúbico para el siguiente problema?

11

Dada una matriz simétrica real , ¿existe un algoritmo que calcule la suma sobre todo 1 \ leq i <j <k \ leq n con complejidad de tiempo mejor que O (n ^ 3) ?n×nA=(aij)

i,j,kmax(aij,aik,ajk)
1i<j<knO(n3)
usuario89217
fuente
3
Tenga en cuenta que esto es al menos tan difícil como contar el número de triángulos en un gráfico dado. Si su matriz de entrada codifica un gráfico tal que "0" indica un borde y "1" indica un borde faltante, entonces max(aij,aik,ajk)=0 si y solo si es un triángulo formado por los nodos i , j y k , y de lo contrario es 1 .
Jukka Suomela
1
¿Creo que los únicos algoritmos significativamente subcúbicos conocidos para el recuento de triángulos se basan en la multiplicación rápida de matrices? Puede ser complicado aplicar esas técnicas aquí en este problema. Además, si está buscando algo práctico, cualquier cosa basada en la multiplicación rápida de matrices no será útil.
Jukka Suomela

Respuestas:

3

Existe un enfoque bastante práctico que funciona en tiempo , donde es el número de bits en la palabra del procesador. La idea principal es iterar sobre los elementos de la matriz uno por uno en orden creciente (romper los lazos arbitrariamente) y "encenderlos". Considere el momento en que el elemento más grande de algunos triples está encendido. Por simplicidad, supongamos que dicho elemento es . Es natural agregar el valor del triple a la respuesta ahora, cuando se enciende el último elemento. Entonces, tenemos que contar el número de posibles 's de modo que yO(n3/w)waij,aik,ajkaijkaikajkya están encendidos (ese sería el número de triples, aquí es el elemento más grande, por lo que se encendieron por completo en este momento). Aquí podemos acelerar la implementación ingenua de utilizando la optimización de bits.aijO(n)

Para obtener detalles, puede consultar la siguiente implementación en C ++ 11 que debería funcionar para , (no está muy optimizado; sin embargo, aún supera la suma ingenua para por un amplio margen al menos en mi máquina).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 considera el uso de trampas de optimizaciones de bits, puede usar el método de cuatro rusos para obtener el mismo resultado aquí, produciendo un algoritmo , que debería ser menos práctico (porque es bastante grande en la mayoría del hardware moderno) pero es teóricamente mejor. De hecho, elijamos y mantengamos cada fila de la matriz como una matriz de enteros de a , donde el -ésimo número en la matriz corresponde a los bits de la fila que van desde inclusive hasta exclusivo enO(n3/logn)wblog2nnb02b1iibmin(n,(i+1)b)0-indexación. Podemos precalcular los productos escalares de cada dos de estos bloques en el tiempo . Actualizar una posición en la matriz es rápido porque estamos cambiando solo un número entero. Para encontrar el producto escalar de las filas y simplemente repita los arreglos correspondientes a esas filas, busque productos escalares de los bloques correspondientes en la tabla y resuma los productos obtenidos.O(22bb)ij

El párrafo anterior supone que las operaciones con números enteros toman tiempo. Es una suposición bastante común , porque generalmente no cambia la velocidad comparativa de los algoritmos (por ejemplo, si no usamos esa suposición, el método de fuerza bruta realmente funciona en tiempo (aquí medimos el tiempo en operaciones de bit) si toma valores enteros con valores absolutos al menos hasta para alguna constante (y de lo contrario podemos resolver el problema con multiplicaciones matriciales de todos modos); sin embargo, el método de los cuatro rusos sugerido anteriormente usanO(1)O(n3logn)aijnεε>0O(nε)O(n3/logn) operaciones con números de tamaño en ese caso; por lo que realiza operaciones de bit , que aún es mejor que la fuerza bruta a pesar del cambio del modelo).O(logn)O(n3)

Sin embargo, la pregunta sobre la existencia del enfoque sigue siendo interesante.O(n3ε)

Las técnicas (optimizaciones de bits y método de cuatro rusos) presentadas en esta respuesta no son en absoluto originales y se presentan aquí para completar la exposición. Sin embargo, encontrar una manera de aplicarlos no fue trivial.

Kaban-5
fuente
En primer lugar, su sugerencia parece ser útil en términos prácticos, podría probarla en mi caso de uso. ¡Gracias! En segundo lugar, la complejidad computacional de sus algoritmos sigue siendo para cualquier tipo numérico de ancho fijo. ¿Podría dar más detalles sobre el enfoque ? No entiendo cómo podríamos encontrar el producto escalar y más rápido que (que sería necesario si accedemos a todos sus elementos). O(n3)O(n3/logn)mat[i]mat[j]O(n)
user89217
Además, su código no define matcuál parece ser importante. Entiendo cómo se podría definir, pero me pregunto si (mat[i] & mat[j]).count()funcionaría como se desea con cualquier contenedor STL.
user89217
1
En cuanto a mat- Supongo que debemos usar std::vector<boost::dynamic_bitset<int64_t>>.
user89217
En cuanto a mat: sí, tenía en mente un bitset estándar, pero boost::dynamic_bitseten este caso es aún mejor, porque su tamaño no tiene que ser constante en tiempo de compilación. Editará la respuesta para agregar este detalle y aclarar el enfoque de los cuatro rusos.
Kaban-5
1
Genial, esto me parece sólido. Un punto menor: dado que el modelo transdicotómico asume que podemos realizar operaciones con palabras de máquina en , no hay necesidad de calcular previamente ningún producto escalar. De hecho, el modelo supone que , por lo que es al menos tan bueno como . Y, como usted dice, precalcular productos escalares no tiene ningún sentido práctico (una búsqueda de matriz será más lenta que la operación binaria). O(1)wlog2nO(n3/w)O(n3/logn)
user89217