1. 程式人生 > 其它 >圖的儲存結構與實現總結

圖的儲存結構與實現總結

目錄


圖的儲存結構

圖的儲存結構主要分兩種,一種是鄰接矩陣,一種是鄰接表

鄰接矩陣

圖的鄰接矩陣儲存方式是用兩個陣列來表示圖一個一維陣列儲存圖中頂點資訊,一個二維陣列(鄰接矩陣)儲存圖中的邊或弧的資訊。
設圖G有n個頂點,則鄰接矩陣是一個n*n的方陣,定義為:

看一個例項,下圖左就是一個無向圖。

從上面可以看出,無向圖的邊陣列是一個對稱矩陣。所謂對稱矩陣就是n階矩陣的元滿足aij = aji。即從矩陣的左上角到右下角的主對角線為軸,右上角的元和左下角相對應的元全都是相等的。
從這個矩陣中,很容易知道圖中的資訊。
(1)要判斷任意兩頂點是否有邊無邊就很容易了;
(2)要知道某個頂點的度,其實就是這個頂點vi在鄰接矩陣中第i行或(第i列)的元素之和;


(3)求頂點vi的所有鄰接點就是將矩陣中第i行元素掃描一遍,arc[i][j]為1就是鄰接點;
而有向圖講究入度和出度,頂點vi的入度為1,正好是第i列各數之和。頂點vi的出度為2,即第i行的各數之和。
若圖G是網圖,有n個頂點,則鄰接矩陣是一個n*n的方陣,定義為:

鄰接表

鄰接矩陣是不錯的一種圖儲存結構,但是,對於邊數相對頂點較少的圖,這種結構存在對儲存空間的極大浪費。因此,找到一種陣列與連結串列相結合的儲存方法稱為鄰接表。
鄰接表的處理方法是這樣的:
(1)圖中頂點用一個一維陣列儲存,當然,頂點也可以用單鏈表來儲存,不過,陣列可以較容易的讀取頂點的資訊,更加方便。
(2)圖中每個頂點vi的所有鄰接點構成一個線性表,由於鄰接點的個數不定,所以,用單鏈表儲存

,無向圖稱為頂點vi的邊表,有向圖則稱為頂點vi作為弧尾的出邊表
例如,下圖就是一個無向圖的鄰接表的結構。

從圖中可以看出,頂點表的各個結點由data和firstedge兩個域表示,data是資料域,儲存頂點的資訊firstedge是指標域,指向邊表的第一個結點,即此頂點的第一個鄰接點邊表結點由adjvex和next兩個域組成。adjvex是鄰接點域,儲存某頂點的鄰接點在頂點表中的下標,next則儲存指向邊表中下一個結點的指標。
對於帶權值的網圖,可以在邊表結點定義中再增加一個weight的資料域,儲存權值資訊即可。如下圖所示。

兩者區別

對於一個具有n個頂點e條邊的無向圖
它的鄰接表表示有n個頂點表結點2e個邊表結點
對於一個具有n個頂點e條邊的有向圖
它的鄰接表表示有n個頂點表結點e個邊表結點
如果圖中邊的數目遠遠小於n^2稱作稀疏圖,這是用鄰接表表示比用鄰接矩陣表示節省空間;
如果圖中邊的數目接近於n^2,對於無向圖接近於n*(n-1)稱作稠密圖,考慮到鄰接表中要附加鏈域,採用鄰接矩陣表示法為宜。

圖的java實現

這個實現是基於鄰接矩陣的

頂點

使用label作為頂點的標識

edgelist作為linkedlist,儲存以這個頂點為起點的邊

後面3個屬性是為了應對其他操作(比如深度遍歷等),特意保留的變數

package datastructure.graph.adjacencymatrixgraph;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;


/**鄰接矩陣的頂點類
 * @author xusy
 *
 * @param <T>
 */
public class Vertex<T> {
	
	/**
	 * 能夠標識這個定點的屬性,可以用不同型別來標識頂點如String,Integer....
	 */
	private T label;
	
