2008-10-29 23 views
25

¿Cuál es el mejor algoritmo para comparar dos matrices para ver si tienen los mismos miembros?Algoritmo para saber si dos matrices tienen miembros idénticos

Suponga que no hay duplicados, los miembros pueden estar en cualquier orden y ninguno está ordenado.

compare(
    [a, b, c, d], 
    [b, a, d, c] 
) ==> true 

compare(
    [a, b, e], 
    [a, b, c] 
) ==> false 

compare(
    [a, b, c], 
    [a, b] 
) ==> false 
+0

¿Por qué no darle un puntapié y ver qué sucede si no podemos ordenar? obviamente necesitamos poder comparar por igualdad. – Hugo

+3

Parece que está preguntando cómo comparar los conjuntos – naumcho

+0

sí, eso es correcto ... – nickf

Respuesta

17

respuestas obvias serían:

  1. Ordenar ambas listas, a continuación, comprobar cada elemento para ver si son idénticos
  2. añadir los elementos de una matriz a una tabla hash , a continuación, iterar a través de la otra matriz, comprobando que cada elemento está en el hash
  3. iterativo algoritmo de búsqueda de
  4. nickf

Cuál usarías dependerá de si primero puedes ordenar las listas y si tienes un buen algoritmo hash a mano.

+0

Optimizaciones menores ... 1. Como se menciona a continuación, primero verifique la longitud. 2. La operación Set.add (E o) de Java devuelve verdadero si el elemento fue agregado, por lo que la iteración puede simplemente probar 'if (! SetA.add (elementFromB))' y devolver falso. –

+2

El problema con el enfoque de tabla hash es que no funciona cuando la lista puede tener valores duplicados. ¿A [1, 2, 2, 3] == b [1, 2, 3, 3]? Ambos tienen la misma longitud y todos los elementos en b se pueden encontrar en la tabla hash de a. Necesita una estructura establecida que cuente los elementos y verifique que los recuentos sean iguales. – jmucchiello

+3

Desde una perspectiva matemática, un conjunto no contiene el mismo valor dos veces. –

-3

Lo mejor que puedo pensar es O (n^2), supongo.

function compare($foo, $bar) { 
    if (count($foo) != count($bar)) return false; 

    foreach ($foo as $f) { 
     foreach ($bar as $b) { 
      if ($f == $b) { 
       // $f exists in $bar, skip to the next $foo 
       continue 2; 
      } 
     } 
     return false; 
    } 
    return true; 
} 
+0

O (n) sería lo mejor si está usando una tabla hash. Si está ordenando primero O (nlogn). –

+0

¿Esto todavía funciona si foo tiene duplicados? No parece que verifique si tienen el mismo número de duplicados. Es decir, esto coincidiría falsamente con [0 0 0 0 1 2 3] y [0 1 1 2 2 3 3] –

+0

no, no funciona en ese caso, pero esa fue una de las suposiciones al principio. Supongo que si no puedes estar seguro de que no hay duplicados, entonces puedes eliminar elementos de la matriz de barras a medida que se combinan. – nickf

6

Puede cargar uno en una tabla hash, manteniendo un registro de cuántos elementos tiene. Luego, revise el segundo para verificar si cada uno de sus elementos está en la tabla hash y cuente cuántos elementos tiene. Si cada elemento en la segunda matriz está en la tabla hash, y las dos longitudes coinciden, son las mismas, de lo contrario no lo son. Esto debería ser O (N).

Para que esto funcione en presencia de duplicados, realice un seguimiento de cuántos elementos se han visto. Incremente al hacer un bucle sobre la primera matriz y disminuya mientras se repite la segunda matriz. Durante el ciclo sobre la segunda matriz, si no puede encontrar algo en la tabla hash, o si el contador ya está en cero, son desiguales. También compare los recuentos totales.

Otro método que funcionaría en presencia de duplicados es ordenar ambas matrices y hacer una comparación lineal. Esto debería ser O (N * log (N)).

+0

Esta solución funciona mejor en presencia de duplicados. – jmucchiello

1

Yo sugeriría usar un tipo primero y ordenar los dos primero. Luego, comparará el primer elemento de cada matriz, luego el segundo y así sucesivamente.

Si encuentra una discrepancia, puede detenerla.

1

Si ordena las dos matrices primero, obtendría O (N log (N)).

0

La mejor manera es probablemente usar hashmaps. Dado que la inserción en un hashmap es O (1), la construcción de un hashmap a partir de un array debería tomar O (n). Luego tiene n búsquedas, que cada una toma O (1), por lo que otra operación O (n). En general, es O (n).

en Python:

def comparray(a, b): 
    sa = set(a) 
    return len(sa)==len(b) and all(el in sa for el in b) 
+0

