1. 程式人生 > >資料結構複習之【圖】

資料結構複習之【圖】

一、基本術語

:由有窮、非空點集和邊集合組成,簡寫成G(V,E);

Vertex:圖中的頂點;

無向圖:圖中每條邊都沒有方向;

有向圖:圖中每條邊都有方向;

無向邊:邊是沒有方向的,寫為(a,b)

有向邊:邊是有方向的,寫為<a,b>

有向邊也成為弧;開始頂點稱為弧尾,結束頂點稱為弧頭;

簡單圖:不存在指向自己的邊、不存在兩條重複的邊的圖;

無向完全圖:每個頂點之間都有一條邊的無向圖;

有向完全圖:每個頂點之間都有兩條互為相反的邊的無向圖;

稀疏圖:邊相對於頂點來說很少的圖;

稠密圖:邊很多的圖;

權重:圖中的邊可能會帶有一個權重,為了區分邊的長短;

:帶有權重的圖;

:與特定頂點相連線的邊數;

出度、入度:對於有向圖的概念,出度表示此頂點為起點的邊的數目,入度表示此頂點為終點的邊的數目;

:第一個頂點和最後一個頂點相同的路徑;

簡單環:除去第一個頂點和最後一個頂點後沒有重複頂點的環;

連通圖:任意兩個頂點都相互連通的圖;

極大連通子圖:包含竟可能多的頂點(必須是連通的),即找不到另外一個頂點,使得此頂點能夠連線到此極大連通子圖的任意一個頂點;

連通分量:極大連通子圖的數量;

強連通圖:此為有向圖的概念,表示任意兩個頂點a,b,使得a能夠連線到b,b也能連線到a 的圖;

生成樹:n個頂點,n-1條邊,並且保證n個頂點相互連通(不存在環);

最小生成樹

:此生成樹的邊的權重之和是所有生成樹中最小的;

AOV網:結點表示活動的網;

AOE網:邊表示活動的持續時間的網;

二、圖的儲存結構

1.鄰接矩陣

維持一個二維陣列,arr[i][j]表示i到j的邊,如果兩頂點之間存在邊,則為1,否則為0;

維持一個一維陣列,儲存頂點資訊,比如頂點的名字;

下圖為一般的有向圖:


注意:如果我們要看vi節點鄰接的點,則只需要遍歷arr[i]即可;

 下圖為帶有權重的圖的鄰接矩陣表示法:

 

缺點:鄰接矩陣表示法對於稀疏圖來說不合理,因為太浪費空間; 

2.鄰接表

如果圖示一般的圖,則如下圖:


 如果是網,即邊帶有權值,則如下圖:


3.十字連結串列

只針對有向圖;,適用於計算出度和入度;

頂點結點:

邊結點:

 

好處:建立的時間複雜度和鄰接連結串列相同,但是能夠同時計算入度和出度;

4.鄰接多重表

針對無向圖; 如果我們只是單純對節點進行操作,則鄰接表是一個很好的選擇,但是如果我們要在鄰接表中刪除一條邊,則需要刪除四個頂點(因為無向圖);

在鄰接多重表中,只需要刪除一個節點,即可完成邊的刪除,因此比較方便;

因此鄰接多重表適用於對邊進行刪除的操作;

頂點節點和鄰接表沒區別,邊表節點如下圖:

比如:

 

5.邊集陣列

合依次對邊進行操作;

儲存邊的資訊,如下圖:



三、圖的遍歷

DFS

思想:往深裡遍歷,如果不能深入,則回朔;

比如:


	/**
	 * O(v+e)
	 */
	@Test
	public void DFS() {
		for (int i = 0; i < g.nodes.length; i++) {
			if (!visited[i]) {
				DFS_Traverse(g, i);
			}
		}
	}

	private void DFS_Traverse(Graph2 g, int i) {
		visited[i] = true;
		System.out.println(i);
		EdgeNode node = g.nodes[i].next;
		while (node != null) {
			if (!visited[node.idx]) {
				DFS_Traverse(g, node.idx);
			}
			node = node.next;
		}
	}

BFS

思想:對所有鄰接節點遍歷;

/**
	 * O(v+e)
	 */
	@Test
	public void BFS() {
		ArrayList<Integer> list = new ArrayList<Integer>();
		for (int i = 0; i < g.nodes.length; i++) {
			if (!visited[i]) {
				visited[i] = true;
				list.add(i);
				System.out.println(i);
				while (!list.isEmpty()) {
					int k = list.remove(0);
					EdgeNode current = g.nodes[k].next;
					while (current != null) {
						if (!visited[current.idx]) {
							visited[current.idx] = true;
							System.out.println(current.idx);
							list.add(current.idx);
							
						}
						current = current.next;
					}
				}

			}
		}
	}


四、最小生成樹

prim

鄰接矩陣儲存;
/**
	 * 時間複雜度為O(n^2)
	 * 適用於稠密圖
	 */
	@Test
	public void prim(){
		int cost[] = new int[9];
		int pre[] = new int[9];
		
		for(int i=0;i<g1.vertex.length;i++){
			cost[i] = g1.adjMatrix[0][i];
		}
		cost[0] = 0;
		
		for(int i=1;i<g1.vertex.length;i++){
			int min = 65536;
			int k = 0;
			for(int j=1;j<g1.vertex.length;j++){
				if(cost[j]!=0&&cost[j]<min){
					min = cost[j];
					k = j;
				}
			}
			cost[k] = 0;
			System.out.println(pre[k]+","+k);
			for(int j=1;j<g1.vertex.length;j++){
				if(cost[j]!=0&&g1.adjMatrix[k][j]<cost[j]){
					pre[j] = k;
					cost[j] = g1.adjMatrix[k][j];
				}
			}
		}
	}

