请教大家一个算法题

chu009 2011-10-13 01:30:02
我面试的时候曾经遇到的一道算法题,写在这大家一起讨论下,争取找出个好的解决方案。
题目是这样的:有两个文件,A文件里有大量的电话号码,上亿条,里面有少量的重复号码,要求把A文件里面的重复号码去掉然后存入B文件。

我的解决方法:建立一个二叉排序树存储所有A文件中不重复的电话号码信息。我从A文件每次读取一条电话号码,然后插入到二叉树结构中,如果这条记录已经存在则不进行插入。最后二叉排序树中包含了所有A文件中不重复的电话号码信息。改进的方式是在插入过程中将二叉排序树调整为二叉平衡树。
将二叉树按某种方式进行遍历,将电话号码信息依次写入B文件。如果按中序遍历,则B文件中的电话号码是有序的。

这里我建立二叉树的时间复杂度是O(nlgn),写入B文件O(n),但是二叉树节点需要存储电话号码信息就要占用内存,上亿节点占用多大的内存啊,这是对方给我提出的challenge,我没给出更好的方法。

我的出发点是降低时间复杂度,但是没有解决内存占用问题。
但是不把A文件中的节点存入内存,假如这样做:将A文件一次取一条记录,然后在B文件从头至尾查找是否有重复的,如果没有重复的加入到B文件末尾,然后A文件再取下一条,重复此过程。
这样虽然节省了内存,但是时间复杂度为O(N*N)(上亿条记录,这个时间复杂度是很恐怖的),而且每插入一条就要把B文件读取一遍也是非常耗时的。

我没有想出更好的方法,大家帮忙看看吧。
...全文
306 22 打赏 收藏 转发到动态 举报
写回复
用AI写文章
22 条回复
切换为时间正序
请发表友善的回复…
发表回复
jernymy 2011-10-14
  • 打赏
  • 举报
回复
[Quote=引用 14 楼 chu009 的回复:]
500000000/64是什么意思?
引用 13 楼 jernymy 的回复:

想到《编程珠玑2》中的例子,使用位图模式

暂时考虑到的方案,前提是文件中的电话号码均为手机号码
范围在13000000000 - 1899999999

则for的循环范围是(1899999999-13000000000) = 6000000000
(0-6000000000)

比如5亿条,……
[/Quote]

一个int64的变量占用64个bit,则500000000bit共占用500000000/64个int64个变量
所以使用781250个int64的数组就可以表示所有的500000000个bit位。
MTBlue 2011-10-14
  • 打赏
  • 举报
回复
最差情况999*N,遗憾的是如果是最差情况,且1亿条的记录是11位手机号码那么假设全部都是137********的情况,分页问题还是没有解决。。。
期待解答
MTBlue 2011-10-14
  • 打赏
  • 举报
回复
觉得问题的精髓在于将一亿多条记录分页,并使所分出来的任意两个页面中没有重复号码
所以我觉得,只要在楼主一开始的解法上加一个条件,比如说13789345678,12978653409,13789013476,13289564312,这样四个号码,根据前三位(或某几位,根据实际情况而定,如内存大小,号码是否有规律)作为判断依据,前三位完全一致的号码(例如137系列)全部从文件A删除,读入内存进行二叉树排序,或者hash,去除重复,完成后写入文件B,直至A文件被分析完。
以前三位为例,复杂度 999*N
nameqwe 2011-10-14
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 chu009 的回复:]

用hash时间复杂度为O(n),不错。
但是把A分为10个文件来处理,如果彼此之间包含重复号码就不能达到去重目的了。
另外一亿条占内存100000000*4B/1024=381470MB
而不是100000000*4B/(1024*1024) =381.47MB。
引用 8 楼 nameqwe 的回复:
解决内存占用问题 同时保持时间复杂度为0(n)

同样用hash来处理,但由于……
[/Quote]


分文件的思想你还没理解啊。
chu009 2011-10-14
  • 打赏
  • 举报