	/**
	 * 這個定點對應的邊<br>
	 * 如果為有向圖,則代表以這個定點為起點的邊
	 */
	private List<Edge> edgeList;
	
	/**
	 * 表示這個頂點是否已被訪問,在bfs和dfs中會被使用到
	 */
	private boolean visited;
	
	/**
	 * 該頂點的前驅節點<br>
	 * 在求圖中某兩個頂點之間的最短路徑時,在從起始頂點遍歷過程中,需要記錄下遍歷到某個頂點時的前驅頂點
	 */
	private Vertex previousVertex;
	
	/**
	 * 這個定點的權值(注意不是邊的權值)
	 */
	private double cost;
	
	/**建立頂點
	 * @param label  這個頂點的標識
	 * @param cost  這個頂點的權值
	 */
	public Vertex(T label,double cost){
		this.label=label;
		//用連結串列儲存邊
		edgeList=new LinkedList<>();
		visited=false;
		previousVertex=null;
		this.cost=cost;
	}
	
	//下面與頂點的標識相關
	
	/**返回頂點的標識
	 * @return
	 */
	public T getLabel() {
		return label;
	}

	/** 
	 * 根據頂點的標識確定是否是同一個頂點
	 */
	@Override
	public boolean equals(Object otherVertex) {
		boolean result;
		//如果otherVertex為空或者類不同,直接返回false
		if(otherVertex==null||getClass()!=otherVertex.getClass()){
			return false;
		}
		Vertex other=(Vertex)otherVertex;
		//根據label確定是否是同一個頂點
		result=label.equals(other.getLabel());
		return result;		
	}

	//下面與頂點的邊相關
	
	/** 返回邊的迭代器
	 * @return
	 */
	public Iterator<Edge> getEdgeIterator(){
		return edgeList.iterator();
	}
	
	/**返回是否有以這個頂點為出發點的邊數
	 * @return
	 */
	public int getEdgeCount(){
		return edgeList.size();
	}

	/**將這個頂點與endVertex連線,邊的權值為weight
	 * @param endVertex
	 * @param weight
	 * @return 如果頂點已經與endVertex連線,那麼將會更新權值,返回false<br>
	 * 如果頂點沒有與endVertex相連,則互相連線,返回true
	 */
	public boolean connect(Vertex endVertex,double weight){
		Iterator<Edge> iterator=getEdgeIterator();
		Edge edge=null;
		Vertex vertex=null;
		while(iterator.hasNext()){
			edge=iterator.next();
			vertex=edge.getEndVertex();
			if(vertex.equals(endVertex)){
				//如果頂點已經與endVertex連線,那麼將會更新權值,返回false
				edge.setWeight(weight);
				return false;
			}
		}
		//如果頂點沒有與endVertex相連,則互相連線,返回true
		edge=new Edge(this,endVertex, weight);
		edgeList.add(edge);		
		return true;
	}
	
	/**將這個頂點與endVertex連線的邊刪除
	 * @param endVertex
	 * @return  如果頂點已經與endVertex連線,那麼將會刪除這條邊,返回true<br>
	 * 如果頂點沒有與endVertex連線,則啥都不做,返回false
	 */
	public boolean disconnect(Vertex endVertex){
		Iterator<Edge> iterator=getEdgeIterator();
		Edge edge=null;
		Vertex vertex=null;
		while(iterator.hasNext()){
			edge=iterator.next();
			vertex=edge.getEndVertex();
			if(vertex.equals(endVertex)){
				//如果頂點已經與endVertex連線,那麼將會刪除這條邊,返回true
				//edgeList.remove(edge);
				iterator.remove();
				return true;
			}
		}
		//如果頂點沒有與endVertex連線,則啥都不做,返回false		
		return false;
	}	
	
