Java數(shù)據(jù)結(jié)構(gòu)中圖的進(jìn)階詳解
有向圖
有向圖的定義及相關(guān)術(shù)語(yǔ)
定義∶ 有向圖是一副具有方向性的圖,是由一組頂點(diǎn)和一組有方向的邊組成的,每條方向的邊都連著 一對(duì)有序的頂點(diǎn)。
出度∶ 由某個(gè)頂點(diǎn)指出的邊的個(gè)數(shù)稱(chēng)為該頂點(diǎn)的出度。
入度: 指向某個(gè)頂點(diǎn)的邊的個(gè)數(shù)稱(chēng)為該頂點(diǎn)的入度。
有向路徑︰ 由一系列頂點(diǎn)組成,對(duì)于其中的每個(gè)頂點(diǎn)都存在一條有向邊,從它指向序列中的下一個(gè)頂點(diǎn)。
有向環(huán)∶ —條至少含有一條邊,且起點(diǎn)和終點(diǎn)相同的有向路徑。

一副有向圖中兩個(gè)頂點(diǎn)v和w可能存在以下四種關(guān)系:
1.沒(méi)有邊相連;
⒉存在從v到w的邊v—>w;
3.存在從w到v的邊w—>V;
4.既存在w到v的邊,也存在v到w的邊,即雙向連接;
理解有向圖是一件比較簡(jiǎn)單的,但如果要通過(guò)眼睛看出復(fù)雜有向圖中的路徑就不是那么容易了。

有向圖API設(shè)計(jì)

在api中設(shè)計(jì)了一個(gè)反向圖,其因?yàn)橛邢驁D的實(shí)現(xiàn)中,用adj方法獲取出來(lái)的是由當(dāng)前頂點(diǎn)v指向的其他頂點(diǎn),如果能得到其反向圖,就可以很容易得到指向v的其他頂點(diǎn)。
有向圖的實(shí)現(xiàn)
// 有向圖
public class Digraph {
// 記錄頂點(diǎn)的數(shù)量
private final int V;
//記錄邊的數(shù)量
private int E;
//定義有向圖的鄰接表
private Queue <Integer>[] adj;
public Digraph (int v) {
//初始化頂點(diǎn)數(shù)量
this.V = v;
//初始化邊的數(shù)量
this.E = 0;
//初始化鄰接表
adj = new LinkedList[v];
//初始化鄰接表的空隊(duì)列
for (int i = 0; i < v; i++) {
adj[i] = new LinkedList<>();
}
}
public int V () {
return V;
}
public int E () {
return E;
}
//添加一條 v -> w的有向邊
public void addEage (int v , int w) {
adj[v].add(w);
++E;
}
//獲取頂點(diǎn)v 指向的 所有頂點(diǎn)
public Queue<Integer> adj (int v) {
return adj[v];
}
//將有向圖 反轉(zhuǎn) 后返回
public Digraph reverse () {
//創(chuàng)建一個(gè)反向圖
Digraph reverseDigraph = new Digraph(V);
//獲取原來(lái)有向圖的每個(gè)結(jié)點(diǎn)
for (int i = 0; i < V; i++) {
//獲取每個(gè)結(jié)點(diǎn) 鄰接表的所有結(jié)點(diǎn)
for (Integer w : adj[i]) {
//反轉(zhuǎn)圖記錄下 w -> v
reverseDigraph.adj(w).add(i);
}
}
return reverseDigraph;
}
}
拓?fù)渑判?/h2>
在現(xiàn)實(shí)生活中,我們經(jīng)常會(huì)同一時(shí)間接到很多任務(wù)去完成,但是這些任務(wù)的完成是有先后次序的。以我們學(xué)習(xí)java學(xué)科為例,我們需要學(xué)習(xí)很多知識(shí),但是這些知識(shí)在學(xué)習(xí)的過(guò)程中是需要按照先后次序來(lái)完成的。從java基礎(chǔ),到j(luò)sp/servlet,到ssm,到springboot等是個(gè)循序漸進(jìn)且有依賴(lài)的過(guò)程。在學(xué)習(xí)jsp前要首先掌握java基礎(chǔ)和html基礎(chǔ),學(xué)習(xí)ssm框架前要掌握jsp/servlet之類(lèi)才行。

為了簡(jiǎn)化問(wèn)題,我們使用整數(shù)為頂點(diǎn)編號(hào)的標(biāo)準(zhǔn)模型來(lái)表示這個(gè)案例:

