17

Tengo una matriz muy grande (columnas de 100M por 100M) que tiene muchos valores duplicados uno al lado del otro. Por ejemplo:Cómo almacenar eficientemente una matriz con valores altamente redundantes

8 8 8 8 8 8 8 8 8 8 8 8 8 
8 4 8 8 1 1 1 1 1 8 8 8 8 
8 4 8 8 1 1 1 1 1 8 8 8 8 
8 4 8 8 1 1 1 1 1 8 8 8 8 
8 4 8 8 1 1 1 1 1 8 8 8 8 
8 4 8 8 1 1 1 1 1 8 8 8 8 
8 8 8 8 8 8 8 8 8 8 8 8 8 
8 8 3 3 3 3 3 3 3 3 3 3 3 

Quiero una estructura de datos/algoritmo para almacenar matrices como estas de la forma más compacta posible. Por ejemplo, la matriz de arriba solo debería tener O (1) espacio (incluso si la matriz se estiró arbitrariamente grande), porque solo hay un número constante de regiones rectangulares, donde cada región solo tiene un valor.

La repetición ocurre tanto en las filas como en las columnas hacia abajo, por lo que el método simple de comprimir la matriz fila por fila no es suficiente. (Eso requeriría un mínimo de O (num_rows) espacio para almacenar cualquier matriz.)

La representación de la matriz también debe ser accesible fila por fila, de modo que pueda hacer una multiplicación de matriz en un vector de columna.

+1

¿Para qué sirve esta aplicación? Nunca he visto matrices con este tipo de estructura utilizada antes con multiplicación de matrices. –

+1

La matriz es una matriz de pago para un juego de suma cero para 2 jugadores muy grande. La multiplicación es contra un vector que representa la estrategia mixta de un jugador (cada elemento es la probabilidad de que se use esta estrategia). –

+0

Su ejemplo anterior no dejó en claro que la matriz es escasa, pero noté en un comentario que hizo que está (99% vacío). Los algoritmos y las estructuras que mencioné lo almacenarán de manera eficiente. También es posible que desee utilizar el hecho de que una fila en la matriz A que comprende el mismo valor (x) simplifica el cálculo de AB a Sum (B) * x. Puede usar esto para reducir significativamente la cantidad de cálculos que necesita realizar (puede almacenar Sum (B)). –

Respuesta

13

Puede almacenar la matriz como quadtree con las hojas que contienen valores únicos. Piense en esto como una "corrida" bidimensional de valores.

+0

¿Los nodos se ajustan a la forma de los datos, o cada subdivisión siempre se divide en 4 partes iguales? –

+1

Si definió una nota de hoja como un valor único, entonces tendría que dividir para ajustar los datos. Pero cualquiera es posible. – Akusete

+3

Usted subdivide recursivamente cualquier matriz cuadrada en 4 subparálas, deteniéndose cuando una (sub) matriz contiene el mismo valor en todas partes. Una subdivisión cuádruple puede tener dos subcélulas con constantes diferentes que no se subdividen más, y dos subcélulas que se dividen más. Esto funciona porque, en el peor de los casos, puedes subdividir una matriz en celdas individuales, ¡y claramente forman una matriz de 1x1 que contiene solo un valor! El acceso a cualquier elemento de la matriz recorriendo recursivamente el quadtree es O (log (n)), y el tiempo promedio de acceso será considerablemente menor que el peor de los casos si su matriz es escasa. –

4

Si sus datos son realmente regulares, puede beneficiarse de almacenarlos en un formato estructurado; p.ej. su matriz de ejemplo podría ser almacenada como la siguiente lista de instrucciones "de relleno" rectángulo:

(0,0)-(13,7) = 8 
(4,1)-(8,5) = 1 

(continuación para buscar el valor de una celda en particular, que le iterar atrás por la lista hasta encontrar un rectángulo que contenía esa celda)

+0

Desafortunadamente no tan regular. En promedio, puede haber de 10 a 1000 valores duplicados uno al lado del otro. Pero la matriz también es muy escasa (el 99,99% de las celdas son cero), además de toda la redundancia. –