	/**返回是否有以這個頂點為出發點,以endVertex為結束點的邊
	 * @return 如果有,返回那條邊<br>
	 * 如果沒有,返回null
	 */
	public Edge hasNeighbourVertex(Vertex endVertex){
		Iterator<Edge> iterator=getEdgeIterator();
		Edge edge=null;
		Vertex vertex=null;
		while(iterator.hasNext()){
			edge=iterator.next();
			vertex=edge.getEndVertex();
			if(vertex.equals(endVertex)){
				//如果頂點已經與endVertex連線,那麼將返回這個邊			
				return edge;
			}
		}
		//沒有則返回null
		return null;
	}

	
	
	//下面是與頂點是否被訪問相關
	
	/**返回頂點是否被訪問
	 * @return
	 */
	public boolean isVisited() {
		return visited;
	}
	
	/**
	 * 訪問這個頂點
	 */
	public void visit(){
		visited=true;
	}
	
	/**
	 * 不訪問這個頂點,或者說是清除訪問狀態
	 */
	public void unVisit(){
		visited=false;
	}
	
	/**獲得以這個頂點為出發點,相鄰的第一個沒有被訪問的頂點
	 * @return 如果沒有,返回null<br>
	 * 如果有,返回對應的頂點
	 */
	public Vertex getUnvisitedVertex(){
		Iterator<Edge> iterator=getEdgeIterator();
		Edge edge=null;
		Vertex vertex=null;
		while(iterator.hasNext()){
			edge=iterator.next();
			vertex=edge.getEndVertex();
			if(vertex.isVisited()==false){
				return vertex;
			}
		}
		//沒有則返回null
		return null;
	}
	
	//下面與前驅節點相關	

	/**返回頂點的前驅節點
	 * @return
	 */
	public Vertex getPreviousVertex() {
		return previousVertex;
	}

	/**設定頂點的前驅節點
	 * @param previousVertex
	 */
	public void setPreviousVertex(Vertex previousVertex) {
		this.previousVertex = previousVertex;
	}

	//下面與頂點的權值相關
	
	/**返回頂點的權值
	 * @return
	 */
	public double getCost() {
		return cost;
	}

	/** 設定頂點的權值
	 * @param cost
	 */
	public void setCost(double cost) {
		this.cost = cost;
	}
	
	
	
	
	
	
}

beginVertex是開始點

endVertex是結束點

weight為邊的權值

package datastructure.graph.adjacencymatrixgraph;

/** 連線兩個頂點的邊
 * @author xusy
 *
 */
public class Edge {
	
	/**
	 * beginVertex是邊的起始頂點<br>
	 * 普通情況是不用顯示地儲存beginVertex,但是生成最小生成樹時需要
	 */
	private Vertex beginVertex;
	
	/**
	 * 由於Edge是儲存在Vertex中的,所以包含這個邊的vertex是開始點
	 * endVertex是結束點
	 */
	private Vertex endVertex;
	
	/**
	 * 邊的權值
	 */
	private double weight;

	/**建立邊
	 * @param beginVertex 邊的開始點
	 * @param endVertex 邊的結束點
	 * @param weight  邊的權值
	 */
	public Edge(Vertex beginVertex,Vertex endVertex, double weight) {
		this.beginVertex = beginVertex;
		this.endVertex = endVertex;
		this.weight = weight;
	}

		
	/**返回邊的開始點
	 * @return
	 */
	public Vertex getBeginVertex() {
		return beginVertex;
	}

	/** 返回邊的結束點
	 * @return
	 */
	public Vertex getEndVertex() {
		return endVertex;
	}

	/**返回邊的權值
	 * @return
	 */
	public double getWeight() {
		return weight;
	}

	/**設定邊的權值
	 * @param weight
	 */
	public void setWeight(double weight) {
		this.weight = weight;
	}
	
	
	
	

}

isDirect用來區分有向圖,區別就是加入邊的時候,無向圖會加入兩條,有向圖只會加入一條

如果不需要邊和頂點的權值,加入時,設定為0即可

package datastructure.graph.adjacencymatrixgraph;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

/**鄰接矩陣的圖類
 * @author xusy
 *
 * @param <T>
 */
public class Graph<T> {
	
