算法與數據結構--圖論基礎知識

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();
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章