最近公司的項目中,有個樹形結構變圖結構的問題。本來我們對項目中實體之間的關系是按樹形結構來表示的,也就是說實體之間不會重用,也不會有環。現在我們需要變成圖的結構,實體之間可以重用,但不能有環。那么該如何解決這個問題呢?
我們先定義出什么是環:
環定義:從一條邊出發,如果能回到當前邊則證明有環。
可見,根據定義,上面的圖不存在環。因為從任意一條邊出發都不可能回到自身。下面給出一個帶有環的圖。
圖中紅色箭頭的3,4,5構成了一個環路。因為從3、4、5任一點出發,都可以回到起始點。那么我們如何使用代碼去判斷出圖中是否有環呢?
代碼邏輯
首先你可能想到的是,判斷節點是否被重用過。但是這個辦法行不通,第一張圖給出的就是這個思路的反例。
正確的辦法是,我們需要明確這是用圖來描述的結構,圖的定義可以參考圖數據結構。
例如上面這張圖,我們可以給出這個圖的所有邊(在項目中,每條邊就是一個ResRelation):
1 -> 2
1 -> 3
1 -> 4
2 -> 4
3 -> 4
4 -> 5
5 -> 3
我們需要維持一個集合color來標記某條邊是否被訪問過,維持一個隊列queue用來進行迭代算法。當迭代遍歷整個圖的時候,首先將1(也可以是任意起始點)加入隊列,由於1的可達路徑為[2,3,4]
。所有我們將1的可達路徑加入隊列。同時color中標記1 -> 2
,1 ->3
,1 -> 4
。
繼續迭代,當前queue中的節點是[2,3,4]
,先彈出2,找到了2的可達路徑為4,同時將2 -> 4
標記為已訪問。節點3同樣的道理。
這時候queue中的值可能重復,因為[1,2,3]
都可達4,代碼中我做了一個去重。因此現在queue中只有4。
尋找4的可達路徑,我們找到了5,同時標記4 -> 5
。根據5尋找可達路徑,我們找到了3,同時標記5 -> 3
。此時queue中只有一個節點3,再次迭代尋找3的可達路徑,我們找到了3 -> 4
,這時候我們發現這個邊被訪問過了。因此這個圖中有環。代碼如圖:
private static boolean isCircle(String id, List<ResRelation> resRelationList) {
boolean isCircle = false;
/* 隊列*/
LinkedList<String> queue = Lists.newLinkedList(Lists.newArrayList(id));
/* 標記集合*/
Set<ResRelation> color=new HashSet<>();
while (queue.size() > 0) {
String parentId = queue.poll();
for (ResRelation i : resRelationList) {
/* 找到節點的對應的關系,也就是邊*/
if (parentId.equals(i.getStart_id())) {
/* 查看邊是否被訪問過*/
if(!color.contains(i)){
color.add(i);
if(!queue.contains(i.getEnd_id())){
/*加入隊列 */
queue.add(i.getEnd_id());
}
}else{
/* 如果重復訪問,則有環*/
isCircle=true;
queue.clear();
break;
}
}
}
}
return isCircle;
}