	/**
	 * 用來儲存頂點
	 * T做為標識,vertext作為實際的頂點
	 */
	private Map<T, Vertex<T>> vertexMap;
	
	/**
	 * 圖中邊的數目<br>
	 * 頂點的數目可以用vertexMap.size()
	 */
	private int edgeCount;
	
	/**
	 * 圖是否為有向圖<br>
	 * 如果是有向圖,則為true
	 */
	boolean isDirect;
	
	/**圖的建構函式
	 * @param isDirect  圖是否為有向圖<br>
	 * 如果是有向圖,則為true
	 */
	public Graph(boolean isDirect){
		vertexMap=new LinkedHashMap<>();
		edgeCount=0;
		this.isDirect=isDirect;
	}
	

	//下面與圖的頂點相關
	
	/**返回圖中的頂點個數
	 * @return
	 */
	public int getVertexCount(){
		return vertexMap.size();
	}
	
	/** 返回圖的頂點的迭代器
	 * @return
	 */
	public Iterator<Vertex<T>> getVertexIterator(){
		return vertexMap.values().iterator();
	}
	
	/**在圖中插入節點,節點的標識為label,節點的權值為cost
	 * @param label
	 * @param cost  如果不需要節點的權值,則設0即可
	 * @return 如果圖中不存在該節點,則插入,返回true<br>
	 * 如果圖中已經存在該節點,則更新權值,返回false
	 */
	public boolean addVertex(T label,double cost){
		Vertex vertex=vertexMap.get(label);
		if(vertex!=null){
			//如果圖中已經存在該節點,則更新權值,返回false
			vertex.setCost(cost);
			return false;
		}
		//如果圖中不存在該節點,則插入,返回true
		vertex=new Vertex<T>(label, cost);
		vertexMap.put(label, vertex);
		return true;
	}
	
	//下面與圖的邊相關
	
	/** 返回圖中所有的邊的個數<br>
	 * 如果為有向圖,則是所有的有向邊的個數<br>
	 * 如果為無向圖,則視一條邊為兩條相反的有向邊,相當於返回無向邊的個數*2
	 * @return
	 */
	public int getEdgeCount(){
		Iterator<Vertex<T>> iterator=getVertexIterator();
		int count=0;
		while(iterator.hasNext()){
			Vertex<T> vertex=iterator.next();
			count=count+vertex.getEdgeCount();
		}
		return count;
	}
	
	/** 返回圖中標識為label的頂點作為出發點的邊的個數
	 * @param label
	 * @return 如果為有向圖,則返回標識為label的頂點作為出發點的邊的個數
	 * 如果為無向圖,則返回標識為label的頂點相連線的邊的個數
	 * 如果圖中沒有這個頂點,返回-1
	 */
	public int getEdgeCount(T label){
		Vertex<T> vertex=vertexMap.get(label);
		if(vertex==null){
			//如果圖中沒有這個頂點,返回-1
			return -1;
		}
		//返回途中標識為label的頂點作為出發點的邊的個數
		return vertex.getEdgeCount();
	}
	
	/** 返回圖中標識為label的頂點作為出發點的邊的迭代器
	 * @param label
	 * @return 如果沒有這個頂點,返回null
	 */
	public Iterator<Edge> getEdgeIterator(T label){
		Vertex<T> vertex=vertexMap.get(label);
		if(vertex==null){
			//如果圖中沒有這個頂點,返回null
			return null;
		}
		return vertex.getEdgeIterator();
	}
	
	
	/**在圖中加入一條邊,如果isDirect為true,則為有向圖,則<br>
	 * 建立一條以begin作為標識的節點開始的邊,以end作為標識的節點結束,邊的權值為weight<br>
	 * 如果isDirect為false,則為無向圖,則<br>
	 * 建立兩條邊,一條以begin開始,到end ,一條以end開始,到begin
	 * @param begin
	 * @param end
	 * @param weight 如果不需要邊的權值,可以設為0
	 * @return 如果沒有對應的邊,則加入對應的邊,返回true<br>
	 * 如果有對應的邊,則更新weight,返回false
	 * 如果沒有以begin或者end標識的頂點,則直接返回false
	 */
	public boolean addEdge(T begin,T end,double weight){
		Vertex beginVertex=vertexMap.get(begin);
		Vertex endVertex=vertexMap.get(end);
		if(beginVertex==null||endVertex==null){
			//如果沒有以begin或者end標識的頂點,則直接返回false
			return false;
		}
		//有向圖和無向圖都要建立begin到end的邊
		//如果頂點已經與endVertex連線,那麼將會更新權值,result=false
		//如果頂點沒有與endVertex相連,則互相連線,result=true
		boolean result=beginVertex.connect(endVertex, weight);
		if(result){
			edgeCount++;
		}
		if(!isDirect){
			//如果不是有向圖,則建立兩條邊,一條以end開始,到begin
			endVertex.connect(beginVertex, weight);
			if(result){
				edgeCount++;
			}
		}				
		return result;
	}
	