+0

@Dustin: puede usar [submatrices] (http://en.wikipedia.org/wiki/Submatrix), luego, para contener las áreas que (en su mayoría) son distintas de cero; incluso podría usar submatrices de esas submatrices, en una especie de estructura de árbol. (¿Hay un término para un árbol como ese?) Probablemente no sería tan eficiente como un quadtree, pero imagino que podrías encontrar algunos usos interesantes para él. – JAB

0

No tengo una respuesta específica para la matriz que ha mostrado. En el análisis de elementos finitos (FEA), tiene matrices con datos redundantes. Al implementar un paquete de FEA en mi proyecto de graduación, utilicé el método de almacenamiento de skyline.

Algunos enlaces:

Intel page for sparse matrix storage

Wikipedia link

1

El enfoque más simple es utilizar la codificación de longitud correr en una dimensión y no preocuparse por la otra dimensión.

(Si el conjunto de datos no fuera tan increíblemente grande, interpretarlo como una imagen y usar un método estándar de compresión de imágenes sin pérdida sería muy simple también, pero ya que tendría que trabajar para hacer que el algoritmo funcione en escaso matrices, no terminaría siendo tan simple.)

Otro enfoque simple es probar un relleno de inundación rectangular - comience en el píxel superior derecho y aumente en el rectángulo más grande que pueda (ancho primero); a continuación, marque todos esos píxeles como "hecho" y tome el píxel que queda más a la derecha, repita hasta que termine. (Probablemente desee almacenar estos rectángulos en algún tipo de BSP o árbol cuádruple.)

Una técnica altamente efectiva, no óptima, pero probablemente lo suficientemente buena, es usar un árbol de particiones de espacio binario donde " espacio "no se mide espacialmente sino por el número de cambios. Cortarías recursivamente para que tengas el mismo número de cambios a la izquierda y a la derecha (o arriba y abajo, presumiblemente, querrás mantener las cosas cuadradas) y, a medida que tus tamaños se reduzcan, para que puedas cortar el mayor número cambios como sea posible. Eventualmente, terminará cortando dos rectángulos el uno del otro, cada uno de los cuales tiene el mismo número; luego se detiene. (La codificación por RLE en xey le indicará rápidamente dónde están los puntos de cambio.)

+0

Como mencioné en la pregunta, hay tantas filas que incluso si hiciera un excelente trabajo comprimiendo cada fila, aún sería demasiado grande. –

+0

Para el enfoque de llenado por inundación, tendría que hacerlo incrementalmente de alguna manera (a medida que se genera la matriz). Según lo que describió, sonaba como si ya tuviera todos los píxeles en la memoria (lo que no sucede porque hay demasiados) –

+0

@Dustin - Puede hacer el relleno de inundación rectangular de forma incremental - haga la codificación RLE en x, luego expande en y si tienes dos segmentos de la misma longitud. Si puede almacenar filas 'k' codificadas en RLE, puede usarlas para hacer un relleno de inundación local. (Básicamente busque en y para bloques de la misma extensión x, y codifíquelos como un rectángulo cuando encuentre 2 en una fila o 3+ de k (donde los demás son demasiado largos y pueden cortarse).) –

0

Lo primero que debes probar es siempre las bibliotecas y soluciones existentes. Es mucho trabajo conseguir formatos personalizados que funcionen con todas las operaciones que desearás al final. Las matrices dispersas es un viejo problema, así que asegúrese de leer las cosas existentes.

Suponiendo que no encuentra algo adecuado, recomendaría un formato basado en filas. No intente ser demasiado elegante con representaciones súper compactas, terminará con mucho procesamiento necesario para cada pequeña operación y errores en su código. En su lugar, intente comprimir cada fila por separado. Sabes que vas a tener que escanear cada fila para ver la multiplicación matriz-vector, hazte la vida más fácil.

Comenzaría con la codificación de longitud de ejecución, vea cómo funciona eso primero. Una vez que esté funcionando, intente agregar algunos trucos como referencias a las secciones de la fila anterior. Por lo tanto, una fila puede codificarse como: 126 ceros, 8 unidades, 1000 entradas copiadas directamente de la fila anterior, 32 ceros. Parece que podría ser muy eficiente con tu ejemplo dado.

+0

Hay 100M filas, por lo que manejar los datos fila por fila significa que necesitará al menos O (100M * sizeof (compressed_row)) de memoria, que no tengo. –

0

Muchas de las soluciones anteriores están bien.

Si está trabajando con un archivo, considere las herramientas de compresión orientadas a archivos como compress, bzip, zip, bzip2 y sus amigos. Funcionan muy bien, especialmente si los datos contienen caracteres ASCII redundantes . El uso de una herramienta de compresión externa elimina los problemas y desafíos de dentro de su código y comprimirá datos binarios y ASCII.

En su ejemplo, está mostrando números de un carácter. Los números 0-9 se pueden representar mediante un patrón de codificación de cuatro bits más pequeño. Puede usar los bits adicionales en byte como recuento. Cuatro bits te da códigos extra para escapar a extras ... Pero hay una advertencia que llega a a los viejos errores de Y2K donde se usaron dos caracteres durante un año. La codificación de bytes desde un ofset habría dado 255 años y los mismos dos bytes abarcarían todo el historial escrito de y algo más.

+0

Esta matriz se está manejando en la memoria, desafortunadamente. Y los números 0-9 fueron solo ejemplos para la ilustración. Los datos reales son enteros completos. –

0

Es posible que desee echar un vistazo a GIF format y su algoritmo de compresión. Solo piense en su matriz como un mapa de bits ...

1

Su descripción de O (1) espacio para una matriz de tamaño 100M x 100M es confusa. Cuando tienes una matriz finita, entonces tu tamaño es una constante (a menos que el programa que genera la matriz no lo altere). Entonces la cantidad de espacio requerido para almacenar también es una constante incluso si la multiplicas con un escalar. Definitivamente el tiempo para leer y escribir la matriz no va a ser O (1).

La matriz dispersa es lo que podría pensar para reducir la cantidad de espacio requerido para almacenar dicha matriz. Puede escribir esta matriz dispersa en un archivo y almacenarla como tar.gz, lo que comprime aún más los datos.

Tengo una pregunta ¿qué significa M en 100M? ¿Significa megabyte/millón? Si es así, este tamaño de matriz será 100 x 10^6 x 100 x 10^6 bytes = 10^16/10^6 MB = 10^10/10^6 TB = 10^4 TB !!! ¿Qué tipo de máquina estás usando?

+0

M significa "millón". Sí, la matriz es enorme y almacenarla ingenuamente no es una opción. Es por eso que publiqué la pregunta :) –

