Algorithm 在数据集中检测重复数据的算法,该数据集太大,无法完全加载到内存中
这个问题有最优的解决方案吗 描述一种在一百万个电话号码的文件中查找重复项的算法。该算法在运行时只有两兆字节的可用内存,这意味着您无法同时将所有电话号码加载到内存中 我的“朴素”解决方案是一个O(n^2)解决方案,它对值进行迭代,并将文件分块加载,而不是一次全部加载 对于i=0到999999Algorithm 在数据集中检测重复数据的算法,该数据集太大,无法完全加载到内存中,algorithm,duplicates,Algorithm,Duplicates,这个问题有最优的解决方案吗 描述一种在一百万个电话号码的文件中查找重复项的算法。该算法在运行时只有两兆字节的可用内存,这意味着您无法同时将所有电话号码加载到内存中 我的“朴素”解决方案是一个O(n^2)解决方案,它对值进行迭代,并将文件分块加载,而不是一次全部加载 对于i=0到999999 string currentVal = get the item at index i for j = i+1 to 999,999 if (j - i mod fileChunkSize == 0)
string currentVal = get the item at index i
for j = i+1 to 999,999
if (j - i mod fileChunkSize == 0)
load file chunk into array
if data[j] == currentVal
add currentVal to duplicateList and exit for
必须有另一种情况,即您可以以真正独特的方式加载整个数据集,并验证数字是否重复。有人有吗?如果可以存储临时文件,则可以将文件分块加载,对每个块进行排序,将其写入文件,然后遍历这些块并查找重复项。通过将某个数字与文件中的下一个数字以及每个数据块中的下一个数字进行比较,可以很容易地判断该数字是否重复。然后移动到所有块中下一个最小的数字,并重复,直到数字用完为止
由于排序,您的运行时为O(n log n)。将文件分成M个块,每个块都足够大,可以在内存中进行排序。在内存中对它们进行排序 对于每组两个区块,我们将对两个区块执行合并排序的最后一步,以生成一个更大的区块(c_1+c_2)(c_3+c_4)。。(c_m-1+c_m) 指向磁盘上c_1和c_2上的第一个元素,创建一个新文件(我们称之为c_1+2) 如果c_1指向元素的数字小于c_2指向元素的数字,则将其复制到c_1+2并指向c_1的下一个元素。
否则,将c_2的指向元素复制到并指向c_2的下一个元素 重复上一步,直到两个阵列都为空。您只需要使用内存中的空间来保存指向数字的两个指针。在这个过程中,如果您遇到指向相等元素的c_1和c_2,那么您已经找到了一个副本-您可以复制它两次并递增两个指针 生成的m/2数组可以以相同的方式递归合并——需要记录这些合并步骤的日志(m)才能生成正确的数组。将每个数字与其他数字进行比较,以找到重复的数字
或者,@Evgeny Kluev提到的一个快速而肮脏的解决方案是制作一个bloom过滤器,这个过滤器尽可能大,可以合理地放入内存中。然后,您可以列出未通过bloom过滤器的每个元素的索引,并再次循环文件,以便测试这些成员的重复性。我喜欢@airza解决方案,但可能还有另一种算法需要考虑:可能一百万个电话号码无法一次加载到内存中,因为它们的表达效率低下,即每个电话号码使用的字节数超过了需要的字节数。在这种情况下,通过对电话号码进行散列并将散列存储在(散列)表中,您可能会得到一个有效的解决方案。哈希表支持字典操作(如中的),让您可以轻松找到重复项
更具体地说,如果每个电话号码是13个字节(例如格式为
(NNN)NNN-NNNN
)的字符串),则该字符串表示十亿个数字中的一个。作为一个整数,它可以存储在4个字节中(而不是字符串格式的13个字节)。然后我们可能能够将这个4字节的“散列”存储在一个散列表中,因为现在我们的10亿个散列数字占用的空间相当于3.08亿个数字,而不是10亿个。排除不可能的数字(区号000
,555
等中的所有内容)可能会让我们进一步减小散列大小。我认为Airza的解决方案正朝着一个好的方向发展,但由于排序不是您想要的,而且成本更高,您可以结合angelatlarge的方法执行以下操作:
取一个大小为M/2的内存块C
获取块Ci
- 区块的大小取决于内存,其大小应为M/2。 这是将加载文件块的内存大小,因为整个文件太大,无法加载到内存中
- 这会留下另一个M/2字节来保留哈希表2和/或重复的列表1
- 因此,应该有N/(M/2)个块,每个块的大小| C |=M/2
- 运行时间将是块的数量(N/(M/2)),乘以每个块的大小| C |(或M/2)。总的来说,这应该是线性的(加上或减去从一个块更改到另一个块的开销,这就是为什么最好的描述方式是O((N/M)*|C |)
b、 迭代每个元素,测试并设置是否存在O(1)将对插入和查找进行哈希运算。
c、 如果该元素已经存在
void DeleteDuplicate(File file, int numberOfPhones, int maxMemory)
{
//Assume each 1'000'000 number of phones that fit in 32-bits.
//Assume 2MB of memory
//Assume that arrays of bool are coalesced into 8 bools per byte instead of 1 bool per byte
int chunkSize = maxMemory / 2; // 2MB / 2 / 4-byes per int = 1MB or 256K integers
//numberOfPhones-bits. C++ vector<bool> for example would be space efficient
// Coalesced-size ~= 122KB | Non-Coalesced-size (worst-case) ~= 977KB
bool[] exists = new bool[numberOfPhones];
byte[] numberData = new byte[chunkSize];
int fileIndex = 0;
int bytesLoaded;
do //O(chunkNumber)
{
bytesLoaded = file.GetNextByes(chunkSize, /*out*/ numberData);
List<int> toRemove = new List<int>(); //we still got some 30KB-odd to spare, enough for some 6 thousand-odd duplicates that could be found
for (int ii = 0; ii < bytesLoaded; ii += 4)//O(chunkSize)
{
int phone = BytesToInt(numberData, ii);
if (exists[phone])
toRemove.push(ii);
else
exists[phone] = true;
}
for (int ii = toRemove.Length - 1; ii >= 0; --ii)
numberData.removeAt(toRemove[ii], 4);
File.Write(fileIndex, numberData);
fileIndex += bytesLoaded;
} while (bytesLoaded > 0); // while still stuff to load
}