算法概要 计算用户间连通关系强度的算法可以分成两个部分:第一部分构建连通关系强度的计算模型即由链结关系网络图转换为对应的带有关系语境信息的链结关系网络图;第二部分进行用户间连通关系强度的计算。下面我们对该部分算法进行具体描述。 第一部分算法的步骤描述为:
由原始的数据构造出无向图G(V,E),这里的原始数据来源于社区发现算法形成的社区中的数据;
设带有关系语境信息的链结关系网络图为Gi(Vi,E’),令V’=V;
从任意的顶点出发,采用广度优先搜索(BFS)遍历原始图G;
如果通过BFS遍历得到的一条边e=(A,B),用r表示e的关系节点,如果r
不存在,则创建这个节点,并添加到VI,并标记这个点为关系节点;
在G?中构造两个新的边:ei =(A,r)和e2=(r,B)。
算法的步骤描述为:
设带有关系语境信息的链结关系网络图为G’,其对应的流图为G”;
从G’的源节点S出发,釆用广度优先搜索进行遍历;
设e=(A,B)为遍历得到的一条边。若A是关系节点,将A分割为两个节点A_i A_o,为保持它们的连通性,添加相应的边,同理,对B做相同的操作。将
所有这些节点和边添加到G”中;
对得到的G”,调用方法findConnectivity (G”,s,d)?得到s,d的连通关系强度。
对于方法findConnectivity (G”,s,(l)的描述如下:
初始化节点s,d的连通关系强度值k为0;
在循环中不断检查G”中s,d是否连通,每检查一次,如果连通,则关系强度
值加一。直到s,d不再连通时,退出循环。检查G”中s,d是否连通是通过调用
方法 augmentingPath(G”,s,d)来实现的。
对于方法augmentingPath (G”,s,d)的描述如下:
初始化G”的节点集V,边集E,定义队列queue;
初始化V中的每个节点为未被访问的状态,其父节点为空;
设置S为己访问状态,将其加入队列queue;
循环判断队列queue是否为空。
如果queue非空,取出其队尾节点u;判断U和d是否是同一个节点,如果不是,循环遍历U的邻居节点集,分别取出U的每一邻居节点V,判断边UV的流值是否等于0,如果等于0且V未计算用户间连通关系强度的算法可以分成两个部分:
第一部分构建连通关系强度的计算模型即由链结关系网络图转换为对应的带有关系语境信息的链结关系网络图;第二部分进行用户间连通关系强度的计算。下面我们对该部分算法进行具体描述。
第一部分算法的步骤描述为:
调用LFM算法划分社区,该算法对应的类为LFMAlgorithm,输入为文本格式的用户好友数据,输出为存放社区划分结果的文本文档resulttxt,该文档中的一行代表划分出的一个社区;
输入待推荐好友的用户U,存放社区划分结果的文档,构建查找用户所在社区的类FindCommunity的对象,调用方法findCommunityByName()’输出用户u所在社区的名字;
第二部分算法的步骤描述为:
首先进行数据的初始化,构建待推荐好友的用户所在社区的邻接矩阵_c,将
待推荐好友的用户所在的社区中的节点和节点间的关系语境信息存放到一张数据库表中。
输入待推荐好友的用户U所在社区的邻接矩阵C,构建计算用户相似度的类
CountUserSimilarity的对象,调用计算用户相似度的方法countUserSimilarO,
分别计算出用户U与社区中其他用户的用户相似度S/mi的值,返回为存放U与其他用户的用户相似度的数组;
输入待推荐好友用户U,构建计算用户间连通关系强度的类CountConnection的对象,调用计算用户间连通关系强度的方法countCoimectionO,计算出用户U与社区中其他用户的连通关系强度connectivity,数据来源为存放用户及用户间关系语境信息的数据库表,返回节点对象数组,在每个节点对象的connectivity属性里存放了该节点与用户U所对应的节点间的连通关系强度的值;
输入待推荐好友的用户U,存放U与其他用户的用户相似度的数组以及节点对象数组,构建计算用户间的综合相似度的类CountComShnilarity的对象,调用方法countComSimilarityO计算出u与其他用户的综合相似度,返回为存放U与其他用户的综合相似度的数组;
输入待推荐好友的用户U,推荐的好友个数K,及存放U与其他用户的综合相似度的数组,构建进行TopK推荐的类TopkRecommendation的对象,调用removeDirectFriends()方法去除 u 的直接好友,调用 topkRecommendationO方法进行TopK推荐,返回为U推荐的K个潜在好友的编号的数组;算法结束。
整个算法的流程如图
被访问,则设置V为己访问状态,设置U为V的父节点,并把V加入队列,遍历U的邻居节点集完毕,该循环退出。如果U和d为同一节点时,循环判断U的父节点和S是否为同一节点,如果不是,在E中取出U的父节点和U所对应的边,设置此边的流值为1,把U的父节点赋给U,当U的父节点和S为同一节点时,此循环退出,同时令方法返回真值,本方法结束。
在上面对算法的两个部分分别进行了描述,其中第二部分算法中的方法augmentingPath (G”,s,d)为计算用户s,d的连通关系强度的具体实现部分。我们知道,图论中的最大流算法用于计算从源节点到目标节点之间的最大的边连通度。为了计算源节点和目标节点之间的连通关系强度,算法将关系节点分割成两个节点,利用最大流算法计算出来的边连通度也即是我们要计算的连通关系强度的值。
用户综合相似度 在第二章中介绍了基于用户相似度的好友推荐算法,该算法根据用户之间的
链接信息来计算用户间的相似程度即用户相似度?S/m’,即便它提出了两阶段的好友推荐模型,在模型的第二阶段考虑到了相似度的传播,但本质上它的好友推荐模型的两个阶段仍只是考虑用户间的链接信息或者说拓扑结构,没有考虑社交网络中用户间关系的内容以及关系的强度。在本章的第三节中介绍了用户间关系强度的一种定量计算方式即计算连通关系强度connectivity,并且给出了计算模型和算法描述,连通关系强度的计算充分考虑了用户间关系的内容,在一定程度上弥补了在推荐算法中仅考虑用户间的链结信息计算用户相似度进行推荐的不足。在这里,将根据用户间的链结信息计算的用户相似度Sim?和用户间的连通关系强度connectivity结合起来,提出用户综合相似度com-similarity的概念。设有两个用户a与C,则a?c的综合相似度com-siVrn’/an’oKa^c)的计算公式如公式com-similarity{a,c)= y0Sim'(a,c) + (\-fi)connectivity(a, c) (3-1)
其中β为调整因子,且;β ∈ [0,l],可以通过实验来确定它的取值。 。
下面举个简单的例子来说明在进行推荐时依据综合相似度和用户相似度的 一些区别。
当为用户1推荐1个好友时,可以在用户3与4中进行选择,因为用户2已经是用户1的好友所以不再考虑用户2。首先分别计算出用户1和用户3、4的用户相似度Sim’(l,3)和Sim’(l,4)。从图3.4的好友链结关系网络图可以得出邻
接矩阵C,其表示为:
从邻接矩阵C中可以得出: 1 j ; ;
用户1的特征向量表示为:(1,1,0,0) j
用户2的特征向量表示为:(1,1,1,1) j
用户3的特征向量表示为:(0,1,1,0)
用户4的特征向量表示为:(0,1,0,1)。
由于用户1的邻居只有用户2,那么当计算Sim’(l,3)时,在两阶段好友推荐模型的第二阶段便只需要计算Sim(2,3),根据本文2.2.2小节介绍的用户相似度的计算公式(2-2),可以得出:
同理计算得出Sim’(l,4)=0.209。
其次计算用户1和用户3、4的连通关系强度,从图3.5中可以得出,
算法总体思想 本文的好友推荐算法的总体思想为:第一步,釆用社区发现算法将好友社交 网络划分成不同的社区,每一个社区可以看成是一个社交圈,从中发现待推荐好友的用户所在的社区;第二步,在待推荐好友的用户所在的社区中计算出其与其他用户的综合相似度,将综合相似度最大的前K个用户作为潜在好友向其进行推荐。
算法描述与分析 综合上述思想,将用于好友社交网络中的基于社区发现和用户综合相似度的
好友推荐算法分成两个部分,描述如下:
第一部分算法的步骤描述为:
1、调用LFM算法划分社区,该算法对应的类为LFMAlgorithm,输入为文本格
式的用户好友数据,输出为存放社区划分结果的文本文档resulttxt,该文档
中的一行代表划分出的一个社区;
2、输入待推荐好友的用户U,存放社区划分结果的文档,构建查找用户所在社区
的类FindCommunity的对象,调用方法findCommunityByName()’输出用户u所在社区的名字;
第二部分算法的步骤描述为:
首先进行数据的初始化,构建待推荐好友的用户所在社区的邻接矩阵_c,将 待推荐好友的用户所在的社区中的节点和节点间的关系语境信息存放到一张数据库表中。
输入待推荐好友的用户U所在社区的邻接矩阵C,构建计算用户相似度的类
CountUserSimilarity的对象,调用计算用户相似度的方法countUserSimilarO,
分别计算出用户U与社区中其他用户的用户相似度S/mi的值,返回为存放U
与其他用户的用户相似度的数组;
输入待推荐好友用户U,构建计算用户间连通关系强度的类CountConnection
的对象,调用计算用户间连通关系强度的方法countCoimectionO,计算出用户U与社区中其他用户的连通关系强度connectivity,数据来源为存放用户及用户间关系语境信息的数据库表,返回节点对象数组,在每个节点对象的connectivity属性里存放了该节点与用户U所对应的节点间的连通关系强度的值;
输入待推荐好友的用户U,存放U与其他用户的用户相似度的数组以及节点对象数组,构建计算用户间的综合相似度的类CountComShnilarity的对象,调用方法countComSimilarityO计算出u与其他用户的综合相似度,返回为存放U与其他用户的综合相似度的数组;
输入待推荐好友的用户U,推荐的好友个数K,及存放U与其他用户的综合相 似度的数组,构建进行TopK推荐的类TopkRecommendation的对象,调用 removeDirectFriends()方法去除 u 的直接好友,调用topkRecommendationO方法进行TopK推荐,返回为U推荐的K个潜在好友的编号的数组;算法结束。
本文算法的时间复杂度的主要来源有:重叠社区发现,寻找待推荐好友的用
户所在的社区,计算待推荐好友的用户与社区中其他用户的用户相似度和连通关系强度,寻找综合相似度最大的前K个用户等五个部分。其中,重叠社区发现的
LFM算法在最坏情况下即当社区规模和网络中总节点数n在同一数量级时的时间复杂度为0(n2);寻找待推荐好友的用户所在的社区的时间复杂度为0(n),n为节点总数目;计算待推荐好友的用户与社区中其他用户的用户相似度的时间复杂度为0(m2),其中m为社区中包含的节点数目;计算连通关系强度的时间复杂度为0(me)在这里m为社区中的节点数目,e为社区中的边数;寻找综合相似度最大的前K个用户的时间复杂度为0(K m),所以本文算法总的时间复杂度为0(n2)。
算法代码 (1) LFMAlgorithm类中的关键代码即LFM算法的具体实现部分, 具体代码如下:
while (seedlist. size ()>0){StringBuffer dian=new StringBuffer(); int pointNum=l ; dian.append (seedlist?get (0〉》; map3.put{seedlist?get (0), Integer.valueOf (map3.get (seedlist.get (0) ) .toStringO )+1) ; 量 seedlist. remove (0); int e_in=0, e_out=0, e_in_side=0, e_out_all=0,m3=0; /7初始化各变量初值 double ceDu_max=0, ceDu=0; /7社区测度的中间值ceDuTemp记录没加入点i时的测度 String point_tem="" ; do {in3=dian. toString {) . split (", " ) ? length; string bb = "" +dian+", " ; double xx=0,yy=0; for (int k2^0;k2<m3;k2++){e_out—all=^e_out_all+Integer ? valueOf (map ? get (dian.toString (). split ("," ) [k2]).toString (). split (";" )[2]);} for (int iii=0;iii<m3-1;iii++){for (int j=iii+l ;j<m3;j++){if (map.get (dian,toString ()?split ()[iii]).toString (). split () [1].contains ("," +dian.toString {)?split ()[j]+"," )){ e_in_side++; if (map.get (dian.toString (). split (, ) [iii])?toString {). split (";" )[1].substring(map.get (dian.toString (), split ()[iii]).toString ()?split ( ";" )[1].indexOf("," +dian.toString ().split ("," ) [j]+"," )+l )□contains (,"+dian.toString (). split e_in_side++; ; ].substring(map.get (dian.toString {)?split ("," ) [iii]).toString (). split ( ";" )[1]?indexOf("," +dian.toString ().split ("," ) [j]+"," )+1).contains "+dian.toString0.split [j]+"," )){ e_in—side++; } } } } m3=m3-l ; e_in=2*e_in_side; e_out=e —out —all-e_in; xx=Double. valueOf (e_in) / java. lang.Math.pow((Double. valueOf (e_in+e_o ut)),zz); ceDu=xx-yy; e_in=0 ; e_out=0 ; e —in —side=0 ; e —out_all=0 ; dian.delete (dian.lastlndexOf ("," ), dian.lengthO ) ; if (ceDu>ceDu_max){ce Du_max=ceDu; point_tem=aa; } } } if (ceDu_max>0) {dian.append ("," +point_tem); bb=bb+"," +point_tem+"," ; pointNuiti++; e_in=0;e_out=0; map3.put(point—tem. Integer ? valueOf (map3 ? get (point_tera) . toString () ) +1) ; e —in_side=0;e_out—all=0;ceDu_max=0;ceDu=0;seedlist. remove (point—tern); }else {ceDu_max=-1;} }while (ceDu_max>=0); System.out .println ("社区" +nuinber+"里总点数:" +pointNuni); System.out .println("社区 " + (nuinber++) +"里的全部点为:" +dian); result.write(dian+"\r\n" ); result.flush 0; }
(2) FindCoimnunity类中的核心方法findCommunityByName,功能是通过用户的
名字査找其所属社区的名字。具体代码如下:
public void findCommunityByName(String name) { String teiapString = null; BufferedReader reader = mill; ArrayList list = new ArrayList<Integer> < ); try { reader = new BufferedReader(new FileReader(new File ("result.txt" ))); int line = 1; while ((tempString = reader.readLine())] = null) { String[] splitString = tempString. split ("," ); for (int i = 0; i < splitString.length; i++) { if {name.egualsIgnoreCase(splitString[i])) { list . add(line ); } } line ++; } reader. close {); System, out .print {name + "所在的社区为:“); for (int i = 0; i < list . size (); i++) { System, out .print ("社区" +list ? get (i) +” “); } } catch {FileNotFoundException e ) { e .printStackTrace{); System, out .println (”文件未找到! ”); } catch (lOException e ) { e .printStackTrace(); System, out .println ("发生工2异常。“); } finally { if (reader != null) { try { reader ? close (); } catch (lOException e ) { e .printStackTrace {); 1 } } }
(1) LFMAlgorithm类中的关键代码即LFM算法的具体实现部分,具体代码如下:
while (seedlist. size () > 0){ StringBuffer dian = new StringBuffer(); int pointNum = l ; dian.append (seedlist ? get (0〉》; map3.put {seedlist ? get (0), Integer.valueOf (map3.get (seedlist.get (0) ) .toStringO ) + 1) ; 量 seedlist. remove (0); int e_in = 0, e_out = 0, e_in_side = 0, e_out_all = 0, m3 = 0; / 7初始化各变量初值 double ceDu_max = 0, ceDu = 0; / 7社区测度的中间值ceDuTemp记录没加入点i时的测度 String point_tem = "“; /7话则把此点加入社区C do { in3=dian. toString {) . split (“, “) ? length; string bb = " + dian + " , “; double xx=0,yy=0; for (int k2^0;k2<m3;k2++){ e_out—all=^e_out_all+Integer ? valueOf (map ? get (dian.toString (). split (", " ) [k2]). toString (). split ("; ”)[2]); } for (int iii = 0; iii < m3 - 1; iii++) { for (int j = iii + l ; j < m3; j++) { if (map.get (dian, toString () ? split ()[iii]).toString (). split () [1 ].contains (“, "+dian.toString{)?split ()[j]+" , ")){ e_in_side++; if (map.get (dian.toString (). split (, ) [iii])?toString {). split (”;")[1 ].substring(map.get (dian.toString (), split ()[iii]).toString () ? split ( “; “)[1].indexOf("," + dian.toString ().split ("," ) [j] + "," ) + l )□contains (, " +dian.toString (). split e_in_side++;
(3) CountUserSimilarity类中的核心方法,其中firstSimilar方法的功能是计算第一阶段的余弦相似度,secondSimilar方法的功能是计算第二阶段的传播相似度,countUserSimilar方法的功能是计算出用户相似度。具体代码如下:
public double firstSimilar (int m, int n, int [] [] C) { double value = 0 ; double valuel - 0 ; double value2 = 0 ; double result = 0 ; for (int i = 0 ; i < C[0 ].length; i++) {value += C[m - 1 ] [i] * C[n - 1 ] [i]; } for (int i = 0 ; i < C[0 ].length; i++) {valuel += Math .pow(C[m - l][i], 2 ); value2 += Math .pow(C[n - l][i], 2 ); } valuel = Math .sqrt(valuel); value2 = Math .sgrt(value2); result = value / {valuel * value2);result = Round .round(result , 3 );return result ;} public double secondSimilar(int m, int n, int [][] C) { double tempi = 0 ; int sum = 0 ;double jieguo = 0 ; for (int i=0 ;i<C[0 ].length ;i++){if (i!=m-1 Sc & C[m-1 ] [i] =- 1 ) {tempi = firstSimilar (i+1 , n, C); sum = countNum(i+l, C); jieguo += tempi/sum; } } return jieguo;} public double[] countUserSimilar(int m,int total,int [][] C) { double ml = 0 ; double m2 = 0 ; double[] result = new double[total+1 ]; for (xnt i=l;i<=total;i++){if (i!= m) {ml = firstSimilar(m, i, C); m2 = secondsimilar(m, i, C); result [i]= Round .round(userSimilar(ml,m2,0 .9 ),3 );} else { result [m]=0 ;} } return result ;}
(4)计算用户间的连通关系强度的各类中的核心方法,其中augmentingPath和findConnectivity方法是计算连通关系强度的具体实现部分,count和countConnection方法的功能是调用计算连通强度的方法,将编号为id,姓名为name的用户u与其他用户的连通关系强度计算出来,并设置到各个节点对象的connectivity属性当中,最后把除u以外的用户节点保存在节点数组nodeSet中并返回。具体代码如下:
public static boolean augmentingPath (Graphic graphic, Node ns, Node nd) { List<Node> nodes = graphic.getNodes(); List<Edge> edges = graphic.getEdges(); Queue<Nocie> queue = new LinkedList<Node> (); for (int i = 0 ; i < nodes. size () ; i++) { Node node = nodes.get(i); node .setColor("WHITE" ); node - setParent(null); } ns.setColor("GRAY" ); queue . add(ns); while (!queue .isEmpty()) { Node u = queue .poll (); if (u.getId() I = nd.getld()) { List<Node> nodeU = u.getNeighbors(); for (int i = 0 ; i < nodeU.size(); i++) { Node v = nodeU.get(i); Edge edgeUV = findEdge(edges, u, v); if ("WHITE" .equals(v.getColor {)) && edgeUV.getCurrentFlow() == 0 ) { v.setColor("GRAY" ); v.setParent(u); queue . add(v); } } } else { while (u.getParent(). getId () != ns.getld()) { Edge edge = flndEdge(edges, u.getParent(), u); edge.setCurrentFlow(1 ); u = u.getParent(); } return true ; } u.setColor("BLACK" ); } return false ; } public int findConnectivity (Graphic graphic, Node ns, Node nd) { int k = 0 ; while (augmentingPath {graph!c, ns, nd)) { k++; } return k; } public void count (Node nodeA) { List<Node> nodes = graphic.getNodes(); List<Edge> edges = graphic.getEdges(); for (Node node : nodes) { if (node.getType() != 0 && nodeA.getld() != node ? getld()) { rs ? countConnectivity(nodeA, node); for (Edge edge : edges) { edge.setCurrentFlow(0 ); } } } } ` public Node[] countConnection(int id, String name) { List<Node> nodes = graphic.getNodes(); Node nodeA = new Node(id, name); nodeA = grrapiiic. getRepeatNode (nodeA. getld (), nodeA. getName () f graphic); CountConnection findValue = new CountConnection(); findValue.count(nodeA); nodes . remove (nodeA); int [] arr = new int [nodes - size ()]; Node[] nodeSet = new Node[nodes . size ()]; for (int i = 0 ; i < nodes. size (); i++) { arr[i] = nodes ? get(i) ? getld(); nodeSet[i] = nodes. get (i); } for (int i = 0 ; i < arr.length - 1 ; i++) { for (int j = 0 ; j < arr.length - i - 1 ; j++) { if (arr[j] > arr[j + 1 ] ) { int temp = arr[j]; arr[j] = arr[j + 1 ]; arr[j + 1 ] = temp; Node tn = nodeSet[j]; nodeSet[j] = nodeSet[j + 1 ]; nodeSet[j + 1 ] = tn; } } } return nodeSet; } public int [] topkRecommendation (±nt u, int K, double [ ] finallyResult) { int total = finallyResult.length; int [] order = new int [total + 1 ]; for (int i = 0 ; i < total + l; i++) { order[i] = i; } for (int i = 0 ; i < finallyResult. length - 1 ; i++) { for (int j = 0 ; j < finallyResult.length - i - 1 ; j++) { if (finallyResult[j] < finallyResult[j + 1 ]) { double temp = finallyResult[j]; finallyResult[j] — finallyResult[j + 1 ]; finallyResult[j + 1 ] = temp; int t = order[j]; order[j] = order[j + 1 ]; order[j + 1 ] = t; } } } return order; } public class Node { int id; String name; List<Node> neighbors; Node parent; String color; int connectivity; int . type; }
(5) CountComSimilarity类中的核心方法countComSimilarity,功能是计算用户u
与社区中其他用户的综合相似度。具体代码如下:
public double [ ] countComSimilarity (int u, int total, double beder, double [] resultfNode[] nodeSet) { double[] finallyResult = new double[total + 1 ]; for (int j = l, i—0 ; j <= result .length - 1 ; j++) { if (j != u) { finallyResult[j] - Round , rouiid (beder * result [ j ] + (1 - beder) *nodeSet [i] ? get Connectivity () , 4 ); i++; } } return finallyResult; }
(6) TopkRecommendation类中的核心方法,其中removeDirectFriends方法的功能
是在存储综合相似度的数组finallyResult中,设置用户u的直接好友的综合相似度为0,即在推荐中排除掉其直接好友,topkRecommendation方法的功能是为用户1推荐K个潜在好友,返回这K个潜在好友的id编号的数组。具体代码如下:
public double[ ] removeDirectFriends (int u, double [ ] finallyResult, int [][] Cf int total) { for(int a = 1; a <= total; a++) { if(C[u - 1][a - l] == l) { finallyResult[a] = 0; } } return finallyResult; }