2

¿Sabes sobre ... interval trees?

Los árboles de intervalo son una forma de almacenar intervalos de manera eficiente y luego consultarlos. Una generalización es Range Tree, que se puede adaptar a cualquier dimensión.

Aquí podría describir eficazmente sus rectángulos y adjuntar un valor a ellos. Por supuesto, los rectángulos pueden superponerse, eso es lo que lo hará eficiente.

0,0-n,n --> 8 
4,4-7,7 --> 1 
8,8-8,n --> 3 

A continuación, al consultar un valor en un punto determinado, se devuelven una lista de varios rectángulos y la necesidad de determinar la más interna: este es el valor en este punto.

0

Quiero comprobar mi hipótesis, si no por otra razón que la de guiar a mi forma de pensar sobre el problema:

  1. La matriz es altamente redundante, no necesariamente escasa.
  2. Queremos minimizar el almacenamiento (en el disco y la RAM).
  3. Queremos ser capaces de multiplicar A [m * n] por el vector B [n * 1] para obtener AB [m * 1] sin descomprimir primero (al menos no más de lo necesario para hacer los cálculos).
  4. No necesitamos acceso aleatorio a ninguna entrada A [i * j] --todas las operaciones están sobre la matriz.
  5. La multiplicación se realiza en línea (según sea necesario), por lo que debe ser lo más eficiente posible.
  6. La matriz es estática.

