一道百度试题,大家来试试

时间:2008-01-16 11:14:23  来源:  作者:

考虑一个在线好友系统。系统为每个用户维护一个好友列表,列表限制最多可以有500个好友,
好友必须是这个系统中的其它用户。好友关系是单向的,用户B是用户A的好友,但A不一定是B的好友。

用户以ID形式表示,现给出好友列表数据的文本形式如下:
1  3,5,7,67,78,3332
2  567,890
31  1,66
14    567
78  10000

每行数据有两列,第一列为用户ID,第二列为其好友ID,不同ID间用”,”分隔,ID升序排列。列之间用”t”分隔。


要求:
请设计合适的索引数据结构,来完成以下查询:
给定用户A和B,查询A和B之间是否有这样的关系:B是A的二维好友(好友的好友)。
如上例中,10000为1的二维好友,因为78为1的好友,10000为78的好友。

详细说明自己的解题思路,说明自己实现的一些关键点。
并给出实现的伪代码实现建立索引过程和查询过程,并说明空间和时间复杂度。

限制:
用户数量不超过1000万,平均50个好友。



 福瑞哈哥 回复于:2007-07-05 17:08:08

每个用户是排序数组,用户id就是数组索引。
用户好友用排序数组,初始长度50,以后根据情况realloc扩充。
查找就简单了,定位用户A,遍历A的好友,af1,af2,...,afn,
再查找afx的好友看有没有B。完成。
内存也就使用几百M。
内存可能不止这么多,麻烦。

[ 本帖最后由 福瑞哈哥 于 2007-7-5 17:36 编辑 ]


 feiyang21687 回复于:2007-07-05 17:13:05

这样算法的时间复杂度O(n^2),太慢了。


 福瑞哈哥 回复于:2007-07-05 17:17:38

引用:原帖由 feiyang21687 于 2007-7-5 17:13 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7011800&ptid=958272]
这样算法的时间复杂度O(n^2),太慢了。 



如果能放在内存中,如果你能做到比这还快,还干嘛去百度做题啊?
如果不嫌罗嗦,就使用双向指针吧。每个用户把视自己为朋友的用户也列出来。不过我是不会这么做的。

啊,双向指针也并不快。

[ 本帖最后由 福瑞哈哥 于 2007-7-5 17:30 编辑 ]


 ivhb 回复于:2007-07-05 17:24:42

引用:原帖由 feiyang21687 于 2007-7-5 17:13 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7011800&ptid=958272]
这样算法的时间复杂度O(n^2),太慢了。 




用qsort+bsearch,怎么得出的那个复杂度?


 converse 回复于:2007-07-05 17:27:10

福瑞的算法,最坏的情况下要把某ID的所有好友的好友都给查一遍的.


 福瑞哈哥 回复于:2007-07-05 17:27:22

引用:原帖由 ivhb 于 2007-7-5 17:24 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7011869&ptid=958272]



用qsort+bsearch,怎么得出的那个复杂度? 



同意,好友是顺序遍历是N,间接好友查找就不需要N了。
我觉得设计有一点是不能把非重要或者非常用功能当作优化重点。


 福瑞哈哥 回复于:2007-07-05 17:32:23

引用:原帖由 converse 于 2007-7-5 17:27 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7011885&ptid=958272]
福瑞的算法,最坏的情况下要把某ID的所有好友的好友都给查一遍的. 



没错,不过不是O(N^2)。


 jigloo 回复于:2007-07-05 17:45:21

http://www.wespoke.com/archives/001077.html


 spibit 回复于:2007-07-05 17:55:57

[table=95%][tr][td][font=FixedSys]typedef struct _user
{
  int userid;

  // 好友数目
  int count; 

  // 好友列表数组,最后一个为0表示结束。
  int *friends;
}user_t;

// user_t指针的数组,假设数据已经全部读进来了,users数组最后一个为0,表示结束。
user_t *users[];

// 这个函数,获取users数组里面 userid匹配的 user,
// 已经排序好的,折中搜索?复杂度ln(n),实现略。
user_t  * users_get_user(user_t **users, int userid);

// 是好友吗?看在不在好友列表里面了,
// 已经排序好的,折中搜索?复杂度ln(n),实现略。
int user_is_friend(user_t * user, int userid);

// B是A好友的好友?
int yes = 0;
int * friendsA = users_get_user(users, A)->friends;
user_t  * friendsAA;
while (0 != friendsA)
{
  friendsAA = users_get_user(users, *friendsA);
  if (0 == user_is_friend(friendsAA, B))
  {
    yes = 1;
    break;
  }
  friendsA ++;
}[/font][/td][/tr][/table]

[ 本帖最后由 spibit 于 2007-7-5 18:07 编辑 ]


 福瑞哈哥 回复于:2007-07-05 18:00:22

引用:原帖由 spibit 于 2007-7-5 17:55 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012103&ptid=958272]
typedef struct _user
{
  int userid;

  // 好友数目
  int count; 

  // 好友列表数组,最后一个为0表示结束。
  int *friends;
}user_t;

// user_t指针的数组,假设数据已经全部读进来了,use ... 



我支持你。          因为这也是我的想法。 :)


 BILLJEFF 回复于:2007-07-05 18:45:44

建立图,邻接链表,然后DFS,对查询结果进行缓存,存储最近N次查询(因为目的是为了查询)。


 chenzhanyiczy 回复于:2007-07-05 19:09:09

Actually, I think there should be able to use Linker table plus Binary . Sth like this :

