直接上代码吧。
/*** 这是一个无向图数据结构,支持非联通。* 支持ID-Mapping解绑使用* @Author Ma Shuai* @Date 9/13/24 5:45 PM* @Version 1.0*/public class UnbindGraph {private static String SPLIT = ":";public UnbindGraph() {this.adjacentNodeMap = new HashMap<>();this.nodeMessageMap = new HashMap<>();}@Datapublic class NodeMessage {private String idType;private String idValue;// key是业务来源table_name(业务来源唯一对应一个表) value是表主键PK。 Long类型有需要时可换成beanprivate Map<String, List<Long>> tableAndPKList;public NodeMessage(String idType, String idValue,String tableName, Long pk){this.idType = idType;this.idValue = idValue;this.tableAndPKList = new HashMap<>();this.tableAndPKList.put(tableName,new ArrayList<Long>(){{add(pk);}});}private void setTableAndPKList(Map<String, Set<Long>> tableAndPKList) {}public void setTableAndPKList(String tableName, Long pk) {if(!this.tableAndPKList.containsKey(tableName)){this.tableAndPKList.put(tableName,new ArrayList<Long>(){{add(pk);}});}else {this.tableAndPKList.get(tableName).add(pk);}}}//节点关系 node String=>idtype:idvalueprivate Map<String, Set<String>> adjacentNodeMap;//节点信息 node message (方便拓展性维护性以及空间的节省 单独Map存节点信息)private Map<String, NodeMessage> nodeMessageMap;/*** 添加节点关系 A-B* @param A* @param B*/public void addEdge(GraphNodeParameter A,GraphNodeParameter B){// 都不符合规范不入图(所以图结果集不存在不符合规范的数据会导致有的centerid对应的数据没有得到处理,最后要按照老的centerid逻辑删除数据)if(!checkGraphNodeParameterIsTrue(A)&&!checkGraphNodeParameterIsTrue(B)){return;}setAdjacentNodeRelation(A,B);}/*** A-B 入图* @param A* @param B*/private void setAdjacentNodeRelation(GraphNodeParameter A,GraphNodeParameter B) {//当前节点的预处理pretreatmentNode(A,B);// AB都合规if(checkGraphNodeParameterIsTrue(A)&&checkGraphNodeParameterIsTrue(B)){String idTypeValueA = A.getIdType()+SPLIT+A.getIdValue();String idTypeValueB = B.getIdType()+SPLIT+B.getIdValue();//无向setAdjacentNode(idTypeValueA, idTypeValueB);setAdjacentNode(idTypeValueB, idTypeValueA);}}/*** 存储节点信息、整理临接表* @param nodeParameters*/private void pretreatmentNode(GraphNodeParameter... nodeParameters) {for (GraphNodeParameter N : nodeParameters) {if(!checkGraphNodeParameterIsTrue(N)){continue;}//节点信息String idType = N.getIdType();String idValue = N.getIdValue();String idTypeValue = idType+SPLIT+idValue;String tableName = N.getTableName();Long pk = N.getPK();// pretreatment adjacentNodeMapif(!this.adjacentNodeMap.containsKey(idTypeValue)){this.adjacentNodeMap.put(idTypeValue,new HashSet<>());}// add NodeMessageif(!this.nodeMessageMap.containsKey(idTypeValue)) {this.nodeMessageMap.put(idTypeValue,new NodeMessage(idType,idValue,tableName,pk));}else {this.nodeMessageMap.get(idTypeValue).setTableAndPKList(tableName,pk);}}}/*** 设置临接关系* @param idTypeValueA* @param idTypeValueB*/private void setAdjacentNode(String idTypeValueA, String idTypeValueB) {this.adjacentNodeMap.get(idTypeValueA).add(idTypeValueB);}/*** 校验节点数据* @param nodeParameter* @return*/private boolean checkGraphNodeParameterIsTrue(GraphNodeParameter nodeParameter){returnnodeParameter != null&&!StringUtils.isEmptyOrWhitespaceOnly(nodeParameter.getIdValue())&&!StringUtils.isEmptyOrWhitespaceOnly(nodeParameter.getIdType());}/*** 图遍历,默认使用DFS* @return List<List<NodeMessage>> 存储多个连通图数据,每个List<NodeMessage>是同一个连通图的数据*/public List<List<NodeMessage>> traversingGraph(){return traversingGraph(true);}/*** DFS or BFS 遍历图,true使用DFS,false使用BFS,NULL默认使用DFS* @param b* @return List<List<NodeMessage>> 存储多个连通图数据,每个List<NodeMessage>是同一个连通图的数据*/public List<List<NodeMessage>> traversingGraph(Boolean b){b = b==null?true:b;//此数据结构是个非联通图,所以遍历完后要存储多组联通图,每个map属于同一个graph。List<List<NodeMessage>> nGraph = new ArrayList<>();Set<String> visited = new HashSet<>();Set<String> nodes = this.adjacentNodeMap.keySet();if(b){for (String start : nodes) {if (!visited.contains(start)) {nGraph.add(dfs(start,visited));}}}else {for (String start : nodes) {if (!visited.contains(start)) {nGraph.add(bfs(start,visited));}}}return nGraph;}/*** 广度优先(BFS)* @param start* @return List<NodeMessage> 存储同一个连通图的数据*/public List<NodeMessage> bfs(String start) {Set<String> visited = new HashSet<>();return bfs(start, visited);}/*** 广度优先(BFS)* @param start* @param visited* @return List<NodeMessage> 存储同一个连通图的数据*/public List<NodeMessage> bfs(String start, Set<String> visited) {List<NodeMessage> nodeMessages = new ArrayList<>();Queue<String> queue = new LinkedList<>();queue.offer(start);visited.add(start);while (!queue.isEmpty()){String current = queue.poll();//存储层次遍历的数据nodeMessages.add(this.nodeMessageMap.get(current));for (String adjacent : adjacentNodeMap.get(current)) {if(!visited.contains(adjacent)){queue.offer(adjacent);visited.add(adjacent);}}}return nodeMessages;}/*** 深度优先搜索(DFS)* @param start* @return List<NodeMessage> 存储同一个连通图的数据*/public List<NodeMessage> dfs(String start) {Set<String> visited = new HashSet<>();return dfs(start, visited);}/*** 深度优先搜索(DFS)* @param start* @return List<NodeMessage> 存储同一个连通图的数据*/public List<NodeMessage> dfs(String start, Set<String> visited) {List<NodeMessage> nodeMessages = new ArrayList<>();dfs(start, visited, nodeMessages);return nodeMessages;}/*** 深度优先搜索(DFS)* @param start* @param visited* @param nodeMessages 存储同一个连通图的数据*/private void dfs(String start, Set<String> visited, List<NodeMessage> nodeMessages) {if(visited.contains(start)) return;visited.add(start);nodeMessages.add(this.nodeMessageMap.get(start));for (String neighbor : this.adjacentNodeMap.get(start)) {dfs(neighbor,visited,nodeMessages);}}}
图的入参单独做了一个bean(可根据自己业务需求自定义,同理上述图算法中的NodeMessage类也可以自定义需要存储的节点信息)
/*** @Author Ma Shuai* @Date 2021-09-03 19:48* @Version 1.0*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GraphNodeParameter {private String idType;private String idValue;private String tableName;private Long pK;
}
测试:(需要解绑的关系不入图,然后再遍历图得到的就是解绑后的结果)
@Testpublic void test1() {//创建图对象UnbindGraph unbindGraph = new UnbindGraph();// 添加连通关系1unbindGraph.addEdge(new GraphNodeParameter("mobile","mobile222","c_center_account_for_uc",123L),new GraphNodeParameter("userId","userId333","c_center_account_for_uc",123L));unbindGraph.addEdge(new GraphNodeParameter("unionId","unionId444","c_center_account_for_user_union",343L),new GraphNodeParameter("userId","userId333","c_center_account_for_user_union",343L));// 添加连通关系2unbindGraph.addEdge(new GraphNodeParameter("wxId","wxId111","c_center_account_for_user_union",24543L),null);unbindGraph.addEdge(new GraphNodeParameter("unionId","","c_center_account_for_user_union",24543L),new GraphNodeParameter("wxId","wxId111","c_center_account_for_user_union",24543L));// 添加连通关系3unbindGraph.addEdge(new GraphNodeParameter("mobile","mobile111","c_center_account_for_uc",24524L),new GraphNodeParameter("userId","userId111","c_center_account_for_uc",24524L));unbindGraph.addEdge(new GraphNodeParameter("unionId","unionId111","c_center_account_for_uc",35634L),new GraphNodeParameter("userId","userId111","c_center_account_for_uc",35634L));unbindGraph.addEdge(new GraphNodeParameter("unionId","unionId111","c_center_account_for_crm",2356345L),new GraphNodeParameter("customerId","customerId111","c_center_account_for_crm",2356345L));//图遍历List<List<UnbindGraph.NodeMessage>> lists = unbindGraph.traversingGraph();for (List<UnbindGraph.NodeMessage> list : lists) {System.out.println(list);}}
打印结果:
解释:idType和idValue相同时算作同一个节点,只不过添加相同节点时可能携带的信息不同。(比如相同的两个节点之间多条线,可以携带线的信息)
[UnbindGraph.NodeMessage(idType=wxId, idValue=wxId111, tableAndPKList={c_center_account_for_user_union=[24543, 24543]})]
[UnbindGraph.NodeMessage(idType=mobile, idValue=6a5af3a1467d36ed640aa7103e372e9c, tableAndPKList={c_center_account_for_uc=[24524]}), UnbindGraph.NodeMessage(idType=userId, idValue=userId111, tableAndPKList={c_center_account_for_uc=[24524, 35634]}), UnbindGraph.NodeMessage(idType=unionId, idValue=unionId111, tableAndPKList={c_center_account_for_uc=[35634], c_center_account_for_crm=[2356345]}), UnbindGraph.NodeMessage(idType=customerId, idValue=customerId111, tableAndPKList={c_center_account_for_crm=[2356345]})]
[UnbindGraph.NodeMessage(idType=unionId, idValue=unionId444, tableAndPKList={c_center_account_for_user_union=[343]}), UnbindGraph.NodeMessage(idType=userId, idValue=userId333, tableAndPKList={c_center_account_for_uc=[123], c_center_account_for_user_union=[343]}), UnbindGraph.NodeMessage(idType=mobile, idValue=557cd727e8f4d02ad2c295aa1d436e82, tableAndPKList={c_center_account_for_uc=[123]})]