Uno puede probar todo tipo de esquemas inteligentes para detectar rectángulos o similitudes entre sí, etc., pero eso terminará perjudicando el rendimiento al hacer la multiplicación. Propongo 2 soluciones relativamente simples.

Voy a tener que retroceder un poco, así que tenga paciencia conmigo.

Si los datos son predominantemente sesgados hacia la repetición horizontal, entonces lo siguiente puede funcionar bien.

Piense en la matriz aplanada en una matriz (esta es realmente la forma en que se almacena en la memoria de todos modos). P.ej.

A 
| w0 w1 w2 | 
| x0 x1 x2 | 
| y0 y1 y2 | 
| z0 z1 z2 | 

convierte

A’ 
| w0 w1 w2 x0 x1 x2 y0 y1 y2 z0 z1 z2 | 

podemos utilizar el hecho de que cualquier índice [i,j] = i * j.

Por lo tanto, cuando hacemos la multiplicación que iterar sobre la “matriz” matriz A' con k = [0 ..m * n-1] e indexe en el vector B usando (k mod n) y en el vector AB con (k div n). "Div" es una división entera. Por ejemplo, A[10] = z1. 10 mod 3 = 1 y 10 div 3 = 3 A[3,1] = z1.

Ahora, a la compresión. Hacemos un funcionamiento normal de Run Length Encoding (RLE), pero en contra de A ', no A. Con la matriz plana habrá más secuencias de repetición, por lo tanto, mejor compresión. Luego, después de codificar las ejecuciones, hacemos otro proceso donde extraemos subcadenas comunes. Podemos hacer una forma de compresión de diccionario, o procesar los datos de ejecución en alguna forma de gráfico optimizado de espacio como un árbol de raíz/árbol de sufijo o un dispositivo de su propia creación que combine tops y colas. El gráfico debe tener una representación de todas las cadenas únicas en los datos. Puede elegir cualquier cantidad de métodos para dividir la secuencia en cadenas: coincidir prefijos, longitud o algo más (lo que mejor se adapte a su gráfico) pero hacerlo en un límite de ejecución, no en bytes o su descodificación se hará más complicada.El gráfico se convierte en una máquina de estados cuando descomprimimos la secuencia.

voy a utilizar un flujo de bits y Patricia trie como ejemplo, ya que es más sencillo, pero se puede usar algo más (más bits por cambio de estado mejor fusión, etc. Busque los documentos por Stefan Nilsson).

Para comprimir los datos de ejecución construimos una tabla hash contra el gráfico. La tabla asigna una cadena a una secuencia de bits. Puede hacer esto caminando el gráfico y codificando cada rama izquierda como 0 y rama derecha como 1 (opción arbitraria).

Procese los datos de ejecución y construya una cadena de bits hasta que obtenga una coincidencia en la tabla hash, genere los bits y borre la cadena (los bits no estarán en un límite de bytes, por lo que puede tener que almacenarlos hasta que obtener una secuencia lo suficientemente larga para escribir). Enjuague y repita hasta que haya procesado la secuencia completa de datos de ejecución. Usted almacena el gráfico y el flujo de bits. El flujo de bits codifica cadenas, no bytes.

Si invierte el proceso, usando el flujo de bits para recorrer el gráfico hasta llegar a un nodo de hoja/terminal, obtiene los datos de ejecución originales, que puede decodificar sobre la marcha para generar la secuencia de enteros que multiplica contra el vector B para obtener AB. Cada vez que se queda sin ejecuciones, lee el siguiente bit y busca su cadena correspondiente. No nos importa que no tengamos acceso aleatorio a A, porque solo lo necesitamos en B (B, que puede estar comprimido pero no necesariamente).

