圖的儲存結構與實現總結
目錄
圖的儲存結構
圖的儲存結構主要分兩種,一種是鄰接矩陣,一種是鄰接表。
鄰接矩陣
圖的鄰接矩陣儲存方式是用兩個陣列來表示圖。一個一維陣列儲存圖中頂點資訊,一個二維陣列(鄰接矩陣)儲存圖中的邊或弧的資訊。
設圖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的所有鄰接點構成一個線性表,由於鄰接點的個數不定,所以,用單鏈表儲存
例如,下圖就是一個無向圖的鄰接表的結構。
從圖中可以看出,頂點表的各個結點由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();
}
}
```;