Java實(shí)現(xiàn)拓?fù)渑判蛩惴ǖ氖纠a
拓?fù)渑判蛟?/h2>
1.點(diǎn)睛
一個(gè)無(wú)環(huán)的有向圖被稱(chēng)為有向無(wú)環(huán)圖。有向無(wú)環(huán)圖是描述一個(gè)工程、計(jì)劃、生產(chǎn)、系統(tǒng)等流程的有效工具。一個(gè)大工程可分為若干子工程(活動(dòng)),活動(dòng)之間通常有一定的約束,例如先做什么活動(dòng),有什么活動(dòng)完成后才可以開(kāi)始下一個(gè)活動(dòng)。
用節(jié)點(diǎn)表示活動(dòng),用弧表示活動(dòng)之間的優(yōu)先關(guān)系的有向圖,被稱(chēng)為 AOV 網(wǎng)。
在 AOV 網(wǎng)中,若從節(jié)點(diǎn) i 到節(jié)點(diǎn) j 存在一條有向路徑,則稱(chēng)節(jié)點(diǎn) i 是節(jié)點(diǎn) j 的前驅(qū),或者稱(chēng)節(jié)點(diǎn) j 是節(jié)點(diǎn) i 的后繼。若<i,j>是圖中的弧,則稱(chēng)節(jié)點(diǎn) i 是節(jié)點(diǎn) j 的直接前驅(qū),節(jié)點(diǎn) j 是節(jié)點(diǎn) i 的直接后繼。
在 AOV 網(wǎng)中弧表示了活動(dòng)之間存在的制約關(guān)系。例如,計(jì)算機(jī)專(zhuān)業(yè)的學(xué)生必須完成一系列規(guī)定的基礎(chǔ)課和專(zhuān)業(yè)課才能畢業(yè)。學(xué)生按照怎樣的順序來(lái)學(xué)習(xí)這些課程呢?
課程的名稱(chēng)與相應(yīng)編號(hào)如下表所示。
| 課程編號(hào) | 課程名稱(chēng) | 先修課程 |
| C | 程序設(shè)計(jì)基礎(chǔ) | 無(wú) |
| C1 | 數(shù)據(jù)結(jié)構(gòu) | C0、C2 |
| C2 | 離散數(shù)學(xué) | C0 |
| C3 | 高級(jí)程序設(shè)計(jì) | C0、C |
| C4 | 數(shù)值分析 | C2、C3、C5 |
| C5 | 高等數(shù)學(xué) | 無(wú) |
如果用節(jié)點(diǎn)表示課程,用弧表示先修關(guān)系,若課程 i 是課程 j 的先修課程,則用弧<i,j>表示,課程之間的關(guān)系如下圖所示。

在 AOV 中不允許有環(huán),否則會(huì)出現(xiàn)自己是自己的前驅(qū)情況,陷入死循環(huán)。怎么判斷在 AOV 網(wǎng)中是否有環(huán)呢?一種檢測(cè)的辦法就是對(duì)有向圖中的節(jié)點(diǎn)進(jìn)行拓?fù)渑判?。如?nbsp;AOV 網(wǎng)中的所有節(jié)點(diǎn)都在拓?fù)湫蛄兄校瑒t在 AOV 網(wǎng)中必定無(wú)環(huán)。
2.拓?fù)渑判?/h3>
拓?fù)渑判蛑笇?nbsp;AOV 網(wǎng)中的節(jié)點(diǎn)排成一個(gè)線(xiàn)性序列,該序列必須滿(mǎn)足:若從節(jié)點(diǎn) i 到節(jié)點(diǎn) j 有一條路徑,則在該序列中節(jié)點(diǎn) i 一定在節(jié)點(diǎn) j 之前。
拓?fù)渑判虻幕舅枷耄?/p>
1 選擇一個(gè)無(wú)前驅(qū)的節(jié)點(diǎn)并輸出。
2 從圖中刪除該節(jié)點(diǎn)和該節(jié)點(diǎn)所有發(fā)出邊。
3 重復(fù)步驟1、2,直到不存在無(wú)前驅(qū)的節(jié)點(diǎn)。
4 如果輸出的節(jié)點(diǎn)數(shù)少于 AOV 網(wǎng)中節(jié)點(diǎn)數(shù),則說(shuō)網(wǎng)中有環(huán),否則輸出的序列即拓?fù)湫蛄小?/p>
拓?fù)渑判虿⒉皇俏ㄒ坏?,例如上圖中,節(jié)點(diǎn) C0 和 C5 都無(wú)前驅(qū),先輸出哪一個(gè)都可以。
下面圖解是其中一種刪除順序。