Por lo tanto, aunque RLE está sesgado hacia las ejecuciones horizontales, seguimos obteniendo una buena compresión vertical porque las cadenas comunes se almacenan solo una vez.

Explicaré el otro método en una respuesta por separado, ya que es demasiado largo como lo es, pero ese método puede acelerar el cálculo debido a que las filas repetidas en la matriz A se multiplican para obtener el mismo resultado en AB.

10

Ahora para mi método preferido.

Ok, como mencioné en mi respuesta anterior, las filas con las mismas entradas en cada columna de la matriz A se multiplicarán con el mismo resultado en la matriz AB. Si podemos mantener esa relación, teóricamente podemos acelerar los cálculos de manera significativa (un profiler es tu amigo).

En este método, mantenemos la estructura de la columna row * de la matriz.

Cada fila se comprime con cualquier método que pueda descomprimirse lo suficientemente rápido como para no afectar demasiado la velocidad de multiplicación. RLE puede ser suficiente.

Ahora tenemos una lista de filas comprimidas.

Utilizamos un método de codificación de entropía (como Shannon-Fano, Huffman o codificación aritmética), pero no comprimimos los datos en las filas con esto, lo usamos para comprimir el conjunto de filas. Lo usamos para codificar la frecuencia relativa de las filas. Es decir. tratamos una fila de la misma manera que la codificación de entropía estándar trataría un carácter/byte.

En este ejemplo RLE comprime una fila, y Huffman comprime toda la conjunto de filas.

Así, por ejemplo, dada la siguiente matriz (con el prefijo números de fila, Huffman utiliza para facilitar la explicación) longitud

0 | 8 8 8 8 8 8 8 8 8 8 8 8 8 | 
1 | 8 4 8 8 1 1 1 1 1 8 8 8 8 | 
2 | 8 4 8 8 1 1 1 1 1 8 8 8 8 | 
3 | 8 4 8 8 1 1 1 1 1 8 8 8 8 | 
4 | 8 4 8 8 1 1 1 1 1 8 8 8 8 | 
5 | 8 4 8 8 1 1 1 1 1 8 8 8 8 | 
6 | 8 8 8 8 8 8 8 8 8 8 8 8 8 | 
7 | 8 8 3 3 3 3 3 3 3 3 3 3 3 | 

Run codificada

0 | 8{13}     | 
1 | 8{1} 4{1} 8{2} 1{5} 8{4} | 
2 | 8{1} 4{1} 8{2} 1{5} 8{4} | 
3 | 8{1} 4{1} 8{2} 1{5} 8{4} | 
4 | 8{1} 4{1} 8{2} 1{5} 8{4} | 
5 | 8{1} 4{1} 8{2} 1{5} 8{4} | 
6 | 8{13}     | 
7 | 8{2} 3{11}    | 

Así, 0 y 6 aparecen dos veces y 1 - 5 aparecen 5 veces. 7 solo una vez

Tabla de frecuencia

A: 5 (1-5) | 8{1} 4{1} 8{2} 1{5} 8{4} | 
B: 2 (0,6) | 8{13}     | 
C: 1 7 | 8{2} 3{11}    | 

Huffman árbol

0|1 
/ \ 
    A 0|1 
    / \ 
    B  C 

Así que en este caso se necesita un bit (para cada fila) para codificar las filas 1 - 5, y 2 bits para codificar filas 0, 6 y 7.

(Si las ejecuciones son más largas que unos pocos bytes, entonces freq cuenta un hash que compila como lo hace con el RLE).

Almacena el árbol de Huffman, cadenas exclusivas y el flujo de bits de codificación de fila.

Lo bueno de Huffman es que tiene una propiedad de prefijo único, por lo que siempre sabrá cuando haya terminado. Por lo tanto, dada la cadena de bits 10000001011, puede reconstruir la matriz A a partir de las cadenas únicas almacenadas y el árbol. La secuencia de bits codificada le indica el orden en que aparecen las filas.

Es posible que desee examinar la codificación adaptativa de Huffman o su contraparte aritmética.

