Tôi đã đăng bài này một lần trước đây trên SO, nhưng tôi sẽ sao chép nó ở đây vì nó khá hay. Nó sử dụng băm, xây dựng một cái gì đó giống như một bộ băm tại chỗ. Nó được đảm bảo là O (1) trong không gian nách (đệ quy là một lệnh gọi đuôi), và thường là độ phức tạp thời gian O (N). Thuật toán như sau:
- Lấy phần tử đầu tiên của mảng, đây sẽ là trạm gác.
- Sắp xếp lại phần còn lại của mảng, càng nhiều càng tốt, sao cho mỗi phần tử ở vị trí tương ứng với hàm băm của nó. Khi bước này hoàn thành, các bản sao sẽ được phát hiện. Đặt chúng bằng sentinel.
- Di chuyển tất cả các phần tử có chỉ số bằng băm đến đầu mảng.
- Di chuyển tất cả các phần tử bằng sentinel, ngoại trừ phần tử đầu tiên của mảng, đến cuối mảng.
- Những gì còn lại giữa các phần tử được băm đúng cách và các phần tử trùng lặp sẽ là những phần tử không thể được đặt trong chỉ mục tương ứng với hàm băm của chúng do xung đột. Định kỳ để đối phó với các yếu tố này.
Điều này có thể được chứng minh là O (N) với điều kiện không có tình huống bệnh lý nào trong phép băm: Ngay cả khi không có bản sao, khoảng 2/3 phần tử sẽ bị loại bỏ ở mỗi lần đệ quy. Mỗi mức đệ quy là O (n) trong đó n nhỏ là lượng phần tử còn lại. Vấn đề duy nhất là, trong thực tế, nó chậm hơn so với sắp xếp nhanh khi có ít bản sao, tức là có nhiều va chạm. Tuy nhiên, khi có một lượng lớn các bản sao, nó nhanh đến kinh ngạc.
Chỉnh sửa: Trong các triển khai hiện tại của D, hash_t là 32 bit. Mọi thứ về thuật toán này giả định rằng sẽ có rất ít, nếu có, xung đột băm trong không gian 32 bit đầy đủ. Tuy nhiên, va chạm có thể xảy ra thường xuyên trong không gian mô đun. Tuy nhiên, giả định này rất có thể sẽ đúng với bất kỳ tập dữ liệu nào có kích thước hợp lý. Nếu khóa nhỏ hơn hoặc bằng 32 bit, nó có thể là hàm băm của chính nó, nghĩa là không thể xảy ra xung đột trong không gian 32 bit đầy đủ. Nếu nó lớn hơn, đơn giản là bạn không thể lắp đủ chúng vào không gian địa chỉ bộ nhớ 32-bit thì đó là một vấn đề. Tôi giả sử hash_t sẽ được tăng lên 64 bit trong các triển khai 64 bit của D, trong đó tập dữ liệu có thể lớn hơn. Hơn nữa, nếu điều này từng được chứng minh là một vấn đề, người ta có thể thay đổi hàm băm ở mỗi mức đệ quy.
Đây là cách triển khai bằng ngôn ngữ lập trình 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);
}