拓?fù)湫蛄袨椋篊0、C5、C3、C2、C1、C4
在上述過(guò)程中有刪除節(jié)點(diǎn)和邊的操作,實(shí)際上,沒(méi)必要真的刪除節(jié)點(diǎn)和邊。可以將沒(méi)有前驅(qū)的節(jié)點(diǎn)(入度為0)暫存到棧中,輸出時(shí)出棧即表示刪除。進(jìn)行邊的刪除時(shí)將其鄰接點(diǎn)的入度減1即可。例如下圖中刪除 C0 的所有發(fā)出邊,相對(duì)于 C3、C2、C1 節(jié)點(diǎn)入度減1。

3.算法步驟
1 求各節(jié)點(diǎn)的入度,將其存入數(shù)組 indegree[] 中,并將入度為 0 的節(jié)點(diǎn)入棧 S。
2 如果棧不空,則重復(fù)執(zhí)行以下操作:將棧頂元素 i 出棧并保存到拓?fù)湫蛄袛?shù)組 topo[] 中;將節(jié)點(diǎn) i 的所有鄰節(jié)點(diǎn)入度都減 1,如果減 1 后入度為 0,則立即入棧 S。
3 如果輸出的節(jié)點(diǎn)數(shù)少于 AOV 網(wǎng)中的節(jié)點(diǎn)數(shù),則說(shuō)明網(wǎng)中有環(huán),否則輸出拓?fù)湫蛄小?/p>
4.圖解
AOV 網(wǎng)如下圖所示。

1 輸入邊時(shí),累加節(jié)點(diǎn)入度并保存到數(shù)組 indegree[] 中,將入度為0 的節(jié)點(diǎn)入棧 S。

2 將棧頂元素 5 出棧并保存到拓?fù)湫蛄袛?shù)組 topo[] 中。將節(jié)點(diǎn) 5 的所有鄰接點(diǎn)(C3、C4)入度都減1,如果減1后,入度為0,則立即入棧 S。

3 將棧頂元素 0 出棧并保存到拓?fù)湫蛄袛?shù)組 topo[] 中。將節(jié)點(diǎn) 0 的所有鄰接點(diǎn)(C1、C2、C4)入度都減1,如果減1后,入度為0,則立即入棧 S。

4 將棧頂元素 3 出棧并保存到拓?fù)湫蛄袛?shù)組 topo[] 中。將節(jié)點(diǎn) 3 的所有鄰接點(diǎn)(C4)入度都減1,如果減1后,入度為0,則立即入棧 S。

5 將棧頂元素 2 出棧并保存到拓?fù)湫蛄袛?shù)組 topo[] 中。將節(jié)點(diǎn) 2 的所有鄰接點(diǎn)(C1、C4)入度都減1,如果減1后,入度為0,則立即入棧 S。

6 將棧頂元素 4 出棧并保存到拓?fù)湫蛄袛?shù)組 topo[] 中。節(jié)點(diǎn) 4 沒(méi)有鄰接點(diǎn)。

7 將棧頂元素 1 出棧并保存到拓?fù)湫蛄袛?shù)組 topo[] 中。節(jié)點(diǎn) 1 沒(méi)有鄰接點(diǎn)。