	/**在圖中刪除一條邊,如果isDirect為true,則為有向圖,則<br>
	 * 刪除一條以begin作為標識的節點開始的邊,以end作為標識的節點結束<br>
	 * 如果isDirect為false,則為無向圖,則<br>
	 * 刪除兩條邊,一條以begin開始,到end ,一條以end開始,到begin
	 * @param begin
	 * @param end
	 * @return 如果有對應的邊,則刪除對應的邊,返回true<br>
	 * 如果沒有有對應的邊,則直接返回false
	 * 如果沒有以begin或者end標識的頂點,則直接返回false
	 */
	public boolean removeEdge(T begin,T end){
		Vertex beginVertex=vertexMap.get(begin);
		Vertex endVertex=vertexMap.get(end);
		if(beginVertex==null||endVertex==null){
			//如果沒有以begin或者end標識的頂點,則直接返回false
			return false;
		}
		//有向圖和無向圖都要刪除begin到end的邊
		//如果頂點已經與endVertex連線,那麼將會刪除這條邊,返回true
		//如果頂點沒有與endVertex連線,則啥都不做,返回false
		boolean result=beginVertex.disconnect(endVertex);
		if(result){
			edgeCount--;
		}
		if(!isDirect){
			//如果不是有向圖,則刪除兩條邊,一條以end開始,到begin
			endVertex.disconnect(beginVertex);
			if(result){
				edgeCount--;
			}
		}				
		return result;
	}
	

	//下面與列印相關
	
	/**
	 * 列印圖的概況,所有頂點,所有邊
	 */
	public void printGraph(){
		Iterator<Vertex<T>> iteratorVertex=getVertexIterator();
		Iterator<Edge> iteratorEdge;
		Vertex<T> vertex;
		Edge edge;
		T label;
		System.out.println("圖是否為有向圖:"+isDirect+",圖的頂點個數:"+getVertexCount()+",圖的總邊個數:"+getEdgeCount());
		while(iteratorVertex.hasNext()){
			vertex=iteratorVertex.next();
			label=vertex.getLabel();
			iteratorEdge=vertex.getEdgeIterator();
			System.out.println("頂點:"+label+",以這個頂點為出發點的邊的個數:"+getEdgeCount(label)+",該頂點的權值為:"+vertex.getCost());
			while(iteratorEdge.hasNext()){
				edge=iteratorEdge.next();
				System.out.print("邊:從 "+label+" 到 "+edge.getEndVertex().getLabel()+" ,權值:"+edge.getWeight()+"   ");
			}
			System.out.println();
		}
		System.out.println();
	}
	
	
}

測試

package datastructure.graph.adjacencymatrixgraph;

public class Main {

	public static void main(String[] args) {
		Graph<String> graph=new Graph<>(false);
		graph.addVertex("first", 0);
		graph.addVertex("second", 0);
		graph.addVertex("third", 1);
		graph.addEdge("first", "second", 1);
		graph.addEdge("first", "third", 2);
		
		graph.printGraph();
		
		graph.removeEdge("first", "second");
		graph.printGraph();

	}

}

```;