Bunu daha önce bir kez SO'da yayınladım, ancak burada yeniden üreteceğim çünkü oldukça havalı. Yerleşik bir hash gibi bir şey inşa ederek hashing kullanır. Koltuk altı uzayda O (1) olması garantilidir (özyineleme bir kuyruk çağrısıdır) ve tipik olarak O (N) zaman karmaşıklığıdır. Algoritma aşağıdaki gibidir:
- Dizinin ilk elemanını alın, bu nöbetçi olacaktır.
- Dizinin geri kalanını mümkün olduğunca yeniden sıralayın, her öğe kendi hash değerine karşılık gelen konumda olsun. Bu adım tamamlandığında, kopyalar keşfedilecektir. Onları nöbetçiye eşit ayarlayın.
- Dizinin karma değerine eşit olduğu tüm öğeleri dizinin başlangıcına taşı.
- Dizinin ilk öğesi dışında sentinele eşit olan tüm öğeleri dizinin sonuna taşı.
- Doğru şekilde hash edilmiş öğeler ile yinelenen öğeler arasında kalan, bir çarpışma nedeniyle hash'lerine karşılık gelen dizine yerleştirilemeyen öğeler olacaktır. Bu unsurlarla başa çıkmak için tekrarlayın.
Karma işleminde patolojik bir senaryo olmaması koşuluyla, bunun O (N) olduğu gösterilebilir: Yineleme olmasa bile, öğelerin yaklaşık 2 / 3'ü her özyinelemede ortadan kaldırılacaktır. Her özyineleme seviyesi O (n) 'dir, burada küçük n kalan elemanların miktarıdır. Tek sorun, pratikte, birkaç kopya, yani çok sayıda çarpışma olduğunda hızlı sıralamadan daha yavaş olmasıdır. Bununla birlikte, çok sayıda kopya olduğunda, inanılmaz derecede hızlıdır.
Düzenleme: D'nin mevcut uygulamalarında, hash_t 32 bittir. Bu algoritmayla ilgili her şey, tam 32-bit uzayda çok az karma çarpışma olacağını varsayar. Bununla birlikte, çarpışmalar modül uzayında sıklıkla meydana gelebilir. Ancak bu varsayım, makul büyüklükteki herhangi bir veri kümesi için büyük olasılıkla doğru olacaktır. Anahtar 32 bitten küçükse veya buna eşitse, kendi karması olabilir, yani tam 32 bit alanda bir çarpışma imkansızdır. Daha büyükse, sorun olması için 32 bit bellek adres alanına yeterince sığdıramazsınız. Veri kümelerinin daha büyük olabileceği 64 bitlik D uygulamalarında hash_t'nin 64 bit'e çıkarılacağını varsayıyorum. Dahası, eğer bunun bir sorun olduğu kanıtlanırsa, her özyineleme seviyesinde karma işlevi değiştirilebilir.
İşte D programlama dilinde bir uygulama:
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);
}