8 ??眨惴ㄍV?。輸出拓?fù)渑判蛐蛄小?/p>

拓?fù)渑判蛩惴▽?shí)現(xiàn)
1.拓?fù)鋱D

2.實(shí)現(xiàn)代碼
package graph.topoSort;
import java.util.Scanner;
import java.util.Stack;
public class TopoSort {
static final int maxn = 105;
static int map[][] = new int[maxn][maxn];
static int indegree[] = new int[maxn];
static int topo[] = new int[maxn];
static int n, m;
static Stack<Integer> s = new Stack<>();
static boolean TopoSort() { // 拓?fù)渑判?
int cnt = 0;
for (int i = 0; i < n; i++)
if (indegree[i] == 0)
s.push(i);
while (!s.empty()) {
int u = s.peek();
s.pop();
topo[cnt++] = u;
for (int j = 0; j < n; j++)
if (map[u][j] == 1)
if (--indegree[j] == 0)
s.push(j);
}
if (cnt < n) {
return false;
}
return true;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
for (int i = 0; i < m; i++) {
int u, v;
u = scanner.nextInt();
v = scanner.nextInt();
map[u][v] = 1;
indegree[v]++;
}
TopoSort();
for (int i = 0; i < n - 1; i++)
System.out.print(topo[i] + " ");
System.out.println(topo[n - 1]);
}
}3.測(cè)試

以上就是Java實(shí)現(xiàn)拓?fù)渑判蛩惴ǖ氖纠a的詳細(xì)內(nèi)容,更多關(guān)于Java拓?fù)渑判蛩惴ǖ馁Y料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Apache?Commons?Imaging處理圖像實(shí)例深究
這篇文章主要為大家介紹了Apache?Commons?Imaging處理圖像的實(shí)例探索深究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
javaBean的基礎(chǔ)知識(shí)及常見(jiàn)亂碼解決方法
這篇文章主要介紹了javaBean的基礎(chǔ)知識(shí)及常見(jiàn)亂碼解決方法的相關(guān)資料,需要的朋友可以參考下2017-03-03
自定義JmsListenerContainerFactory時(shí),containerFactory字段解讀
這篇文章主要介紹了自定義JmsListenerContainerFactory時(shí),containerFactory字段解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
Java filter中的chain.doFilter使用詳解
這篇文章主要介紹了Java filter中的chain.doFilter使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
EasyExcel實(shí)現(xiàn)讀寫(xiě)Excel文件的示例代碼
EasyExcel是阿里巴巴開(kāi)源的一個(gè)excel處理框架,以使用簡(jiǎn)單、節(jié)省內(nèi)存著稱(chēng)。它可以在盡可能節(jié)約內(nèi)存的情況下支持讀寫(xiě)百M(fèi)的Excel,所以本文就將利用它實(shí)現(xiàn)讀寫(xiě)Excel文件,感興趣的可以了解一下2022-08-08
SpringBoot整合minio快速入門(mén)教程(代碼示例)
這篇文章主要介紹了SpringBoot整合minio快速入門(mén)實(shí)現(xiàn)文件上傳和下載的示例代碼,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-04-04
Java實(shí)現(xiàn)HashMap排序方法的示例詳解
這篇文章主要通過(guò)一些示例為大家介紹了Java對(duì)HashMap進(jìn)行排序的方法,幫助大家更好的理解和使用Java,感興趣的朋友可以了解一下2022-05-05
mybatis 通過(guò)攔截器打印完整的sql語(yǔ)句以及執(zhí)行結(jié)果操作
這篇文章主要介紹了mybatis 通過(guò)攔截器打印完整的sql語(yǔ)句以及執(zhí)行結(jié)果操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10
數(shù)組重排序(如何將所有奇數(shù)都放在所有偶數(shù)前面)的深入分析
本篇文章是對(duì)數(shù)組重排序(如何將所有奇數(shù)都放在所有偶數(shù)前面)的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06