struct binary
{
  struct binary *lbinary;
  struct binary *rbinary;
  int val;
}

struct linkt
{
  int val;
  struct linkt *nlink;
}

struct a
{
  struct linkt *p_link;
  struct binary *p_binary;
}

Dunno right !!??


 Edengundam 回复于:2007-07-05 19:11:43

引用:原帖由 福瑞哈哥 于 2007-7-5 18:00 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012126&ptid=958272]


我支持你。          因为这也是我的想法。 :) 




这个想法最直接. 其实给出的复杂度是总用户的复杂度, 既然使用了数组下标索引, 也就没有什么时间复杂度的问题.

这题关键是数据结构占用的内存数量. 以及每次查找A, B关系的时间开销.

这种直接的方法:

空间开销: 10,000,000 * 50 * 4 / 1,000,000 是2GB的内存占用. 
对于一个二维好友来说, 其搜索命中概率为 50 * 50 / 10, 000, 000  大约是万分之2.5的概率, 千分之0.25.
也就是说, 这种算法, 几乎每次都进行2500次检测. 也就是平均好友数量的二次方倍.

如果考虑实际环境内存为512M
那么需要对索引数据结构进行分块. 而对于这种常规的方法, 可能导致大量的swap交换. 一维好友产生的结果分布很散.

综上: 如何将数据的内聚性提升, 将可以缓解小内存时swap交换的概率(或者将部分索引存储到磁盘上, 总之目的是降低兑换的频率).
对于内存2GB情况, 因为搜索命中率较低, 因此降低这种N^2, 可以提高系统响应.


 Edengundam 回复于:2007-07-05 19:35:44

引用:原帖由 chenzhanyiczy 于 2007-7-5 19:09 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012389&ptid=958272]
Actually, I think there should be able to use Linker table plus Binary . Sth like this :

struct binary
{
  struct binary *lbinary;
  struct binary *rbinary;
  int val;
}

struct linkt
 ... 




你能给出用binary的思路吗..??

查找的时间相对平均一维好友数应该是O(n)吧...:)
可能咱俩思路一样


 chenzhanyiczy 回复于:2007-07-05 19:56:24

Maybe U & me both imagine are similar.

The Binary will store all member of each member of A user.

The Linnk will store all member of A user.


 福瑞哈哥 回复于:2007-07-05 20:14:49

引用:原帖由 Edengundam 于 2007-7-5 19:11 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012396&ptid=958272]



这个想法最直接. 其实给出的复杂度是总用户的复杂度, 既然使用了数组下标索引, 也就没有什么时间复杂度的问题.

这题关键是数据结构占用的内存数量. 以及每次查找A, B关系的时间开销.

这种直接的方法 ... 



对,内存大概要2G,这是麻烦的地方。不过你知道搜索引擎公司都是怎么做的吗? 如果一台机器放不下,那就用两台,把数据一分为二。或者一台机器两个进程,内存现在一般都是2G的,如果多进程可以把内存加到更多比如16G。


 Edengundam 回复于:2007-07-05 20:26:24

引用:原帖由 福瑞哈哥 于 2007-7-5 20:14 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012582&ptid=958272]


对,内存大概要2G,这是麻烦的地方。不过你知道搜索引擎公司都是怎么做的吗? 如果一台机器放不下,那就用两台,把数据一分为二。或者一台机器两个进程,内存现在一般都是2G的,如果多进程可以把内存加到更 ... 




我意思是应该提高查找二维好友速度...空间换时间


 福瑞哈哥 回复于:2007-07-05 20:30:02

引用:原帖由 Edengundam 于 2007-7-5 20:26 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012617&ptid=958272]



我意思是应该提高查找二维好友速度...空间换时间 



关注,愿闻其详。


 koolcoy 回复于:2007-07-05 20:47:43

我的算法:
扫描输入, 并且对于任意一个用户x, 计算下面两个集合:
1. 用户x的好友的id集合
2. 把x加为好友的id集合.
有了上面这两个集合, 对于任意要判断y是否是x的二维好友就简单了:
如果: (x的好友的id集合) 交 (把y加为好友的id集合) != 空集, 则y是x的二维好友.

注意: 因为上面算法的特点, 我们可以把每个用户的那两个集合保存到磁盘或数据库, 以提高效率, 这样即使内存很紧的计算机也不会很慢.

[ 本帖最后由 koolcoy 于 2007-7-5 20:52 编辑 ]


 chenzhanyiczy 回复于:2007-07-05 20:52:54

Good article ! Worth to attention.


 Edengundam 回复于:2007-07-05 21:12:19

引用:原帖由 koolcoy 于 2007-7-5 20:47 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012687&ptid=958272]
我的算法:
扫描输入, 并且对于任意一个用户x, 计算下面两个集合:
1. 用户x的好友的id集合
2. 把x加为好友的id集合.
有了上面这两个集合, 对于任意要判断y是否是x的二维好友就简单了:
如果: (x的好友的id集 ... 




bingo

和我想的一样:em02: 

帮你把分析部分加上:
存储空间开销: 平均每个用户的父好友(别人添加你的用户)为50, 平均每个用户的子好友(你添加其他用户)为50.
10,000,000 * 100 * 4 = 4GB

因为每次搜索时候, 对于A,B两个用户只需两次数组索引, 取得两个数组. 

对于两个有序数组(假设两数组元素数相近, 这里近似考虑为平均好友数), 时间开销为2*n. 时间复杂度O(n).
******
但是因为保存父好友可能存在一种极端情况, 导致某个数组元素(n)相对另一个数组(m)非常大. (n >> m)
这种情况下可以考虑使用复杂度为m*log(n)的算法.
用m的每个元素去n中进行binary search.
******这个特例的搜索优化是同学想到的...

最后, 可以考虑对于内存极小情况下512M的分析. 
因为, 内聚性增强, 因此每次最坏开销为读取两个分散在磁盘上的数据块. :em15:


不知道还有没有更好的数据结构...哈哈

[ 本帖最后由 Edengundam 于 2007-7-5 21:13 编辑 ]


 福瑞哈哥 回复于:2007-07-05 21:24:51

引用:原帖由 Edengundam 于 2007-7-5 21:12 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012745&ptid=958272]