此時(shí)如果某個(gè)同學(xué)要學(xué)習(xí)這些課程,就需要指定出一個(gè)學(xué)習(xí)的方案,我們只需要對(duì)圖中的頂點(diǎn)進(jìn)行排序,讓它轉(zhuǎn)換為一個(gè)線性序列,就可以解決問(wèn)題,這時(shí)就需要用到一種叫拓?fù)渑判虻乃惴ā?/p>
拓?fù)渑判驁D解
給定一副有向圖,將所有的頂點(diǎn)排序,使得所有的有向邊均從排在前面的元素指向排在后面的元素,此時(shí)就可以明確的表示出每個(gè)頂點(diǎn)的優(yōu)先級(jí)。下列是一副拓?fù)渑判蚝蟮氖疽鈭D︰

檢測(cè)有向圖中的環(huán)
如果學(xué)習(xí)x課程前必須先學(xué)習(xí)y課程,學(xué)習(xí)y課程前必須先學(xué)習(xí)z課程,學(xué)習(xí)z課程前必須先學(xué)習(xí)x課程,那么一定是有問(wèn)題了,我們就沒(méi)有辦法學(xué)習(xí)了,因?yàn)檫@三個(gè)條件沒(méi)有辦法同時(shí)滿(mǎn)足。其實(shí)這三門(mén)課程x、y、z的條件組成了一個(gè)環(huán)︰

因此,如果我們要使用拓?fù)渑判蚪鉀Q優(yōu)先級(jí)問(wèn)題,首先得保證圖中沒(méi)有環(huán)的存在。
檢測(cè)有向環(huán)的API設(shè)計(jì)

檢測(cè)有向環(huán)實(shí)現(xiàn)
在API中添加了onStack[]布爾數(shù)組,索引為圖的頂點(diǎn),當(dāng)我們深度搜索時(shí)︰
1.在如果當(dāng)前頂點(diǎn)正在搜索,則把對(duì)應(yīng)的onStack數(shù)組中的值改為true,標(biāo)識(shí)進(jìn)棧;
2.如果當(dāng)前頂點(diǎn)搜索完畢,則把對(duì)應(yīng)的onStack數(shù)組中的值改為false,標(biāo)識(shí)出棧;
3.如果即將要搜索某個(gè)頂點(diǎn),但該頂點(diǎn)已經(jīng)在棧中,則圖中有環(huán);


代碼
/**
* 檢查圖中是否存在環(huán)
*/
public class DirectedCycle {
/**
* 索引代表頂點(diǎn),用來(lái)記錄頂點(diǎn)是否被搜索過(guò)
*/
private boolean[] marked;
/**
* 判斷圖中是否有環(huán)
*/
private boolean hasCycle;
/**
* 采用棧的思想,記錄當(dāng)前頂點(diǎn)是否已經(jīng)存在 當(dāng)前搜索的的路徑上
* 存在則可以判斷 圖中是存在環(huán)的
*/
private boolean[] onStack;
/**
* 判斷傳入的有向圖 是否存在環(huán)
* @param G
*/
public DirectedCycle (Digraph G) {
marked = new boolean[G.V()];
onStack = new boolean[G.V()];
hasCycle = false;
//因?yàn)椴恢缽哪莻€(gè)點(diǎn)出發(fā) 可能存在環(huán)
//所以需要從所有的頂點(diǎn)都出發(fā)搜索 判斷是否存在環(huán)
for (int i = 0; i < G.V(); i++) {
dfs(G,i);
}
}
/**
* 采用深度搜索 判斷有向圖是否存在環(huán)
* onStack 入棧出棧 然后判斷當(dāng)前搜索的頂點(diǎn)是否已經(jīng)在搜索路徑上
*
* @param G
* @param v
*/
private void dfs (Digraph G,int v) {
//標(biāo)記頂點(diǎn)已經(jīng)搜索過(guò)
marked[v] = true;
for (Integer adj : G.adj(v)) {
//判斷v 是否已經(jīng)在搜索的路徑上了
if(marked[adj] && onStack[adj]) {
//存在環(huán)
hasCycle = true;
}else {
//采用回溯的思路
//讓頂點(diǎn)入棧
onStack[adj] = true;
dfs(G,adj);
//回溯 頂點(diǎn)出棧
onStack[adj] = false;
}
}
}
/**
* 判斷是否存在環(huán)
* @return
*/
public boolean hasCycle(){
return hasCycle;
}
}基于深度優(yōu)先的頂點(diǎn)排序
如果要把圖中的頂點(diǎn)生成線性序列其實(shí)是一件非常簡(jiǎn)單的事,之前我們學(xué)習(xí)并使用了多次深度優(yōu)先搜索,我們會(huì)發(fā)現(xiàn)其實(shí)深度優(yōu)先搜索有一個(gè)特點(diǎn),那就是在一個(gè)連通子圖上,每個(gè)頂點(diǎn)只會(huì)被搜索一次,如果我們能在深度優(yōu)先搜索的基礎(chǔ)上,添加一行代碼,只需要將搜索的頂點(diǎn)放入到線性序列的數(shù)據(jù)結(jié)構(gòu)中,我們就能完成這件事。
頂點(diǎn)排序API設(shè)計(jì)