Al ver que las filas en A con las mismas entradas de columna multiplican el mismo resultado en AB sobre el vector B puede almacenar el resultado y usarlo en vez de calcularlo nuevamente (siempre es bueno evitar multiplicaciones de 100M * 100M si puede)

enlaces a más información:

Arithmetic Coding + Statistical Modeling = Data Compression

Priority Queues and the STL

Arithmetic coding

Huffman coding

Una comparación

Sin comprimir

0 1 2 3 4 5 6 7 
    ================================= 
0 | 3 3 3 3 3 3 3 3 | 
    |-------+    +-------| 
1 | 4 4 | 3 3 3 3 | 4 4 | 
    |  +-----------+---+  | 
2 | 4 4 | 5 5 5 | 1 | 4 4 | 
    |  |   | |  | 
3 | 4 4 | 5 5 5 | 1 | 4 4 | 
    |---+---|   | |  | 
4 | 5 | 0 | 5 5 5 | 1 | 4 4 | 
    | | +---+-------+---+-------| 
5 | 5 | 0 0 | 2 2 2 2 2 | 
    | |  |     | 
6 | 5 | 0 0 | 2 2 2 2 2 | 
    | |  +-------------------| 
7 | 5 | 0 0 0 0 0 0 0 | 
    ================================= 

= 64 bytes

Quadtree

0 1 2 3 4 5 6 7 
    ================================= 
0 | 3 | 3 |  |  | 3 | 3 | 
    |---+---| 3 | 3 |---+---| 
1 | 4 | 4 |  |  | 4 | 4 | 
    |-------+-------|-------+-------| 
2 |  |  | 5 | 1 |  | 
    | 4 | 5 |---+---| 4 | 
3 |  |  | 5 | 1 |  | 
    |---------------+---------------| 
4 | 5 | 0 | 5 | 5 | 5 | 1 | 4 | 4 | 
    |---+---|---+---|---+---|---+---| 
5 | 5 | 0 | 0 | 2 | 2 | 2 | 2 | 2 | 
    |-------+-------|-------+-------| 
6 | 5 | 0 | 0 | 2 | 2 | 2 | 2 | 2 | 
    |---+---+---+---|---+---+---+---| 
7 | 5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 
    ================================= 

0 +- 0 +- 0 -> 3 
    | +- 1 -> 3 
    | +- 2 -> 4 
    | +- 3 -> 4 
    +- 1  -> 3 
    +- 2  -> 4 
    +- 3  -> 5 
1 +- 0  -> 3 
    +- 1 +- 0 -> 3 
    | +- 1 -> 3 
    | +- 2 -> 4 
    | +- 3 -> 4 
    +- 2 +- 0 -> 5 
    | +- 1 -> 1 
    | +- 2 -> 5 
    | +- 3 -> 1 
    +- 3  -> 4 
2 +- 0 +- 0 -> 5 
    | +- 1 -> 0 
    | +- 2 -> 5 
    | +- 3 -> 0 
    +- 1 +- 0 -> 5 
    | +- 1 -> 5 
    | +- 2 -> 0 
    | +- 3 -> 2 
    +- 2 +- 0 -> 5 
    | +- 1 -> 0 
    | +- 2 -> 5 
    | +- 3 -> 0 
    +- 3 +- 0 -> 0 
     +- 1 -> 2 
     +- 2 -> 0 
     +- 3 -> 0 
3 +- 0 +- 0 -> 5 
    | +- 1 -> 1 
    | +- 2 -> 2 
    | +- 3 -> 2 
    +- 1 +- 0 -> 4 
    | +- 1 -> 4 
    | +- 2 -> 2 
    | +- 3 -> 2 
    +- 2 +- 0 -> 2 
    | +- 1 -> 2 
    | +- 2 -> 0 
    | +- 3 -> 0 
    +- 3 +- 0 -> 2 
     +- 1 -> 2 
     +- 2 -> 0 
     +- 3 -> 0 