bingo

和我想的一样:em02: 

帮你把分析部分加上:
存储空间开销: 平均每个用户的父好友(别人添加你的用户)为50, 平均每个用户的子好友(你添加其他用户)为50.
10,000,000 * 100 * 4 = 4GB

因为每 ... 



做反向索引,取A的好友集合,再取B的反向好友集合,然后比较两个集合。
我来说具体一点吧,两个有序集合N的比较时间不需要2*N,因为只需要比较两端是否重合就可以了。
但是如果要找出具体路径这种方法就不见得有多少优势了。

确实会快一些,不过每次都要维护这两个有序数组也挺麻烦的。


 koolcoy 回复于:2007-07-05 21:26:08

引用:原帖由 Edengundam 于 2007-7-5 21:12 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012745&ptid=958272]



bingo

和我想的一样:em02: 

帮你把分析部分加上:
存储空间开销: 平均每个用户的父好友(别人添加你的用户)为50, 平均每个用户的子好友(你添加其他用户)为50.
10,000,000 * 100 * 4 = 4GB

因为每 ... 


赞那个特殊情况的搜索优化


 koolcoy 回复于:2007-07-05 21:28:58

引用:原帖由 福瑞哈哥 于 2007-7-5 21:24 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012792&ptid=958272]


做反向索引,取A的好友集合,再取B的反向好友集合,然后比较两个集合。
我来说具体一点吧,两个有序集合N的比较时间不需要2*N,因为只需要比较两端是否重合就可以了。
但是如果要找出具体路径这种方法就不 ... 


怎么能够只比较两端就可以了呢?


 福瑞哈哥 回复于:2007-07-05 21:32:43

引用:原帖由 koolcoy 于 2007-7-5 21:28 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012815&ptid=958272]

怎么能够只比较两端就可以了呢? 



如果只是要找他们是否有关系,只比较两端就可以了。


 koolcoy 回复于:2007-07-05 21:35:07

引用:原帖由 福瑞哈哥 于 2007-7-5 21:32 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012831&ptid=958272]


如果只是要找他们是否有关系,只比较两端就可以了。 


不行吧, 例如两个数组(1, 3, 5), (2, 3, 6), 只比较两端行吗?


 Edengundam 回复于:2007-07-05 21:35:56

引用:原帖由 福瑞哈哥 于 2007-7-5 21:24 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012792&ptid=958272]


做反向索引,取A的好友集合,再取B的反向好友集合,然后比较两个集合。
[color=Red]我来说具体一点吧,两个有序集合N的比较时间不需要2*N,因为只需要比较两端是否重合就可以了。[/color]但是如果要找出具体路径这种方法就不见得有多少优势了。

确实会快一些,不过每次都要维护这两个有序数组也挺麻烦的。

 



首先, 我们澄清, 搜索命中好友的概率只有万分之2.5
对于你的思路1W次平均的搜索总开销: 
2.5 * ((1 + 2500) * 2500/2) + 9997.5 * 2500 = 32809375
对于后者开销:
2.5 * ((1 + 100) * 100 /2) + 9997.5 * 100 = 1012375

相差32倍, 并且使用这样的算法, 搜索全路径时间开销也是2*N. 这里只需要100次.

另外判断2个数组是否包含相同元素, 2*N. 因为存在一个数组1, 3, 5, ..., 49另一个数组2, 4, 6, ..., 50的情况. 


题目只设计搜索优化, 对于数据变动问题, 我们可以暂时不用考虑, 另外我分析少了一条:
在使用这种技术时候, 初始化索引结构的时间开销比你的方法至少多1倍以上(4GB+内存需要寻址)
但是, 这个初始化过程很简单...只是对当前用户的朋友列表做一次迭代就可以了.


 Edengundam 回复于:2007-07-05 21:37:54

引用:原帖由 koolcoy 于 2007-7-5 21:26 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012800&ptid=958272]

赞那个特殊情况的搜索优化 




嘿嘿, 我那个同学今年又要参加baidu的算法大赛了, 上次就是来北京总部的...他特别牛.....一直搞算法的

给了你个鲜花, 哈哈...


 福瑞哈哥 回复于:2007-07-05 21:40:38

引用:原帖由 koolcoy 于 2007-7-5 21:35 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012842&ptid=958272]

不行吧, 例如两个数组(1, 3, 5), (2, 3, 6), 只比较两端行吗? 



你理解的是正确的,比较两端缩小范围后再比较。这也正是反向索引后能优化的地方。


 koolcoy 回复于:2007-07-05 21:43:29

引用:原帖由 Edengundam 于 2007-7-5 21:37 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012858&ptid=958272]



嘿嘿, 我那个同学今年又要参加baidu的算法大赛了, 上次就是来北京总部的...他特别牛.....一直搞算法的

