Performance 是否使用最少的辅助内存删除重复项?
在腋窝内存使用率必须降至最低(最好小到甚至不需要任何堆分配)的约束下,从数组中删除重复项的最有效方法是什么?排序似乎是显而易见的选择,但这显然不是渐进有效的。是否有更好的算法可以就地或接近就地完成?如果排序是最好的选择,那么什么样的排序最适合这种情况?如果对数组进行排序,仍然需要另一个过程来删除重复项,因此最坏情况下的复杂度是O(NN)(假设快速排序),或者使用Shellsort的复杂度是O(Nsqrt(N)) 您可以通过简单地扫描数组中的每个元素来实现O(N*N),并在执行时删除重复项 以下是Lua中的一个示例:Performance 是否使用最少的辅助内存删除重复项?,performance,algorithm,optimization,sorting,hash,Performance,Algorithm,Optimization,Sorting,Hash,在腋窝内存使用率必须降至最低(最好小到甚至不需要任何堆分配)的约束下,从数组中删除重复项的最有效方法是什么?排序似乎是显而易见的选择,但这显然不是渐进有效的。是否有更好的算法可以就地或接近就地完成?如果排序是最好的选择,那么什么样的排序最适合这种情况?如果对数组进行排序,仍然需要另一个过程来删除重复项,因此最坏情况下的复杂度是O(NN)(假设快速排序),或者使用Shellsort的复杂度是O(Nsqrt(N)) 您可以通过简单地扫描数组中的每个元素来实现O(N*N),并在执行时删除重复项 以下是
function removedups (t)
local result = {}
local count = 0
local found
for i,v in ipairs(t) do
found = false
if count > 0 then
for j = 1,count do
if v == result[j] then found = true; break end
end
end
if not found then
count = count + 1
result[count] = v
end
end
return result, count
end
将辅助内存使用量保持在最低限度,最好的办法是进行有效的排序以使它们有序,然后使用FROM和to索引对数组进行单次遍历 每次通过循环时,都要推进FROM索引。仅当关键点与最后一个不同时,才将元素从复制到(和增量复制到)
使用快速排序,最后一次的排序将平均为O(n-log-n)和O(n)。如果没有气泡排序之类的东西,我看不到任何方法可以做到这一点。当您找到一个重复时,您需要减少数组的长度。快速排序不是为数组大小的更改而设计的 这个算法总是O(n^2),但它也几乎不使用额外的内存——堆栈或堆
// returns the new size
int bubblesqueeze(int* a, int size) {
for (int j = 0; j < size - 1; ++j) {
for (int i = j + 1; i < size; ++i) {
// when a dupe is found, move the end value to index j
// and shrink the size of the array
while (i < size && a[i] == a[j]) {
a[i] = a[--size];
}
if (i < size && a[i] < a[j]) {
int tmp = a[j];
a[j] = a[i];
a[i] = tmp;
}
}
}
return size;
}
//返回新的大小
int bubblesqueze(int*a,int size){
对于(int j=0;j
如果您有两个不同的变量来遍历数据,而不是一个变量,那么您可以通过忽略数据集中当前已存在的所有副本来限制输出
显然,C语言中的这个例子并不是一个有效的排序算法,但它只是一个从一个角度来看问题的例子
您也可以先盲目地对数据进行排序,然后重新定位数据以删除DUP,但我不确定这样会更快
#define ARRAY_LENGTH 15
int stop = 1;
int scan_sort[ARRAY_LENGTH] = {5,2,3,5,1,2,5,4,3,5,4,8,6,4,1};
void step_relocate(char tmp,char s,int *dataset)
{
for(;tmp<s;s--)
dataset[s] = dataset[s-1];
}
int exists(int var,int *dataset)
{
int tmp=0;
for(;tmp < stop; tmp++)
{
if( dataset[tmp] == var)
return 1;/* value exsist */
if( dataset[tmp] > var)
tmp=stop;/* Value not in array*/
}
return 0;/* Value not in array*/
}
void main(void)
{
int tmp1=0;
int tmp2=0;
int index = 1;
while(index < ARRAY_LENGTH)
{
if(exists(scan_sort[index],scan_sort))
;/* Dismiss all values currently in the final dataset */
else if(scan_sort[stop-1] < scan_sort[index])
{
scan_sort[stop] = scan_sort[index];/* Insert the value as the highest one */
stop++;/* One more value adde to the final dataset */
}
else
{
for(tmp1=0;tmp1<stop;tmp1++)/* find where the data shall be inserted */
{
if(scan_sort[index] < scan_sort[tmp1])
{
index = index;
break;
}
}
tmp2 = scan_sort[index]; /* Store in case this value is the next after stop*/
step_relocate(tmp1,stop,scan_sort);/* Relocated data already in the dataset*/
scan_sort[tmp1] = tmp2;/* insert the new value */
stop++;/* One more value adde to the final dataset */
}
index++;
}
printf("Result: ");
for(tmp1 = 0; tmp1 < stop; tmp1++)
printf( "%d ",scan_sort[tmp1]);
printf("\n");
system( "pause" );
}
#定义数组长度15
int stop=1;
int scan_sort[ARRAY_LENGTH]={5,2,3,5,1,2,5,4,3,5,4,8,6,4,1};
无效步骤重新定位(字符tmp、字符s、int*数据集)
{
对于(;tmp变量)
tmp=stop;/*值不在数组中*/
}
返回0;/*值不在数组中*/
}
真空总管(真空)
{
int-tmp1=0;
int-tmp2=0;
int指数=1;
while(索引<数组长度)
{
如果(存在(扫描排序[索引],扫描排序))
;/*忽略最终数据集中当前的所有值*/
否则如果(扫描排序[stop-1]<扫描排序[index])
{
扫描排序[停止]=扫描排序[索引];/*插入值作为最高值*/
停止+++;/*再向最终数据集添加一个值*/
}
其他的
{
对于(tmp1=0;tmp1我将回答我自己的问题,因为在发布之后,我想出了一个非常聪明的算法来实现这一点。它使用散列,构建类似于散列集的东西。它保证在腋窝空间中是O(1)(递归是尾部调用),并且通常是O(N)时间复杂度。算法如下:
以数组的第一个元素为例,这将是哨兵
尽可能对数组的其余部分重新排序,使每个元素都位于其哈希对应的位置。此步骤完成后,将发现重复项。将它们设置为sentinel
将索引等于哈希的所有元素移动到数组的开头
将所有与sentinel相等的元素(数组的第一个元素除外)移动到数组的末尾
正确散列的元素和重复元素之间剩下的是由于冲突而无法放置在与其散列对应的索引中的元素。递归以处理这些元素
如果散列中没有病理情况,则可以显示为O(N):
即使没有重复项,在每次递归中也会消除大约2/3的元素。每个递归级别都是O(n)其中,小n是剩余元素的数量。唯一的问题是,在实践中,当重复数很少时,即大量碰撞时,它比快速排序慢。然而,当有大量重复时,它的速度惊人地快
编辑:在当前的D实现中,哈希值为32位。关于此算法的所有内容都假设在整个32位空间中几乎没有(如果有的话)哈希冲突。但是,冲突可能在模空间中频繁发生。但是,对于任何大小合理的数据集,此假设都很可能成立。如果小于或等于32位,它可以是它自己的散列,这意味着在完整的32位空间中不可能发生冲突。如果它更大,您就无法将足够多的数据放入32位内存地址空间,这将成为一个问题。我假设在D的64位实现中,散列将增加到64位,其中数据集可以更大。此外,如果这确实被证明是一个问题,那么可以在每个递归级别更改哈希函数
以下是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);
}
void uniqueInPlace(T)(参考T[]数据输入){
uniqueInPlaceImpl(数据输入,0);
}
无效uniqueInPlaceImpl(T)(参考T[]数据输入,大小\u T开始){
如果(dataIn.length-开始<2)
返回;
不变T sentinel=dataIn[start];
T[]数据=dataIn[start+1..$]