Publiqué esto una vez antes en SO, pero lo reproduciré aquí porque es bastante bueno. Utiliza hash, construyendo algo así como un hash establecido en su lugar. Se garantiza que es O (1) en el espacio axilar (la recursividad es una llamada de cola) y, por lo general, es una complejidad de tiempo O (N). El algoritmo es como sigue:
- Tome el primer elemento de la matriz, este será el centinela.
- Reordene el resto de la matriz, tanto como sea posible, de modo que cada elemento esté en la posición correspondiente a su hash. Cuando se complete este paso, se descubrirán duplicados. Ponlos igual a centinela.
- Mueva todos los elementos para los que el índice es igual al hash al comienzo de la matriz.
- Mueva todos los elementos que sean iguales a centinela, excepto el primer elemento de la matriz, al final de la matriz.
- Lo que queda entre los elementos correctamente hash y los elementos duplicados serán los elementos que no se pudieron colocar en el índice correspondiente a su hash debido a una colisión. Recurra a lidiar con estos elementos.
Se puede demostrar que es O (N) siempre que no haya un escenario patológico en el hash: incluso si no hay duplicados, aproximadamente 2/3 de los elementos se eliminarán en cada recursión. Cada nivel de recursividad es O (n) donde n pequeña es la cantidad de elementos que quedan. El único problema es que, en la práctica, es más lento que una clasificación rápida cuando hay pocos duplicados, es decir, muchas colisiones. Sin embargo, cuando hay una gran cantidad de duplicados, es increíblemente rápido.
Editar: en las implementaciones actuales de D, hash_t es de 32 bits. Todo sobre este algoritmo asume que habrá muy pocas, si es que hay alguna, colisiones hash en el espacio completo de 32 bits. Sin embargo, las colisiones pueden ocurrir con frecuencia en el espacio del módulo. Sin embargo, esta suposición será, con toda probabilidad, cierta para cualquier conjunto de datos de tamaño razonable. Si la clave es menor o igual a 32 bits, puede ser su propio hash, lo que significa que una colisión en el espacio completo de 32 bits es imposible. Si es más grande, simplemente no puede colocar suficientes en el espacio de direcciones de memoria de 32 bits para que sea un problema. Supongo que hash_t aumentará a 64 bits en implementaciones de 64 bits de D, donde los conjuntos de datos pueden ser más grandes. Además, si esto llegara a ser un problema, se podría cambiar la función hash en cada nivel de recursividad.
Aquí hay una implementación en el lenguaje de programación D:
void uniqueInPlace(T)(ref T[] dataIn) {
uniqueInPlaceImpl(dataIn, 0);
}
void uniqueInPlaceImpl(T)(ref T[] dataIn, size_t start) {
if(dataIn.length - start < 2)
return;
invariant T sentinel = dataIn[start];
T[] data = dataIn[start + 1..$];
static hash_t getHash(T elem) {
static if(is(T == uint) || is(T == int)) {
return cast(hash_t) elem;
} else static if(__traits(compiles, elem.toHash)) {
return elem.toHash;
} else {
static auto ti = typeid(typeof(elem));
return ti.getHash(&elem);
}
}
for(size_t index = 0; index < data.length;) {
if(data[index] == sentinel) {
index++;
continue;
}
auto hash = getHash(data[index]) % data.length;
if(index == hash) {
index++;
continue;
}
if(data[index] == data[hash]) {
data[index] = sentinel;
index++;
continue;
}
if(data[hash] == sentinel) {
swap(data[hash], data[index]);
index++;
continue;
}
auto hashHash = getHash(data[hash]) % data.length;
if(hashHash != hash) {
swap(data[index], data[hash]);
if(hash < index)
index++;
} else {
index++;
}
}
size_t swapPos = 0;
foreach(i; 0..data.length) {
if(data[i] != sentinel && i == getHash(data[i]) % data.length) {
swap(data[i], data[swapPos++]);
}
}
size_t sentinelPos = data.length;
for(size_t i = swapPos; i < sentinelPos;) {
if(data[i] == sentinel) {
swap(data[i], data[--sentinelPos]);
} else {
i++;
}
}
dataIn = dataIn[0..sentinelPos + start + 1];
uniqueInPlaceImpl(dataIn, start + swapPos + 1);
}