给了你个鲜花, 哈哈... 


我不太喜欢算法大赛, 感觉很无聊. 并且只有很少的公司会自己做算法. 实际的软件开发对算法需求都不大, 只要程序员心里对算法, 时间复杂度, 空间复杂度有个大概的数就行了.


 Edengundam 回复于:2007-07-05 21:45:30

引用:原帖由 福瑞哈哥 于 2007-7-5 21:40 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012867&ptid=958272]


你理解的是正确的,比较两端缩小范围后再比较。这也正是反向索引后能优化的地方。 




比较两端缩小范围, 可以优化一下, 就是看看A的子友好最后一个ID和B的父好友的第一个ID的大小关系, 如果是小于等于就直接出结果.
其它情况就可以不考虑了, 算法的复杂度大于意义了, 很可能性能下降. 如果真的需要, 可以考虑对数据进行数据统计再着手进行. 

:em03: 

我现在期待的是还有没有更好的点子......:em15: 实在想不出什么了...在想我也无法冲破这些时间界了:em06: 估计高手还有高招
当然分析一下在2GB内存情况, 对于第一种思路和第二种思路, 性能的优劣可能相当的有挑战, 对于这种情况下, 第二种思路就要精心设计一下缓存策略了:mrgreen:


 福瑞哈哥 回复于:2007-07-05 21:46:41

引用:原帖由 Edengundam 于 2007-7-5 21:35 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012847&ptid=958272]


首先, 我们澄清, 搜索命中好友的概率只有万分之2.5
对于你的思路1W次平均的搜索总开销: 
2.5 * ((1 + 2500) * 2500/2) + 9997.5 * 2500 = 32809375
对于后者开销:
2.5 * ((1 + 100) * 100 /2) + 9997.5 ... 



建立反向连接也是一种方法,其关键是对两个集合的比较算法。希望能从你那儿学习到更详细的方法。


 Edengundam 回复于:2007-07-05 21:48:59

引用:原帖由 koolcoy 于 2007-7-5 21:43 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012885&ptid=958272]

我不太喜欢算法大赛, 感觉很无聊. 并且只有很少的公司会自己做算法. 实际的软件开发对算法需求都不大, 只要程序员心里对算法, 时间复杂度, 空间复杂度有个大概的数就行了. 




我那个同学挺猛, 一年到头一直比赛......今年还去了东京参加比赛...不过貌似总是和奖金失之交臂...

算法这东西我觉得是兴趣吧, 我算法就特别烂...:em16: 
我觉得算法好还是有优势, 一些大公司还是有不少对算法要求很高的工作职位.


 koolcoy 回复于:2007-07-05 21:49:35

引用:原帖由 福瑞哈哥 于 2007-7-5 21:46 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012903&ptid=958272]


建立反向连接也是一种方法,其关键是对两个集合的比较算法。希望能从你那儿学习到更详细的方法。 


比较集合就两种吧, 复杂度分别为O(m+n)和O(m*ln(n)), 先估算一下哪种快, 然后就用那种方法.


 Edengundam 回复于:2007-07-05 22:03:12

引用:原帖由 福瑞哈哥 于 2007-7-5 21:46 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012903&ptid=958272]


建立反向连接也是一种方法,其关键是对两个集合的比较算法。希望能从你那儿学习到更详细的方法。 