((1*4) + 3) + ((2*4) + 2) + (4 * 8) = 49 leaf nodes 
49 * (2 + 1) = 147 (2 * 8 bit indexer, 1 byte data) 
+ 14 inner nodes -> 2 * 14 bytes (2 * 8 bit indexers) 
= 175 Bytes 

Región Hash

0 1 2 3 4 5 6 7 
    ================================= 
0 | 3 3 3 3 3 3 3 3 | 
    |-------+---------------+-------| 
1 | 4 4 | 3 3 3 3 | 4 4 | 
    |  +-----------+---+  | 
2 | 4 4 | 5 5 5 | 1 | 4 4 | 
    |  |   | |  | 
3 | 4 4 | 5 5 5 | 1 | 4 4 | 
    |---+---|   | |  | 
4 | 5 | 0 | 5 5 5 | 1 | 4 4 | 
    | + - +---+-------+---+-------| 
5 | 5 | 0 0 | 2 2 2 2 2 | 
    | |  |     | 
6 | 5 | 0 0 | 2 2 2 2 2 | 
    | +-------+-------------------| 
7 | 5 | 0 0 0 0 0 0 0 | 
    ================================= 

0: (4,1; 4,1), (5,1; 6,2), (7,1; 7,7)   | 3 
1: (2,5; 4,5)         | 1 
2: (5,3; 6,7)         | 1 
3: (0,0; 0,7), (1,2; 1,5)      | 2 
4: (1,0; 3,1), (1,6; 4,7)      | 2 
5: (2,2; 4,4), (4,0; 7,0)      | 2 

Regiones: (3 + 1 + 1 + 2 + 2 + 2) * 5 = 55 bytes {4 bytes rectángulo, 1 byte de datos)

{tabla de búsqueda es una matriz ordenada, por lo que no necesita adicional almacenamiento}.

Huffman codificado RLE

corriente
0 | 3 {8}         | 1 
1 | 4 {2} | 3 {4} | 4 {2}     | 2 
2,3 | 4 {2} | 5 {3} | 1 {1} | 4 {2}   | 4 
4 | 5 {1} | 0 {1} | 5 {3} | 1 {1} | 4 {2} | 5 
5,6 | 5 {1} | 0 {2} | 2 {5}     | 3 
7 | 5 {1} | 0 {7}       | 2 


RLE Data: (1 + 3+ 4 + 5 + 3 + 2) * 2 = 36 
Bit Stream: 20 bits packed into 3 bytes = 3 
Huffman Tree: 10 nodes * 3 = 30 
= 69 Bytes 

Una Giant RLE

3{8};4{2};3{4};4{4};5{3};1{1};4{4};5{3};1{1};4{2};5{1};0{1}; 
5{3};1{1};4{2};5{1};0{2};2{5};5{1};0{2};2{5};5{1};0{7} 

= 2 * 23 = 46 Bytes 

Una Giant RLE flujo codificado con prefijo común plegable

3{8}; 
4{2};3{4}; 
4{4};5{3};1{1}; 
4{4};5{3}; 
1{1};4{2};5{1};0{1};5{3}; 
1{1};4{2};5{1};0{2};2{5}; 
5{1};0{2};2{5}; 
5{1};0{7} 

0 + 0 -> 3{8};4{2};3{4}; 
    + 1 -> 4{4};5{3};1{1}; 

1 + 0 -> 4{2};5{1} + 0 -> 0{1};5{3};1{1}; 
    |    + 1 -> 0{2} 
    | 
    + 1 -> 2{5};5{1} + 0 -> 0{2}; 
        + 1 -> 0{7} 

3{8};4{2};3{4}   | 00 
4{4};5{3};1{1}   | 01 
4{4};5{3};1{1}   | 01 
4{2};5{1};0{1};5{3};1{1} | 100 
4{2};5{1};0{2}   | 101 
2{5};5{1};0{2}   | 110 
2{5};5{1};0{7}   | 111 

Bit stream: 000101100101110111 
RLE Data: 16 * 2 = 32 
Tree: : 5 * 2 = 10 
Bit stream: 18 bits in 3 bytes = 3 
= 45 bytes 
+0