頂點(diǎn)排序?qū)崿F(xiàn)
在API的設(shè)計(jì)中,我們添加了一個(gè)棧reversePost用來(lái)存儲(chǔ)頂點(diǎn),當(dāng)我們深度搜索圖時(shí),每搜索完畢一個(gè)頂點(diǎn),把該頂點(diǎn)放入到reversePost中,這樣就可以實(shí)現(xiàn)頂點(diǎn)排序。




代碼:
/**
* 深度優(yōu)先搜索 的頂點(diǎn)排序
*/
public class DepthFirstOrder {
/**
* 索引代表頂點(diǎn) ,用來(lái)記錄頂點(diǎn)是否已經(jīng)被搜索過(guò)了
*/
private boolean[] marked;
/**
* 使用棧記錄深度優(yōu)先搜索下的頂點(diǎn)
*/
private Stack<Integer> reversePost;
public DepthFirstOrder (Digraph G) {
marked = new boolean[G.V()];
reversePost = new Stack<>();
for (int i = 0; i < G.V(); i++) {
//如果頂點(diǎn)已經(jīng)被搜索過(guò)則不用
if(!marked[i])
dfs(G,i);
}
}
/**
* 基于深度優(yōu)先搜索,生成頂點(diǎn)線性序列
* @param G
* @param v
*/
private void dfs (Digraph G, int v) {
//標(biāo)記頂點(diǎn)已經(jīng)被搜索過(guò)
marked[v] = true;
for (Integer w : G.adj(v)) {
if(!marked[w])
dfs(G,w);
}
//記錄到線性序列中
reversePost.push(v);
}
/**
* 獲取頂點(diǎn)線性序列
* @return
*/
private Stack<Integer> ReversePost() {
return reversePost;
}
}到此這篇關(guān)于Java數(shù)據(jù)結(jié)構(gòu)中圖的進(jìn)階詳解的文章就介紹到這了,更多相關(guān)Java 圖的進(jìn)階內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java數(shù)據(jù)結(jié)構(gòu)最清晰圖解二叉樹(shù)前 中 后序遍歷
- Java數(shù)據(jù)結(jié)構(gòu)之圖的原理與實(shí)現(xiàn)
- 帶你了解Java數(shù)據(jù)結(jié)構(gòu)和算法之無(wú)權(quán)無(wú)向圖
- Java數(shù)據(jù)結(jié)構(gòu)之圖的領(lǐng)接矩陣詳解
- java數(shù)據(jù)結(jié)構(gòu)圖論霍夫曼樹(shù)及其編碼示例詳解
- Java數(shù)據(jù)結(jié)構(gòu)之圖(動(dòng)力節(jié)點(diǎn)Java學(xué)院整理)
相關(guān)文章
淺談Java中Spring Boot的優(yōu)勢(shì)
在本篇文章中小編給大家分析了Java中Spring Boot的優(yōu)勢(shì)以及相關(guān)知識(shí)點(diǎn)內(nèi)容,興趣的朋友們可以學(xué)習(xí)參考下。2018-09-09
Java根據(jù)開(kāi)始時(shí)間和結(jié)束時(shí)間及周幾計(jì)算日期的示例代碼
在Java 7中,java.time包不存在,所以我們需要使用java.util.Calendar和java.util.Date類(lèi)來(lái)實(shí)現(xiàn)類(lèi)似的功能,這篇文章主要介紹了Java根據(jù)開(kāi)始時(shí)間和結(jié)束時(shí)間及周幾計(jì)算出日期的示例代碼,需要的朋友可以參考下2024-06-06
詳解Java中的checked異常和unchecked異常區(qū)別
這篇文章主要介紹了詳解Java中的checked異常和unchecked異常區(qū)別,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02
使用JAXBContext 設(shè)置xml節(jié)點(diǎn)屬性
這篇文章主要介紹了使用JAXBContext 設(shè)置xml節(jié)點(diǎn)屬性的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
mybatis insert foreach循環(huán)插入方式
這篇文章主要介紹了mybatis insert foreach循環(huán)插入方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Mybatis-plus動(dòng)態(tài)條件查詢(xún)QueryWrapper的使用案例
mybatis-plus框架功能很強(qiáng)大,把很多功能都集成了,下面這篇文章主要給大家介紹了關(guān)于Mybatis-plus動(dòng)態(tài)條件查詢(xún)QueryWrapper的使用教程,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07