[table=95%][tr][td][font=FixedSys][color=#000000][color=#0000ff]typedef[/color] [color=#0000ff]struct[/color] FriendList [color=#0000cc]{[/color]
[color=#0000ff]int[/color] fc[color=#0000cc];[/color]
[color=#0000ff]int[/color] fl[color=#0000cc][[/color]50[color=#0000cc]][/color][color=#0000cc];[/color]
[color=#0000cc]}[/color] FriendList[color=#0000cc];[/color]


[color=#0000ff]int[/color] com[color=#0000cc]([/color]FriendList [color=#0000cc]*[/color]a[color=#0000cc],[/color] FriendList [color=#0000cc]*[/color]b[color=#0000cc])[/color]
[color=#0000cc]{[/color]
    [color=#0000ff]int[/color] ac [color=#0000cc]=[/color] a[color=#0000cc]-[/color][color=#0000cc]>[/color]fc[color=#0000cc];[/color]
    [color=#0000ff]int[/color] bc [color=#0000cc]=[/color] b[color=#0000cc]-[/color][color=#0000cc]>[/color]fc[color=#0000cc];[/color]
    [color=#0000ff]if[/color] [color=#0000cc]([/color]ac [color=#0000cc]=[/color][color=#0000cc]=[/color] 0 [color=#0000cc]|[/color][color=#0000cc]|[/color] bc [color=#0000cc]=[/color][color=#0000cc]=[/color] 0[color=#0000cc])[/color]
        [color=#0000ff]return[/color] [color=#0000cc]-[/color]1[color=#0000cc];[/color]

    [color=#0000ff]if[/color] [color=#0000cc]([/color]a[color=#0000cc]-[/color][color=#0000cc]>[/color]fl[color=#0000cc][[/color]ac [color=#0000cc]-[/color] 1[color=#0000cc]][/color] [color=#0000cc]<[/color] b[color=#0000cc]-[/color][color=#0000cc]>[/color]fl[color=#0000cc][[/color]0[color=#0000cc]][/color][color=#0000cc])[/color]
        [color=#0000ff]return[/color] [color=#0000cc]-[/color]1[color=#0000cc];[/color]

    [color=#0000ff]if[/color] [color=#0000cc]([/color]a[color=#0000cc]-[/color][color=#0000cc]>[/color]fl[color=#0000cc][[/color]ac [color=#0000cc]-[/color] 1[color=#0000cc]][/color] [color=#0000cc]==[/color] b[color=#0000cc]-[/color][color=#0000cc]>[/color]fl[color=#0000cc][[/color]0[color=#0000cc]][/color][color=#0000cc])[/color]
        [color=#0000ff]return[/color] b[color=#0000cc]-[/color][color=#0000cc]>[/color]fl[color=#0000cc][[/color]0[color=#0000cc]][/color][color=#0000cc];[/color]

    [color=#0000ff]while[/color] [color=#0000cc]([/color]ac [color=#0000cc]>[/color][color=#0000cc]=[/color] 0 [color=#0000cc]&[/color][color=#0000cc]&[/color] bc [color=#0000cc]>[/color][color=#0000cc]=[/color] 0[color=#0000cc])[/color]
        [color=#0000ff]if[/color] [color=#0000cc]([/color]a[color=#0000cc]-[/color][color=#0000cc]>[/color]fl[color=#0000cc][[/color]ac[color=#0000cc]][/color] [color=#0000cc]>[/color] b[color=#0000cc]-[/color][color=#0000cc]>[/color]fl[color=#0000cc][[/color]bc[color=#0000cc]][/color][color=#0000cc])[/color]
            ac[color=#0000cc]-[/color][color=#0000cc]-[/color][color=#0000cc];[/color]
        [color=#0000ff]else[/color] [color=#0000ff]if[/color] [color=#0000cc]([/color]a[color=#0000cc]-[/color][color=#0000cc]>[/color]fl[color=#0000cc][[/color]ac[color=#0000cc]][/color] [color=#0000cc]>[/color] b[color=#0000cc]-[/color][color=#0000cc]>[/color]fl[color=#0000cc][[/color]bc[color=#0000cc]][/color][color=#0000cc])[/color]
            bc[color=#0000cc]-[/color][color=#0000cc]-[/color][color=#0000cc];[/color]
        [color=#0000ff]else[/color]
            [color=#0000ff]return[/color] a[color=#0000cc]-[/color][color=#0000cc]>[/color]fl[color=#0000cc][[/color]ac[color=#0000cc]][/color][color=#0000cc];[/color]

    [color=#0000ff]return[/color] [color=#0000cc]-[/color]1[color=#0000cc];[/color]
[color=#0000cc]}[/color]
[/color][/font][/td][/tr][/table]


如果需要返回多个结果, 再传进来结构体就行了..很简单的思路

哈哈, 刚才少写了个return.....

[ 本帖最后由 Edengundam 于 2007-7-5 23:03 编辑 ]


 福瑞哈哥 回复于:2007-07-05 22:08:40

引用:原帖由 Edengundam 于 2007-7-5 22:03 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7012988&ptid=958272]



typedef struct FriendList {
int fc;
int fl[50];
} FriendList;


int com(FriendList *a, FriendList *b)
{
    int ac = a->fc;
    int bc = b->fc;
    if (ac == 0 || bc == 0)
       ... 


学习。有序集合的交集。


 robin10 回复于:2007-07-05 22:57:46

if (a->fl[ac - 1] = b->fl[0])
        return b->fl[0];
:em10: 
Edengundam 大哥你少写了一个=吧。。。。


 Edengundam 回复于:2007-07-05 23:06:34

引用:原帖由 robin10 于 2007-7-5 22:57 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7013216&ptid=958272]
if (a->fl[ac - 1] = b->fl[0])
        return b->fl[0];
:em10: 
Edengundam 大哥你少写了一个=吧。。。。 




:em16: :em16: :em16: 
最近果然脑袋不行了...丢三落四....开始还少写个return ...:em06: 

谢谢提醒.

O(m + n) 和 O (m * log(n))的情况大概是n/m > 8 的时候二分搜索就会更快了. 

就是求解m + n > m * log(n)不等式...然后我转换求m + n == m * log(n) 表达式的解, 两边求导, 找驻点什么的...高数忘光了...然后大概得到了n = m^2/(m - 1) ....数学高手帮帮算算...:em15:


 xi_qh 回复于:2007-07-05 23:47:15

long i,j;
#define N 10000000
long id[N];
while(i=0;i<N;i++) 
       id[N]=i;
/*
read_and_set();
读入用户A的好友列表,并设置,比如假设用户A的id是0,好友是3,5,7,67,78,3332
那么id[2]=0 id[4]=0........
*/

/*
开始判断某个id是否二级好友
count=0;
while(j=id;j!=id[j];j=id[j])
        count++;
if(count==2)

*/


 xi_qh 回复于:2007-07-05 23:50:16

long i,j;
#define N 10000000
long id[N];
while(i=0;i<N;i++) 
       id[N]=i;
/*
read_and_set();
读入用户A的好友列表,并设置,比如假设用户A的id是0,好友是3,5,7,67,78,3332
那么id[2]=0 id[4]=0........
*/

/*
开始判断某个id是否二级好友
count=0;
while(j=id;j!=id[j];j=id[j])
        count++;
if(count==2)
        找到一个二级好友
*/

空间复杂度是N,时间复杂度是初始化O(N)+要搜索的好友数*2


 robin10 回复于:2007-07-06 01:18:56

read_and_set();
读入用户A的好友列表,并设置,比如假设用户A的id是0,好友是3,5,7,67,78,3332
那么id[2]=0 id[4]=0........

不是很明白。。。:em06:


 robin10 回复于:2007-07-06 01:41:36

对比上面提到的两种算法,如果考虑最坏情况的话,是不是有这样的关系呢。。。?
对于第二种情况:假使A的好友有500,而加B的好友有1000W,那么查找B是不是A的2维好友的话,是不是要对比 500*(1000W-1)次呢?
而对于第一种情况,最坏的似乎是 在500个好友里面,对500个ID(假使A的500个好友里面每一个都有500个好友。。)的一次折半查找。。。。。具体不太清楚那一个时间多一点。。没有具体想。。。


 robin10 回复于:2007-07-06 01:45:19

而题目说。平均好友只有50。。。这样第一次会不会好点。。。


 Edengundam 回复于:2007-07-06 07:41:51

引用:原帖由 robin10 于 2007-7-6 01:41 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7013500&ptid=958272]
对比上面提到的两种算法,如果考虑最坏情况的话,是不是有这样的关系呢。。。?
对于第二种情况:假使A的好友有500,而加B的好友有1000W,那么查找B是不是A的2维好友的话,是不是要对比 500*(1000W-1)次呢? ... 




******
但是因为保存父好友可能存在一种极端情况, 导致某个数组元素(n)相对另一个数组(m)非常大. (n >> m)
这种情况下可以考虑使用复杂度为m*log(n)的算法.
用m的每个元素去n中进行binary search.
******这个特例的搜索优化是同学想到的...

这个极端我需要的次数拖同学算法的福为:
500 * log(10M) < 4900

即使没有优化我需要的复杂度也只有10M + 500. 

PS: 第一种才是500 * 10M

之前分析少了关键一步...每次都必须用一次binary search处理A, B是一维好友的情况. 第一种, 第二种情况开销都大约是6次(log50).
相比A,B二维关系, 貌似影响是可忽略的, 而且复杂度开销也是个线性的. 两个结论还是: N^2和min(m+n, m*log(n)). 
:mrgreen:

[ 本帖最后由 Edengundam 于 2007-7-6 08:02 编辑 ]


 titansword2000 回复于:2007-07-06 08:33:01

我认为福瑞哈哥谈到下面的这种方法可能是最合适的(无论从时间还是从空间角度):
取A的好友集合,再取B的反向好友集合,然后比较两个集合。
我来说具体一点吧,两个有序集合N的比较时间不需要2*N,因为只需要比较两端是否重合就可以了。
但是如果要找出具体路径这种方法就不见得有多少优势了。

但就象koolcoy所说的那样,不能只比较两端。另外,算法在得到那个反向有序集合后还可以继续进行优化,因为它也是一个有序数组。


 xi_qh 回复于:2007-07-06 09:01:00

不好意思,漏掉一段
/*
read_and_set();
读入用户A的好友列表,并设置,比如假设用户A的id是0,好友是3,5,7,67,78,3332
那么id[2]=0 id[4]=0........
然后再设置A好友的好友,比如如果好友3的好友是 10,100,那么id[9]=2,id[99]=2.......
*/


 xi_qh 回复于:2007-07-06 09:13:24

对上面的补充
假设有三个用户1,10,100,其中10是1的好友,100是10的好友
要找1的二级好友
那么首先id[10]=1,然后id[100]=10,
然后开始查找。因为找100的好友就是两步,首先看100的内容和其索引号是否相符,不等,就查找以其内容为索引的那个位置。
代码也就是
/*
开始判断某个id是否二级好友
count=0;
while(j=id;j!=id[j];j=id[j])
        count++;
if(count==2)
        找到一个二级好友
*/


 Edengundam 回复于:2007-07-06 09:24:26

xi_qh 你的数据结构是一维数组?
你能介绍下你的数据结构如何存储嘛??
因为你只给了一行的例子, 所以看不太明白:em06:

难道要用快速并集的策略??但是如何证明, 相互数据不会影响计算count数??


 xi_qh 回复于:2007-07-06 09:46:55

的确是一维数组,就是用快速并集,在《C算法》里面开篇就讲到了。
储存方式就是一个用户的好友,其对应的数组的内容都存储其id
比如用户id是1,其好友是2,3,4,那么id[2]=1,id[2]=1,id[4]=1,本身id[1]=1
如果用户2的好友是5,6 那么id[5]=2,id[6]=2
现在开始查找 如果是用户7 ,因为id[7]=7,count=0,所以不是
用户6:id[6]=2,继续id[2]=1,继续id[1]=1,此时id和id[]内容相等,中止。count=2,找到一个。
关于相互影响的问题,再设置的时候,可以加一个判断,如果本身数组的内容已经是要找的id用户,此处是1,那就不要设置.


 A.com 回复于:2007-07-06 10:07:02

由于B是给定的,所以只需要查找包含B为好友的的用户U(x),然后在A的好友列表中对比U(x),如果存在,则B为A的二维好友。这种方法的复杂度是固定的,1000w次简单查询(遍历数据库一次)和一次简单对比。缺点是速度慢,优点是内存开销几乎=0,可以在后台慢慢来整理这些数据。

PS:由于A->X->B的好友关系可能是单向的,所以反向好友的算法都有可能存在疏漏,无法采用:em06:


 zhangzezhi 回复于:2007-07-06 10:20:45

ding


 Edengundam 回复于:2007-07-06 10:21:37

引用:原帖由 A.com 于 2007-7-6 10:07 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7014322&ptid=958272]

PS:由于A->X->B的好友关系可能是单向的,所以反向好友的算法都有可能存在疏漏,无法采用




这个你是指哪个算法??

反向的建立是简单, 因为输入数据有序了, 所以建立数据结构不会疏漏. 如下没写完的分配, 大意就是初始化过程和数据结构.

[table=95%][tr][td][font=FixedSys][color=#000000][color=#0000cc]#[/color][color=#ff0000]define[/color] USER_NO 10000000
[color=#0000cc]#[/color][color=#ff0000]define[/color] AVERAGE_NO 50

[color=#0000ff]typedef[/color] [color=#0000ff]struct[/color] FriendList [color=#0000cc]{[/color]
[color=#0000ff]int[/color]    [color=#ff0000]list[/color][color=#0000cc][[/color]AVERAGE_NO[color=#0000cc]][/color][color=#0000cc];[/color]
[color=#0000cc]}[/color] FriendList[color=#0000cc];[/color]

[color=#0000ff]typedef[/color] [color=#0000ff]struct[/color] [color=#0000ff]Friend[/color] [color=#0000cc]{[/color]
[color=#0000ff]int[/color]    fc[color=#0000cc];[/color]
FriendList    [color=#0000cc]*[/color]fl[color=#0000cc];[/color]
[color=#0000cc]}[/color] [color=#0000ff]Friend[/color][color=#0000cc];[/color]


[color=#0000ff]typedef[/color] [color=#0000ff]struct[/color] MapFriend [color=#0000cc]{[/color]
[color=#0000ff]Friend[/color] [color=#0000cc]*[/color]cf[color=#0000cc];[/color]
[color=#0000ff]Friend[/color] [color=#0000cc]*[/color]pf[color=#0000cc];[/color]
[color=#0000cc]}[/color] MapFriend[color=#0000cc];[/color]

MapFriend mapFriend[color=#0000cc][[/color]USER_NO[color=#0000cc]][/color][color=#0000cc];[/color]

[color=#0000ff]void[/color] buildMapFriend[color=#0000cc]([/color][color=#0000ff]int[/color] id[color=#0000cc],[/color] [color=#0000ff]int[/color] fcount[color=#0000cc],[/color] [color=#0000ff]int[/color] [color=#0000cc]*[/color]flist[color=#0000cc])[/color]
[color=#0000cc]{[/color]
    [color=#0000ff]Friend[/color] [color=#0000cc]*[/color]tmp
    mapFriend[color=#0000cc][[/color]id[color=#0000cc]][/color][color=#0000cc].[/color]cf[color=#0000cc]-[/color][color=#0000cc]>[/color]fc [color=#0000cc]=[/color] fcount[color=#0000cc];[/color]
    mapFriend[color=#0000cc][[/color]id[color=#0000cc]][/color][color=#0000cc].[/color]cf[color=#0000cc]-[/color][color=#0000cc]>[/color]fl [color=#0000cc]=[/color] flist[color=#0000cc];[/color]
    [color=#0000ff]while[/color] [color=#0000cc]([/color]fcount[color=#0000cc]-[/color][color=#0000cc]-[/color][color=#0000cc])[/color]
    [color=#0000cc]{[/color]
        tmp [color=#0000cc]=[/color] mapFriend[color=#0000cc][[/color]flist[color=#0000cc][[/color]fcount[color=#0000cc]][/color][color=#0000cc]][/color][color=#0000cc].[/color]pf[color=#0000cc];[/color]
        [color=#0000ff]if[/color] [color=#0000cc]([/color]tmp[color=#0000cc]-[/color][color=#0000cc]>[/color]pf [color=#0000cc]=[/color][color=#0000cc]=[/color] [color=#ff0000]NULL[/color][color=#0000cc])[/color]
        [color=#0000cc]{[/color]
            tmp[color=#0000cc]-[/color][color=#0000cc]>[/color]pf [color=#0000cc]=[/color] [color=#0000cc]([/color][color=#0000ff]Friend[/color] [color=#0000cc]*[/color][color=#0000cc])[/color] malloc0[color=#0000cc]([/color][color=#0000ff]sizeof[/color][color=#0000cc]([/color][color=#0000ff]Friend[/color][color=#0000cc])[/color][color=#0000cc])[/color][color=#0000cc];[/color]
            tmp[color=#0000cc]-[/color][color=#0000cc]>[/color]pf[color=#0000cc]-[/color][color=#0000cc]>[/color]fl [color=#0000cc]=[/color] [color=#0000cc]([/color]FriendList [color=#0000cc]*[/color][color=#0000cc])[/color] malloc0[color=#0000cc]([/color][color=#0000ff]sizeof[/color][color=#0000cc]([/color]FriendList[color=#0000cc])[/color][color=#0000cc])[/color][color=#0000cc];[/color]
        [color=#0000cc]}[/color]
        tmp[color=#0000cc]-[/color][color=#0000cc]>[/color]pf[color=#0000cc]-[/color][color=#0000cc]>[/color]fl[color=#0000cc]-[/color][color=#0000cc]>[/color][color=#ff0000]list[/color][color=#0000cc][[/color]tmp[color=#0000cc]-[/color][color=#0000cc]>[/color]pf[color=#0000cc]-[/color][color=#0000cc]>[/color]fc[color=#0000cc]+[/color][color=#0000cc]+[/color][color=#0000cc]][/color] [color=#0000cc]=[/color] id[color=#0000cc];[/color]
        // if fc >= 50, we need to alloc another FriendList to Friend...^^
[/color]
    [color=#0000cc]}[/color]
[color=#0000cc]}[/color][/font][/td][/tr][/table]

[ 本帖最后由 Edengundam 于 2007-7-6 10:26 编辑 ]


 Edengundam 回复于:2007-07-06 10:31:08

引用:原帖由 xi_qh 于 2007-7-6 09:46 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7014164&ptid=958272]
的确是一维数组,就是用快速并集,在《C算法》里面开篇就讲到了。
储存方式就是一个用户的好友,其对应的数组的内容都存储其id
比如用户id是1,其好友是2,3,4,那么id[2]=1,id[2]=1,id[4]=1,本身id[1]=1
如 ... 




比如下面这个列表:
1          3,5,7,9
2          1,3,5,9
3          4,5,8,9
5          1,2,39
9          1,3

数据结构是一次把所有信息存储上??
 初始化过程                   数组索引-->
     |
     |
    /
数组索引                          [1]    [2]     [3]   [4]    [5]    [6]  [7]   [8]   [9]
初始化值                   1      2       3      4      5      6     7      8     9
读入id=1用户信息          1      2        1     4       1      6    1       8     1
读入id=2用户信息          ?      2         ?     4       ?      6    ?       8     ?


这个数据结构是怎么变化的...不太理解你最后一句话的意思.
是每次查询时候, 都需要进行一次初始化嘛???


 A.com 回复于:2007-07-06 10:36:24

另一种方法就是多任务并行处理了,在得到A的好友列表A(x)后同时在这些用户的好友列表里面找已知的B。这系统开销虽然看起来会比较大,但由于平均好友只有50个,实际上这办法比遍历1000w条数据的总开销要少得多。


 xi_qh 回复于:2007-07-06 10:42:15

的确这是个问题,在查询某个用户的好友时,需要初始化,但是只需初始化某一个id的好友和好友的好友


 Edengundam 回复于:2007-07-06 10:48:34

引用:原帖由 xi_qh 于 2007-7-6 10:42 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7014595&ptid=958272]
的确这是个问题,在查询某个用户的好友时,需要初始化,但是只需初始化某一个id的好友和好友的好友 




这样内存占用小, 但是对数组初始化(10,000,000)相对50来说开销就不划算了. 
另外, 一个用户id平均需要7个数字字符(假设是ASCII编码的文件, 每个字符占用1字节)分隔符是一个t
平均好友列表50 * (7+1): 7表示用户id: 1表示逗号和最后的换行. 平均每行的长度: 8 * 51 = 408字节.
408 * 10M = 4GB
这样下来, 对于磁盘性能貌似是很大的挑战. :wink:


 softsongs 回复于:2007-07-06 13:18:04

做个标记,改天给个解决方案,今天是没时间看了。


 neoedmund 回复于:2007-07-06 15:27:38

最简单的应用啊


 prettywolf 回复于:2007-07-06 23:50:22

百度准备搞即时通讯了.


 robin10 回复于:2007-07-07 01:19:49

引用:原帖由 Edengundam 于 2007-7-6 10:48 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7014631&ptid=958272]



这样内存占用小, 但是对数组初始化(10,000,000)相对50来说开销就不划算了. 
另外, 一个用户id平均需要7个数字字符(假设是ASCII编码的文件, 每个字符占用1字节)分隔符是一个t
平均好友列表50 * (7+1):  ... 





不清楚我是不是理解得不太清楚,但是我同意 Eden 的说法。。。
存储的数据的空间开销是不是很大呢。。。?希望可以祥解:)


 jamesr 回复于:2007-07-07 09:33:51

不需要遍历1000W个吧!这么大的数据在建立是一定有HASH索引的,按普通的思路,平均只要读50*50次数据就行了。


 福瑞哈哥 回复于:2007-07-07 10:49:45

普通思路只需要50*5就可以了,一级好友遍历,二级好友bsearch。简单就好。


 弥敦路九号 回复于:2007-07-07 22:24:19

即然是基于索引的搜索,题目中要给明对索引成本(构建索引时间,空间)的要求,和查询成本的要求(以及对两者优先级)还有数据的变动性频度。

索引成本不计:从任何一种方法逐个获取2维好友列表B+树(归档)形成索引文件.此法对查询性能最好,一个对出发点A的B树查找加上一个对其二维好友B的B树查找。
重索引次查询:B树构建入口(出发用户),对每个用户的发友建立B树,此树上的每个用户链接到入口形成 通过入口找到一维度好友,通过链接找到一维好友做为出发点的入口再找二维好友。这样,总体就是一个入口的B树查找加上N个其一维度好友为入口的B树查找。
居中:在上法基础上对每个用户建立“视其为好友的用户”的B树,这样最后就是求两个B树是否有交集性能要比上法好,但需要多个建立B树的成本。


 converse 回复于:2007-07-07 23:27:58

没有完全看完,但是感觉koolcoy 应该是里面最好的。

考虑到百度是一家互联网公司面对的是海量的数据,如果实际操作中有这样的查询需求应该还会做一个cache缓存的数据。


 bosshoss_cn 回复于:2007-07-08 13:44:38

只是求二维好友,不是N维好友?
不懂,请教下


 bosshoss_cn 回复于:2007-07-08 13:49:32

ID,1维好友,二维好友
最大空间:50*50*10M=25G
:em06:




原文链接:http://bbs.chinaunix.net/viewthread.php?tid=958272
转载请注明作者名及原文出处


文章评论

共有 位网友发表了评论 查看完整内容