Solo por interés, si tomamos la matriz original como 104 bytes, 1 byte por entrada, entonces la versión comprimida tiene 19 bytes en total. –

0

bien, necesitas un algoritmo de compresión prueba RLE (Run Length Encoding) funciona muy bien cuando los datos son altamente redundantes.

1

No estoy seguro de por qué se hizo esta pregunta Community Wiki, pero así es.

Voy a confiar en la suposición de que tienes una aplicación de álgebra lineal, y que tu matriz tiene un tipo rectangular de redundancia. Si es así, entonces puedes hacer algo mucho mejor que quadtrees, y más limpio que cortar la matriz en rectángulos (que generalmente es la idea correcta).

Sea M su matriz, sea v el vector que desea multiplicar por M, y dejar A la matriz especial

A = [1 -1 0 0 0] 
    [0 1 -1 0 0] 
    [0 0 1 -1 0] 
    [0 0 0 1 -1] 
    [0 0 0 0 1] 

también necesitará la matriz inversa de A, que voy a llamar a B:

B = [1 1 1 1 1] 
    [0 1 1 1 1] 
    [0 0 1 1 1] 
    [0 0 0 1 1] 
    [0 0 0 0 1] 

multiplicar un vector v por a es rápido y fácil:. Usted acaba de tomar diferencias de pares consecutivos de elementos de v multiplicar un vector v por B también es rápido y fácil: las entradas de Bv son sumas parciales de los elementos de v. Entonces quieres usar el equ ación

Mv = B AMA B v 

La matriz AMA es escasa: En el medio, cada entrada es una suma alterna de 4 entradas de M que hacen que un cuadrado de 2 x 2. Tienes que estar en una esquina de uno de los rectángulos en M para que esta suma alterna sea distinta de cero. Como AMA es escaso, puede almacenar sus entradas distintas de cero en una matriz asociativa y usar la multiplicación de matriz dispersa para aplicarlo a un vector.

3

Como Ira Baxter sugirió, puede almacenar la matriz como un árbol cuádruple con las hojas que contienen valores únicos.

La forma más simple de hacerlo es que cada nodo del quadtree cubra un área 2^nx 2^n, y cada nodo no hoja apunta a sus 4 hijos de tamaño 2^(n-1) x 2^(n-1).

Es posible que obtenga una compresión ligeramente mejor con un árbol cuádruple adaptativo que permite la subdivisión irregular. Luego, cada nodo no hoja almacena el punto de corte (B, G) y apunta a sus 4 hijos. Por ejemplo, si un nodo no hoja cubre un área desde (A, F) en la esquina superior izquierda hasta (C, H) en la esquina inferior derecha, , entonces sus 4 niños cubren áreas (A, F) a (B-1, G-1) (A, G) a (B-1, H) (B, F) a (C, G-1) (B, G) a (C, H))

Intentaría elegir el punto de corte (B, G) para cada nodo no hoja de modo que se alinee con alguna división real en sus datos.

Por ejemplo, supongamos que tiene una matriz con un cuadrado pequeño en el medio lleno de nueves y cero en otro lugar.

Con el simple quadtree powers-of-two, terminarás con al menos 21 nodos: 5 nodos leaf, 4 leaf nodes de nueves y 12 leaf nodos de ceros. (Obtendrá incluso más nodos si el cuadrado pequeño centrado no es precisamente alguna distancia de la potencia de dos desde los bordes izquierdo y superior, y no tiene una potencia precisa de dos).

Con un quadtree adaptativo, si eres lo suficientemente inteligente como para elegir el punto de corte para el nodo raíz en la esquina superior izquierda de ese cuadrado, entonces para el secundario derecho de la raíz eliges un punto de corte en el esquina inferior derecha del cuadrado, puede representar toda la matriz en 9 nodos: 2 nodos hoja, 1 nodo hoja para los nueves y 6 nodos hoja para los ceros.

Cuestiones relacionadas