回复
总结下,两方面考虑。
1时间,首先要比较的数据必须存在内存中,这样读取数据才比较快,一般用哈希表存储。它的好处是快速准确,查找某个数据是否存在的时间复杂度为O(1)或常量级,A文件遍历一遍为O(N)。缺点是费存储空间。
空间(内存),改进哈希表,使用布隆过滤器,它只需要哈希表 1/8 到 1/4 的大小就能解决同样的问题,这点大家可以自己去查看相关资料。当判断一个元素是否位于布隆过滤器时不需要遍历查找,而是通过一定的算法来判断,当然每次执行这个算法也需要一些时间。

但综合来讲还是不错的,主要用于比如在字处理软件中,需要检查一个英语单词是否拼写正确(也就是要判断它是否在已知的字典中);在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上;在网络爬虫里,一个网址是否被访问过,邮箱服务器拦截垃圾邮件等等。

这个算法zhang98722兄弟提到了,非常感谢。还有兄弟提出的位图思想应该差不多,可以做进一步讨论。非常感谢大家的参与。

chu009 2011-10-14
  • 打赏
  • 举报
回复
500000000/64是什么意思?
[Quote=引用 13 楼 jernymy 的回复:]

想到《编程珠玑2》中的例子,使用位图模式

暂时考虑到的方案,前提是文件中的电话号码均为手机号码
范围在13000000000 - 1899999999

则for的循环范围是(1899999999-13000000000) = 6000000000
(0-6000000000)

比如5亿条,则使用空间500000000/64 = 781250*8Bytes/1024/1024……
[/Quote]
chu009 2011-10-14
  • 打赏
  • 举报
回复
分文件处理思想不错,时间复杂度不变(多了一遍读写过程,另外需要些外部存储空间)可以节约内存。我没有质疑这个思想,相反的增长见识了。
我的意思是应该先保证各个文件之间不包含重复的记录呵呵。
[Quote=引用 16 楼 nameqwe 的回复:]

引用 10 楼 chu009 的回复:

用hash时间复杂度为O(n),不错。
但是把A分为10个文件来处理,如果彼此之间包含重复号码就不能达到去重目的了。
另外一亿条占内存100000000*4B/1024=381470MB
而不是100000000*4B/(1024*1024) =381.47MB。
引用 8 楼 nameqwe 的回复:
解决内存占用问题 同时保持时间复杂度……
[/Quote]
chu009 2011-10-14
  • 打赏
  • 举报
回复
分页或分文件是不错的想法,前面nameqwe兄弟也提到了。
mtblue兄弟直接说出了使所分出来的任意两个页面中没有重复号码的做法,不错。

[Quote=引用 17 楼 mtblue 的回复:]

觉得问题的精髓在于将一亿多条记录分页,并使所分出来的任意两个页面中没有重复号码
所以我觉得,只要在楼主一开始的解法上加一个条件,比如说13789345678,12978653409,13789013476,13289564312,这样四个号码,根据前三位(或某几位,根据实际情况而定,如内存大小,号码是否有规律)作为判断依据,前三位完全一致的号码(例如137系列)全部从文件A删除,读入内存进行二……
[/Quote]
czstemp 2011-10-14
  • 打赏
  • 举报
回复
这种题考了无数遍了,用bit map
jernymy 2011-10-13
  • 打赏
  • 举报
回复
想到《编程珠玑2》中的例子,使用位图模式

暂时考虑到的方案,前提是文件中的电话号码均为手机号码
范围在13000000000 - 1899999999

则for的循环范围是(1899999999-13000000000) = 6000000000
(0-6000000000)

比如5亿条,则使用空间500000000/64 = 781250*8Bytes/1024/1024 ≈ 7.45M

定义int64[781250],VC中__int64, linux上long long,初始化清0。

读取过程,O(N)
1. 读取A文件中数据。
2. 转换成int64,并保存到数组中,其实就是置对应数字的位为1。

写入过程: O(N)
读取数组中内容,如果该为为1,写入到B文件中。


写入后的B文件中电话号码是顺序的,和A文件中的顺序可能不一致。

总过需要O(2N)
alphaxiang 2011-10-13
  • 打赏
  • 举报
回复
99999999999<10> = 0100 1000 0111 0110 1110 0111 1111 1111
如果是11位的电话号码的话,最笨的方法直接用1G的内存做位图
chu009 2011-10-13
  • 打赏
  • 举报
回复
呵呵我算错了。1KB=1024B, 1MB=1024*1024B

[Quote=引用 8 楼 nameqwe 的回复:]
解决内存占用问题 同时保持时间复杂度为0(n)

