1、圖論基礎概念 Graph Theory
圖 :是由由 節點 和 邊 組成的數據模型,它有兩個重要部分
- 1、節點
- 2、邊
節點是兩個村, 邊表示兩個村直接連通的道路
或者節點是人, 邊表示人與人之間的關係。
點是一個域名, 邊是域名之間的調整
無向圖:邊是沒有方向的(如兩個村是否有道路連接)
有向圖:邊有方向(人際關係網,你認識他,他不認識你)
有向圖會使圖更加複雜。具有不對稱性。
可以把無向圖認爲是一種特殊有向圖,是雙向的。
無權圖:每條邊是隻表示一種狀態的,沒有值,如人際關係網中,如果兩個人之間是否連接只是表示認識與不認識,則無權圖即可。如下示例:
有權圖:如果兩個人之間的連線除了表示 認識與不認識的狀態,還要表示認識的程度,就可以加上一個數值表示, 5分熟還是9分熟。如下示例:
自環邊:兩個城市之間,不止一條了,可能有三條
平行邊:一個城市自己向外擴散的路。
平行邊和自環邊不會改變 點與點的連通性,通常在較複雜圖中才會涉及。
圖的連通性: 圖中的各個節點是否連通。
鄰接矩陣:一個矩陣, 適合表示稠密圖(Dense Graph),矩陣中每個位置標示兩個點的連接狀態。
鄰接表:每一行是一個鏈表, 適合表示 稀疏圖(Sparse Graph)。和節點0相連的只有節點1, 所以節點0的鏈表就只有 節點1一個點, 而節點1 和 0、2、3 三個節點相連,則這個鏈表存有三個節點。
稠密圖:在所有節點中, 每個節點都和其中很多的其他節點連接,即每個點的邊的數量 不會比節點數量少很多。
稀疏圖(Sparse Graph):在所有節點中, 每個節點都和其中較少的其他節點連接,每個點的邊的數量 遠少於節點數量。
完全圖:每個節點都和其他所有節點相連。
2 圖的基本實現
鄰接矩陣的實現(稠密圖):
/**
* 用鄰接矩陣來表示 無向圖(用於稠密圖)
* @author admin
*
*/
public class DenseGraph {
private int n;//存放圖的節點數量
private int m;//存放圖的邊數量
private boolean directed;//是有向圖還是無向圖
private int[][] g;//創建矩陣
public DenseGraph(int n, boolean directed) {
this.n = n;
this.m = 0;
this.directed = directed;
g = new int[n][n];//先創建一個n階矩陣,由於是int型, 所有值默認爲0
}
/**
* 返回有多少節點
*/
public int getV() {
return n;
}
/**
* 返回有多少條邊
*/
public int getE() {
return m;
}
/**
* 連接兩個節點,添加兩個節點的邊
* @param v
* @param w
*/
public void addEdge(int v, int w) {
g[v][w] = 1;//兩點連接, 用1表示
if(!directed) {
g[w][v] = 1;//如果是無向表,則兩個方向的值都是相等的
}
if(!hasEdge(v, w)) {
m++; //邊增加一條
}
}
/**
* 判斷兩個點是否有邊
*/
private boolean hasEdge(int v, int w) {
return g[v][w] == 1;
}
}
鄰接表的實現(稠密圖):
/**
* 用鄰接表 來表示 無向圖(用於稀疏圖)
* @author admin
*
*/
public class SparseGraph {
private int n;//存放圖的節點數量
private int m;//存放圖的邊數量
private boolean directed;//是有向圖還是無向圖
ArrayList[] g;//鄰接表
public SparseGraph(int n, boolean directed) {
this.n = n;
this.m = 0;
this.directed = directed;
g = new ArrayList[n];//創建n行的鄰接表
for(int i=0; i<n; i++) {
g[i] = new ArrayList<>();
}
}
/**
* 返回有多少節點
*/
public int getV() {
return n;
}
/**
* 返回有多少條邊
*/
public int getE() {
return m;
}
/**
* 連接兩個節點,添加兩個節點的邊
* @param v
* @param w
*/
public void addEdge(int v, int w) {
g[v].add(w);//v和m相連, 則將m添加到v的鏈表中。
if(v!= w && !directed) { //本例中避免自環邊的添加
g[w].add(v);
}
if(!hasEdge(v, w)) {
m++; //邊增加一條
}
}
/**
* 判斷兩個點是否有邊,避免平行邊
*/
private boolean hasEdge(int v, int w) {
return g[v].contains(w);
}
}
3 圖的常見操作----遍歷鄰邊
按照上述實現的代碼,在鄰接矩陣中只需要遍歷每個數組中哪些位置是1即可得到 與之直接相連的點。而鄰接表中, 只需要 遍歷每一行的鏈表即可得到直接連接的點, 很好實現。
但是,爲了不對外暴露我們圖的內部的具體實現,我們可以用一種 很常見很好用的設計模式– "迭代器模式, 來完全屏蔽我們內部對數據的實現,也不用對外直接暴露數據,防止數據被任意篡改,甚至能將接口往上進行抽象, 讓不同圖對外暴數據的接口相同,從而更好的抽象類。
通過迭代器,我們可以遍歷整個圖中的數據,去得到想查找的點 有哪些鄰邊。
在稠密圖的實現中,我們增加一個內部類 作爲迭代器:
static class GraphIterator {
private DenseGraph graph;//要遍歷的圖
private int n;//要遍歷的節點
private int index;//腳標
public GraphIterator(DenseGraph graph, int n) {
this.graph = graph;
this.n = n;
}
public int begin() {
index = -1;
return next();
}
/**
* 返回相連節點的編號
* @return
*/
public int next() {
for(index += 1; index < graph.g[n].length; index++ ) {
if(graph.g[n][index] == 1) {
return index;
}
}
return -1;
}
public boolean isEnd() {
return index >= graph.g[n].length;
}
}
同理的,我們在稀疏圖的實現中,增加一個內部類:
static class GraphIterator {
private SparseGraph graph;//要遍歷的圖
private int n;//要遍歷的節點
private int index;//腳標
public GraphIterator(SparseGraph graph, int n) {
this.graph = graph;
this.n = n;
}
public int begin() {
index = -1;
return next();
}
/**
* 返回相連節點的編號
* @return
*/
public int next() {
for(index += 1; index < graph.g[n].size(); index++ ) {
if((int)graph.g[n].get(index) == 1) {
return index;
}
}
return -1;
}
public boolean isEnd() {
return index >= graph.g[n].size();
}
}