直接上代码吧。
/**
* 这是一个无向图数据结构,支持非联通。
* 支持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<>();
}
@Data
public class NodeMessage {
private String idType;
private String idValue;
// key是业务来源table_name(业务来源唯一对应一个表) value是表主键PK。 Long类型有需要时可换成bean
private 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:idvalue
private 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 adjacentNodeMap
if(!this.adjacentNodeMap.containsKey(idTypeValue)){
this.adjacentNodeMap.put(idTypeValue,new HashSet<>());
}
// add NodeMessage
if(!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){
return
nodeParameter != 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;
}
测试:(需要解绑的关系不入图,然后再遍历图得到的就是解绑后的结果)
@Test
public void test1() {
//创建图对象
UnbindGraph unbindGraph = new UnbindGraph();
// 添加连通关系1
unbindGraph.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)
);
// 添加连通关系2
unbindGraph.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)
);
// 添加连通关系3
unbindGraph.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]})]