同样用hash来处理,但由于内存原因,可以分批处理。这里需要额外的磁盘空间。

1,电话号码也是数字,必然在数字上有个最大值。假设为MAX = 999999999.
2,将所有电话分为N份(n)可以按照实际情况分配。我们目前假设分为10份。
3 将遍历A文件,将电话号码在 0=< <MAX/10 之间的写在文件z1中,MAX/10 =<……
[/Quote]
chu009 2011-10-13
  • 打赏
  • 举报
回复
用hash时间复杂度为O(n),不错。
但是把A分为10个文件来处理,如果彼此之间包含重复号码就不能达到去重目的了。
另外一亿条占内存100000000*4B/1024=381470MB
而不是100000000*4B/(1024*1024) =381.47MB。
[Quote=引用 8 楼 nameqwe 的回复:]
解决内存占用问题 同时保持时间复杂度为0(n)

同样用hash来处理,但由于内存原因,可以分批处理。这里需要额外的磁盘空间。

1,电话号码也是数字,必然在数字上有个最大值。假设为MAX = 999999999.
2,将所有电话分为N份(n)可以按照实际情况分配。我们目前假设分为10份。
3 将遍历A文件,将电话号码在 0=< <MAX/10 之间的写在文件z1中,MAX/10 =<……
[/Quote]
chu009 2011-10-13
  • 打赏
  • 举报
回复
我建立的二叉树本身就没有重复节点了,直接写到B文件就可以了。如果在文件中进行搜索,时间复杂度就是
O(N*N)了。再有还是没有解决内存问题,因为这棵二叉树有上亿节点(在内存而不是文件中)。

[Quote=引用 4 楼 utiao 的回复:]
将二叉树写到文件中。在文件中进行搜索。每个电话号码的长度可以设置为定长,这样可以快速定位节点所在的位置。
[/Quote]
nameqwe 2011-10-13
  • 打赏
  • 举报
回复
解决内存占用问题 同时保持时间复杂度为0(n)

同样用hash来处理,但由于内存原因,可以分批处理。这里需要额外的磁盘空间。

1,电话号码也是数字,必然在数字上有个最大值。假设为MAX = 999999999.
2,将所有电话分为N份(n)可以按照实际情况分配。我们目前假设分为10份。
3 将遍历A文件,将电话号码在 0=< <MAX/10 之间的写在文件z1中,MAX/10 =< 2*MAX/10之间的写在z2文件中。..... 该过程0(n).

4,读取文件z1,读一个电话,先查hash,若在hash中就读下一条,若不在则写入文件B,并插入hash。直到文件z1结束。

5,清空上面的hash重新来读文件z2 .....

6 读完z10 文件。结束

时间复杂度 0(n) 内存 382M/10 = 38M内存。
nameqwe 2011-10-13
  • 打赏
  • 举报
回复
忘记说了 时间复杂度为 o(n)
nameqwe 2011-10-13
  • 打赏
  • 举报
回复
既然读在内存中 当然用hash。假设每个电话号码用unsigned int 开保存 100000000*4B/(1024*1024) =381.47MB。 这么点内存应该够。

由于 读入后就写出基本上不用存value的空间。

1,读入一条,先查hash,若存在则 读下一条。若不存在,插入到hash表中并写出倒文件B.

2,循环读直到文件A没有了。
冰山来客123499 2011-10-13
  • 打赏
  • 举报
回复
内存中可以保持一个首位或数位号码的一个树,以加快查找
冰山来客123499 2011-10-13
  • 打赏
  • 举报
回复
将二叉树写到文件中。在文件中进行搜索。每个电话号码的长度可以设置为定长,这样可以快速定位节点所在的位置。
zhang98722 2011-10-13
  • 打赏
  • 举报
回复
布隆过滤器
你可以参考一下
然后可以自己参考那个做一个修改
减少错误几率
算是效率比较高的一种方法吧

用这玩意做过邮箱的
还不错
电话号码估计比较麻烦
不过通过算法把电话号码延长的话能够降低错误几率
加载更多回复(2)

590

社区成员

发帖
与我相关
我的任务
社区描述
提出问题
其他 技术论坛(原bbs)
社区管理员
  • community_281
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