krustral

邊集陣列儲存;
/**
	 * 時間複雜度:O(eloge)
	 * 適用於稀疏圖
	 */
	@Test
	public void krustral(){
		Edge[] edges = initEdges();
		int parent[] = new int[9];
		for(int i=0;i<edges.length;i++){
			Edge edge = edges[i];
			int m = find(parent,edge.begin);
			int n = find(parent,edge.end);
			if(m!=n){
				parent[m] = n;
				System.out.println(m+","+n);
			}
		}
		
	}
	private static int find(int[] parent, int f) {
		while (parent[f] > 0) {
			f = parent[f];
		}
		return f;
	}

五、最短路徑

dijkstra演算法

鄰接矩陣儲存;
//O(n^2)
	@Test
	public void Dijkstra(){
		int distance[] = new int[9];
		int pre[] = new int[9];
		boolean finished[] = new boolean[9];
		finished[0] = true;
		for(int i=0;i<9;i++){
			distance[i] = g1.adjMatrix[0][i];
		}
		int k = 0;
		for(int i=1;i<9;i++){
			int min = 65536;
			for(int j=0;j<9;j++){
				if(!finished[j]&&distance[j]<min){
					min = distance[j];
					k = j;
				}
			}
			finished[k] = true;
			System.out.println(pre[k]+","+k);
			for(int j=1;j<9;j++){
				if(!finished[j]&&(min+g1.adjMatrix[k][j])<distance[j]){
					distance[j] = min+g1.adjMatrix[k][j];
					pre[j] = k;
				}
			}
		}
	}

Floyd

使用: (1)鄰接矩陣:儲存圖;
/**
	 * O(n^3)
	 * 求出任意頂點之間的距離
	 */
	@Test
	public void floyd(Graph1 g) {
		int i, j, k;
		int length = g.vertex.length;
		int dist[][] = new int[length][length];
		int pre[][] = new int[length][length];
		for (i = 0; i < g.vertex.length; i++) {
			for (j = 0; j < g.vertex.length; j++) {
				pre[i][j] = j;
				dist[i][j] = g.adjMatrix[i][j];
			}
		}
		for (i = 0; i < length; i++) {
			for (j = 0; j < g.vertex.length; j++) {
				for (k = 0; k < g.vertex.length; k++) {
					if (dist[i][j] > dist[i][k] + dist[k][j]) {
						dist[i][j] = dist[i][k] + dist[k][j];
						pre[i][j] = pre[i][k];
					}
				}
			}

		}
		System.out.println();
	}

六、拓撲排序

使用資料結構:

(1)棧:用來存放入度為0的節點;

(2)變種鄰接列表:作為圖的儲存結構;此鄰接列表的頂點節點還需要存放入度屬性;

/**
* O(n+e)
*/
private static String topologicalSort(Graph2 g2) {
		Stack<Integer> s = new Stack<Integer>();
		int count = 0;
		for(int i=0;i<g2.nodes.length;i++){
			if(g2.nodes[i].indegree==0){
				s.push(i);
			}
		}
		while(!s.isEmpty()){
			int value = s.pop();
			System.out.println(value+"、");
			count++;
			EdgeNode node = g2.nodes[value].next;
			while(node!=null){
				g2.nodes[node.idx].indegree--;
				if(g2.nodes[node.idx].indegree==0){
					s.push(node.idx);
				}
				node = node.next;
			}
			
		}
		if(count<g2.nodes.length){
			return "error";
		}
		return "ok";
	}


七、關鍵路徑

使用資料結構: (1)變種鄰接列表:同拓撲排序; (2)變數: ltv表示某個事件的最晚開始時間; etv表示事件最早開始時間; ete表示活動最早開始時間; lte表示活動最晚開始時間;
//O(n+e)
@Test
	public void CriticalPath(){
		
		Stack<Integer> stack = topological_etv();
		int length = stack.size();
		if(stack==null){
			return ;
		}
		else{
			int[]ltv = new int[length];
			for(int i=0;i<stack.size();i++){
				ltv[i] = etv[stack.size()-1];
			}
			//從拓撲排序的最後開始計算ltv
			while(!stack.isEmpty()){
				int top = stack.pop();
				EdgeNode current = g.nodes[top].next;
				while(current!=null){
					int idx = current.idx;
					//最晚發生時間要取所有活動中最早的
					if((ltv[idx]-current.weight)<ltv[top]){
						ltv[top] = ltv[idx]-current.weight;
					}
				}
			}
			int ete = 0;
			int lte = 0;
			for(int j=0;j<length;j++){
				EdgeNode current = g.nodes[j].next;
				while(current!=null){
					int idx = current.idx;
					ete = etv[j];
					lte = ltv[idx]-current.weight;
					if(ete==lte){
						//是關鍵路徑
					}
				}
			}
			
		}
		
		
	}
	private Stack<Integer> topological_etv(){
		Stack<Integer> stack2 = new Stack<Integer>();
		Stack<Integer>stack1 = new Stack<Integer>();
		for(int i=0;i<g.nodes.length;i++){
			if(g.nodes[i].indegree==0){
				stack1.add(i);
			}
		}
		etv[] = new int[g.nodes.length];
		int count = 0;
		while(!stack1.isEmpty()){
			int top = stack1.pop();
			count++;
			stack2.push(top);
			
			EdgeNode current = g.nodes[top].next;
			while(current!=null){
				int idx = current.idx;
				if((--g.nodes[idx].indegree)==0){
					stack1.push(idx);
				}
				if((etv[top]+current.weight)>etv[idx]){
					etv[idx] = etv[top]+current.weight;
				}
				current = current.next;
			}
		}
		if(count<g.nodes.length){
			return null;
		}
		return stack2;
	}