Me encantaría conocer los detalles de esta estructura de datos con O (1) inserciones y O (1) búsquedas. A menos que tenga una función hash perfecta (sin duplicados) y no exista una sobrecarga a medida que crece el hashmap, la inserción va a ser mayor que O (1) – Frentos

+0

Bueno, se amortiza O (1), lo que significa que en promedio será O (1). Obviamente, hacer suficientes inserciones hará que crezca y colisione, y que las búsquedas tarden un poco más. Supongo que se aplicaría en este caso, ya que está haciendo n de ellos. ¿Conoces detalles sobre el rendimiento hashmap de python? – Claudiu

+0

Acepto que las inserciones/búsquedas típicas se amortizan O (1), y definitivamente usaría alguna forma de diccionario/hash/set como una solución en la práctica. Pero la notación O() es para el peor de los casos. Busqué en Google y encontré estructuras de datos que garantizan el acceso O (1), pero no el análisis del peor momento de inserción para N elementos. – Frentos

0

ignorando el construido en maneras de hacer esto en C#, se podría hacer algo como esto:

Su O (1) en el mejor de los casos, O (N) (por lista) en el peor de los casos.

public bool MatchArrays(object[] array1, object[] array2) 
{ 
    if (array1.length != array2.length) 
     return false; 

    bool retValue = true; 

    HashTable ht = new HashTable(); 

    for (int i = 0; i < array1.length; i++) 
    { 
     ht.Add(array1[i]); 
    } 

    for (int i = 0; i < array2.length; i++) 
    { 
     if (ht.Contains(array2[i]) 
     { 
     retValue = false; 
     break; 
     } 
    } 

    return retValue; 
} 
+0

Las tablas hash no son O (1) para realizar búsquedas. Su solución está en algún lugar entre O (N.log N) y O (N^2) dependiendo de la implementación de la tabla hash. – Frentos

+0

El O (1) es si la lista no coincide, de lo contrario es O (N) dos veces en el peor de los casos y O (N) en el mejor de los casos. Las tablas hash son O (1) cuando no hay posibilidad de una colisión por cierto. – FlySwat

5

asumiendo que no quiere molestar a las matrices originales y el espacio es una consideración, otra solución O (n.log (n)) que utiliza menos espacio que la clasificación de ambas matrices es:

  1. Devuelve FALSE si matrices difieren en tamaño
  2. Ordenar la primera matriz - O (n.log) (n) tiempo, el espacio extra que se requiere es el tamaño de una matriz
  3. Para cada elemento de la segunda matriz, comprobar si está en la copia ordenada de la primera matriz usando una búsqueda binaria - O (n.log (n)) time

Si utiliza este enfoque, utilice una rutina de biblioteca para realizar la búsqueda binaria. La búsqueda binaria sorprendentemente es propensa a errores en el código de la mano.

[Añadido después de revisar las soluciones que sugiere búsquedas de diccionario/set/picadillo:]

En la práctica que haría uso de un hash. Varias personas han afirmado el comportamiento O (1) para hash, lo que les lleva a concluir que una solución basada en hash es O (N). Las inserciones/búsquedas típicas pueden estar cerca de O (1), y algunos esquemas de hashing garantizan la búsqueda O (1) en el peor de los casos, pero la inserción en el peor de los casos - al construir el hash - no es O (1). Dada cualquier estructura de datos hash particular, habría algún conjunto de entradas que produciría un comportamiento patológico. Sospecho que existen estructuras de datos hash con el peor caso combinado para [insertar-N-elementos y luego buscar-N-elementos] de O (N.log (N)) de tiempo y O (N) espacio.

+0

Si suponemos que los datos no son hostiles, los peores momentos rara vez son interesantes. Todo el mundo dice que quicksort es O (n * log (n)), pero el peor de los casos es O (n^2) – erikkallen

+1

Frentos, Su primera solución no funcionará para esta entrada: Array 1 = [1,2 , 3] Array 2 = [1,1,1] – user674669

1

Cuál es la "mejor" solución, obviamente, depende de las restricciones que tenga. Si se trata de un pequeño conjunto de datos, la comparación de clasificación, hashing o fuerza bruta (como nickf publicado) será bastante similar. Como sabe que está tratando con valores enteros, puede obtener O (n) ordenar los tiempos (por ejemplo, clasificación de raíz), y la tabla de dispersión también usará el tiempo O (n). Como siempre, existen inconvenientes en cada enfoque: la ordenación requerirá que duplique los datos o clasifique destructivamente su matriz (perdiendo el orden actual) si desea ahorrar espacio. Una tabla hash obviamente tendrá sobrecarga de memoria para crear la tabla hash. Si usa el método de nickf, puede hacerlo con una sobrecarga de memoria pequeña o nula, pero tiene que tratar con el tiempo de ejecución O (n). Puedes elegir cuál es la mejor para tus propósitos.

+0

Sin embargo, la solución de nickf es la más fácil para múltiples hilos; no hace ninguna escritura a los datos compartidos y puede escalar (cerca) linealmente. Solo tiene problemas con elementos duplicados como se menciona en los comentarios. –

1

El ir en aguas profundas aquí, pero:

listas ordenadas clasificación puede ser O(nlogn) como se ha señalado. solo para aclarar, no importa que haya dos listas, porque: O(2*nlogn) == O(nlogn), luego comparar cada elemento es otro O (n), por lo que ordenar ambos y luego comparar cada elemento es O (n) + O (nlogn) que es: O(nlogn)

tablas hash: la conversión de la primera lista a una tabla hash es O (n) para la lectura + el coste de almacenar en la tabla hash, que supongo que puede ser estimada como O (n) , da O (n). Luego tendrá que verificar la existencia de cada elemento en la otra lista en la tabla hash producida, que es (¿al menos?) O (n) (suponiendo que la comprobación de la existencia de un elemento la tabla hash es constante). Con todo, terminamos con O(n) para el control.

La interfaz de lista de Java defines equals como cada elemento correspondiente es igual.

Curiosamente, la definición de interfaz almost discourages implementing the equals() función de Java Collection.

Por último, el Java Set interface per documentation implements este mismo comportamiento. La implementación debe ser muy eficiente, pero la documentación no menciona el rendimiento. (No se pudo encontrar un enlace a la fuente, es probable que tenga una licencia estricta. Descárgala y mírala tú mismo. Viene con el JDK) Al observar la fuente, el HashSet (que es una implementación comúnmente utilizada de Set) delega a los iguales() implementación en AbstractSet, que usa la función containsAll() de AbstractCollection usando de nuevo la función contains() desde hashSet. Entonces HashSet.equals() se ejecuta en O (n) como se esperaba. (Bucle a través de todos los elementos y mirando hacia arriba en un tiempo constante en la tabla hash.)

favor de edición si usted sabe mejor que me sobra el embarrasment.

+0

La diferencia entre O (2 * n log n) y O (n log n) sí importa en una aplicación práctica; son las constantes ocultas que conforman en gran medida el rendimiento del algoritmo. La velocidad teórica solo es relevante en un nivel superficial. –

0

En caso de colisión, un hashmap es O (n) en la mayoría de los casos porque utiliza una lista vinculada para almacenar las colisiones. Sin embargo, hay mejores enfoques y casi no deberías tener colisiones de todos modos porque si lo hicieras, el hashmap sería inútil. En todos los casos regulares es simplemente O (1). Además de eso, no es probable que tenga más de un pequeño n de colisiones en un hashmap único, por lo que el rendimiento no sería tan malo; puede decir con seguridad que es O (1) o casi O (1) porque la n es tan pequeña que puede ignorarse.

3

Esto se puede hacer de diferentes maneras:

1 - La fuerza bruta: para cada elemento de matriz1 comprobar existe ese elemento en matriz2. Tenga en cuenta que esto requeriría tener en cuenta la posición/índice para que los duplicados se puedan manejar correctamente. Esto requiere O (n^2) con la cantidad de código complicado, ni siquiera pensar en ello en absoluto ...

2 - Clasificar las dos listas, a continuación, comprobar cada elemento para ver si son idénticos. O (n log n) para ordenar y O (n) para verificar, básicamente, O (n log n), la ordenación se puede realizar in situ si el desorden de las matrices no es un problema, si no es necesario tener 2n de memoria de tamaño para copiar la lista ordenada

3 - Agregue los elementos y el recuento de una matriz a una tabla hash, luego itere a través de la otra matriz, verificando que cada elemento se encuentre en la tabla hash y en ese caso recuento de decrementos si no es cero; de lo contrario, quítelo de hashtable. O (n) para crear una tabla hash, y O (n) para comprobar los otros elementos de la matriz en la tabla hash, por lo O (n). Esto introduce una tabla hash con memoria como máximo para n elementos.

4 - Best of Best (Entre los anteriores): Restar o tomar diferencia de cada elemento en el mismo índice de las dos matrices y finalmente resumir los valores subtacted. Por ejemplo, A1 = {1,2,3}, A2 = {3,1,2} el Diff = {- 2,1,1} ahora resume el Diff = 0, lo que significa que tienen el mismo conjunto de enteros. Este enfoque requiere un O (n) sin memoria adicional. Un código C# vería como la siguiente:

public static bool ArrayEqual(int[] list1, int[] list2) 
    { 
     if (list1 == null || list2 == null) 
     { 
      throw new Exception("Invalid input"); 
     } 

     if (list1.Length != list2.Length) 
     { 
      return false; 
     } 

     int diff = 0; 

     for (int i = 0; i < list1.Length; i++) 
     { 
      diff += list1[i] - list2[i]; 
     } 

     return (diff == 0); 
    } 

4 no funciona en absoluto, es el peor

+0

que falla en las entradas: [2,4,6] y [-2,8,6] -> diff [4, -4,0] = 0. – nickf

+0

Más bien, haz esto: 'diff + = Math. abs (list1 [i]) - Math.abs (list2 [i]); ' –

+0

' abs' no funcionará. considerar caso: [2, 4, 0] y [5, 1, 0] => diff [-3, 3, 0] == 0. – Vasu

4

Puede utilizar una firma (una operación conmutativa lo largo de los miembros de la matriz) para optimizar aún más este en el caso en que la matriz generalmente sea diferente, guarde el o(n log n) o la asignación de memoria. Una firma puede tener la forma de un filtro (s) de floración, o incluso una operación conmutativa simple como "suma" o "xor".

Un ejemplo sencillo (suponiendo un largo como el lado firma y GetHashCode como un buen identificador de objeto; si los objetos son, por ejemplo, enteros, entonces su valor es una mejor identificador; y algunas firmas será mayor que de longitud)

public bool MatchArrays(object[] array1, object[] array2) 
{ 
    if (array1.length != array2.length) 
     return false; 
    long signature1 = 0; 
    long signature2 = 0; 
    for (i=0;i<array1.length;i++) { 
     signature1=CommutativeOperation(signature1,array1[i].getHashCode()); 
     signature2=CommutativeOperation(signature2,array2[i].getHashCode()); 
    } 

    if (signature1 != signature2) 
     return false; 

    return MatchArraysTheLongWay(array1, array2); 
} 

donde (mediante una operación de suma, el uso de una operación conmutativa diferente si se desea, por ejemplo, la floración filtros)

public long CommutativeOperation(long oldValue, long newElement) { 
    return oldValue + newElement; 
} 
0

Aquí es otra opción, que me haga saber lo que ustedes think.It debería ser T (n) = 2n * log2n -> O (nLogn) en el peor de los casos.

private boolean compare(List listA, List listB){ 
    if (listA.size()==0||listA.size()==0) return true; 
    List runner = new ArrayList(); 
    List maxList = listA.size()>listB.size()?listA:listB; 
    List minList = listA.size()>listB.size()?listB:listA; 
    int macthes = 0; 
    List nextList = null;; 
    int maxLength = maxList.size(); 
    for(int i=0;i<maxLength;i++){ 
     for (int j=0;j<2;j++) { 
      nextList = (nextList==null)?maxList:(maxList==nextList)?minList:maList; 
      if (i<= nextList.size()) { 
       MatchingItem nextItem =new MatchingItem(nextList.get(i),nextList) 
       int position = runner.indexOf(nextItem); 
       if (position <0){ 
        runner.add(nextItem); 
       }else{ 
        MatchingItem itemInBag = runner.get(position); 
        if (itemInBag.getList != nextList) matches++; 
        runner.remove(position); 
       } 
      } 
     } 
    } 
    return maxLength==macthes; 
} 

public Class MatchingItem{ 
private Object item; 
private List itemList; 
public MatchingItem(Object item,List itemList){ 
    this.item=item 
    this.itemList = itemList 
} 
public boolean equals(object other){ 
    MatchingItem otheritem = (MatchingItem)other; 
    return otheritem.item.equals(this.item) and otheritem.itemlist!=this.itemlist 
} 

public Object getItem(){ return this.item} 
public Object getList(){ return this.itemList} 

}

1

Pseudocódigo:

A:array 
B:array 
C:hashtable 

if A.length != B.length then return false; 

foreach objA in A 
{ 
H = objA; 
if H is not found in C.Keys then 
C.add(H as key,1 as initial value); 
else 
C.Val[H as key]++; 
} 

foreach objB in B 
{ 
H = objB; 
if H is not found in C.Keys then 
return false; 
else 
C.Val[H as key]--; 
} 

if(C contains non-zero value) 
return false; 
else 
return true; 
2

Si los elementos de una matriz se dan como distinta, a continuación, XOR (XOR bit a bit) todos los elementos tanto de las matrices, si la respuesta es cero, entonces ambas matrices tienen el mismo conjunto de números. La complejidad del tiempo es O (n)

+0

No realmente. Hacer XOR en todos los elementos de '{1, 2, 3, 3}' y '{1, 2}' dará como resultado 0, pero las matrices son diferentes. :) –

+0

@ KonstantinYovkov, tomé ese caso en consideración. Es por eso que escribí "si los elementos de una matriz se dan como distintos" – akashrajkn

Cuestiones relacionadas