資料結構和演算法學習筆記六:圖的相關實現
一.簡介
本文將實現不同的圖的儲存結構,程式碼為C#程式碼.
下面是通用的圖型別列舉:
/************************************ * 建立人:movin * 建立時間:2021/7/3 15:50:19 * 版權所有:個人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 圖型別 /// </summary>public enum EGraphType { /// <summary> /// 無向圖 /// </summary> UndirectedGraph, /// <summary> /// 有向圖 /// </summary> DirectedGraph, } }
二.圖的實現
圖的實現我們最直觀的方式就是使用連結串列去儲存,但是這樣儲存太過於複雜,而且首先圖並不是順序結構,它是沒有頭尾的,其次圖中任意兩個頂點之間都可能存在邊,所以使用連結串列的方式儲存是不合適的,使用多重連結串列又會造成巨大的空間浪費,一般我們採用下面的方案實現圖的儲存:
1.鄰接矩陣
圖由頂點和邊(弧)組成,因此我們可以考慮分別儲存頂點和邊(弧).頂點可以使用陣列結構儲存,邊(弧)可以使用一個矩陣(二維陣列)儲存:
/************************************ * 建立人:movin * 建立時間:2021/7/3 15:57:42 * 版權所有:個人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary>/// 鄰接矩陣頂點 /// </summary> public struct AdjacencyMatrixVertex { /// <summary> /// 頂點中可以儲存資料,目前暫定儲存int型別,可以根據需要自行修改 /// </summary> public int Content { get; set; } public AdjacencyMatrixVertex(int content) { Content = content; } } }
/************************************ * 建立人:movin * 建立時間:2021/7/3 15:50:19 * 版權所有:個人 ***********************************/ using System; namespace GraphCore { /// <summary> /// 鄰接矩陣結構的圖 /// </summary> public class AdfaAdjacencyMatrixGraph { /// <summary> /// 圖的型別,有向圖或無向圖 /// </summary> public EGraphType type; /// <summary> /// 所有的頂點儲存為一個一維陣列 /// </summary> public Vertex[] vertices; /// <summary> /// 鄰接矩陣,儲存所有頂點的連線資訊 /// </summary> public int[,] adjacencyMatrix; /// <summary> /// 頂點數量 /// </summary> public int Count { get; private set; } /// <summary> /// 構造方法 /// </summary> /// <param name="vertexCount">頂點數量</param> public AdfaAdjacencyMatrixGraph(int vertexCount) { //引數校驗不合格丟擲引數異常 if(vertexCount <= 0) { throw new ArgumentException(); } vertices = new Vertex[vertexCount]; adjacencyMatrix = new int[vertexCount, vertexCount]; Count = vertexCount; } } }
2.鄰接表
對於稀疏圖來說,使用矩陣儲存邊(弧)的資訊會造成空間的浪費,因此我們可以使用連結串列儲存邊(弧)的資訊,這就是鄰接表:
/************************************ * 建立人:movin * 建立時間:2021/7/3 16:34:09 * 版權所有:個人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 鄰接表的邊表節點 /// </summary> public class AdjacencyListEdgeNode { /// <summary> /// 指向的頂點下標 /// </summary> public int vertexIndex; /// <summary> /// 權重 /// </summary> public int weight; /// <summary> /// 下一個節點的指標 /// </summary> public AdjacencyListEdgeNode next; public AdjacencyListEdgeNode(int vertexIndex, int weight = 0, AdjacencyListEdgeNode next = null) { this.vertexIndex = vertexIndex; this.weight = weight; this.next = next; } } }
/************************************ * 建立人:movin * 建立時間:2021/7/3 16:29:30 * 版權所有:個人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 鄰接表頂點 /// </summary> public struct AdjacencyListVertex { /// <summary> /// 頂點中可以儲存資料,目前暫定儲存int型別,可以根據需要自行修改 /// </summary> public int Content { get; set; } /// <summary> /// 第一個邊表節點的指標 /// </summary> public AdjacencyListEdgeNode firstEdge; public AdjacencyListVertex(int content,AdjacencyListEdgeNode firstEdge = null) { Content = content; this.firstEdge = firstEdge; } } }
/************************************ * 建立人:movin * 建立時間:2021/7/3 16:11:36 * 版權所有:個人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 鄰接表結構的圖 /// </summary> public class AdjacencyListGraph { /// <summary> /// 圖的型別,有向圖或無向圖 /// </summary> public EGraphType type; /// <summary> /// 所有的頂點儲存為一個一維陣列 /// </summary> public AdjacencyMatrixVertex[] vertices; /// <summary> /// 頂點數量 /// </summary> public int Count { get; private set; } public AdjacencyListGraph(int vertexCount,EGraphType type) { //引數校驗不合格丟擲引數異常 if (vertexCount <= 0) { throw new ArgumentException(); } vertices = new AdjacencyMatrixVertex[vertexCount]; Count = vertexCount; this.type = type; } } }
3.十字連結串列:對於有向圖來說,可以將鄰接表和逆鄰接表結合起來,就形成了十字連結串列:
/************************************ * 建立人:movin * 建立時間:2021/7/3 17:04:45 * 版權所有:個人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 十字連結串列的邊表節點 /// </summary> public class OrthogonalListEdgeNode { /// <summary> /// 指向的頂點下標 /// </summary> public int vertexIndex; /// <summary> /// 權重 /// </summary> public int weight; /// <summary> /// 下一個入節點的指標 /// </summary> public OrthogonalListEdgeNode headNext; /// <summary> /// 下一個出節點的指標 /// </summary> public OrthogonalListEdgeNode tailNext; public OrthogonalListEdgeNode(int vertexIndex, int weight = 0, OrthogonalListEdgeNode headNext = null, OrthogonalListEdgeNode tailNext = null) { this.vertexIndex = vertexIndex; this.weight = weight; this.headNext = headNext; this.tailNext = tailNext; } } }
/************************************ * 建立人:movin * 建立時間:2021/7/3 17:03:16 * 版權所有:個人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 十字連結串列頂點 /// </summary> public class OrthogonalListVertex { /// <summary> /// 頂點中可以儲存資料,目前暫定儲存int型別,可以根據需要自行修改 /// </summary> public int Content { get; set; } /// <summary> /// 第一個邊表節點的指標 /// </summary> public OrthogonalListEdgeNode firstIn; /// <summary> /// 第一個邊表節點的指標 /// </summary> public OrthogonalListEdgeNode firstOut; public OrthogonalListVertex(int content, OrthogonalListEdgeNode firstIn = null, OrthogonalListEdgeNode firstOut = null) { Content = content; this.firstIn = firstIn; this.firstOut = firstOut; } } }
/************************************ * 建立人:movin * 建立時間:2021/7/3 17:14:46 * 版權所有:個人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 十字連結串列結構的圖 /// 有向圖 /// </summary> public class OrthogonalListGraph { /// <summary> /// 所有的頂點儲存為一個一維陣列 /// </summary> public OrthogonalListVertex[] vertices; /// <summary> /// 頂點數量 /// </summary> public int Count { get; private set; } public OrthogonalListGraph(int vertexCount) { //引數校驗不合格丟擲引數異常 if (vertexCount <= 0) { throw new ArgumentException(); } vertices = new OrthogonalListVertex[vertexCount]; Count = vertexCount; } } }
4.鄰接多重表:對於無向圖的鄰接表,也可以採用十字連結串列類似的方式優化儲存結構,在邊表節點中同時記錄邊的關聯頂點下標及這條邊從屬於當前關聯頂點時的下一條邊節點的指標:
/************************************ * 建立人:movin * 建立時間:2021/7/3 19:36:26 * 版權所有:個人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 鄰接多重表邊表節點 /// </summary> public class AdjacencyMultiListEdgeNode { /// <summary> /// 關聯頂點i下標 /// </summary> public int iVertexIndex; /// <summary> /// 指向i的下一個節點的指標 /// </summary> public AdjacencyMultiListEdgeNode iLinkNext; /// <summary> /// 關聯頂點j下標 /// </summary> public int jVertexIndex; /// <summary> /// 指向j的下一個節點的指標 /// </summary> public AdjacencyMultiListEdgeNode jLinkNext; /// <summary> /// 權重 /// </summary> public int weight; public AdjacencyMultiListEdgeNode(int iVertexIndex,int jVertexIndex,AdjacencyMultiListEdgeNode iLinkNext = null,AdjacencyMultiListEdgeNode jLinkNext = null,int weight = 0) { this.iVertexIndex = iVertexIndex; this.weight = weight; this.iLinkNext = iLinkNext; this.jVertexIndex = jVertexIndex; this.jLinkNext = jLinkNext; } /// <summary> /// 獲取下一個邊表節點 /// </summary> /// <param name="index">獲取下一個邊表節點的頂點下標</param> /// <returns></returns> public AdjacencyMultiListEdgeNode GetNextEdgeNode(int index) { if(index == iVertexIndex) { return iLinkNext; } if(index == jVertexIndex) { return jLinkNext; } return null; } } }
/************************************ * 建立人:movin * 建立時間:2021/7/3 19:33:52 * 版權所有:個人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 鄰接多重表頂點 /// </summary> public class AdjacencyMultiListVertex { /// <summary> /// 頂點中可以儲存資料,目前暫定儲存int型別,可以根據需要自行修改 /// </summary> public int Content { get; set; } /// <summary> /// 第一個邊表節點的指標 /// </summary> public AdjacencyMultiListEdgeNode firstEdge; public AdjacencyMultiListVertex(int content, AdjacencyMultiListEdgeNode firstEdge = null) { Content = content; this.firstEdge = firstEdge; } } }
/************************************ * 建立人:movin * 建立時間:2021/7/3 19:31:48 * 版權所有:個人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 鄰接多重表結構的圖 /// 無向圖 /// </summary> public class AdjacencyMultiListGraph { /// <summary> /// 所有的頂點儲存為一個一維陣列 /// </summary> public AdjacencyMultiListVertex[] vertices; /// <summary> /// 頂點數量 /// </summary> public int Count { get; private set; } public AdjacencyMultiListGraph(int vertexCount) { //引數校驗不合格丟擲引數異常 if (vertexCount <= 0) { throw new ArgumentException(); } vertices = new AdjacencyMultiListVertex[vertexCount]; Count = vertexCount; } } }
5.邊集陣列:如果僅是儲存圖結構,而不考慮遍歷或者計算頂點的度時,就可以考慮使用兩個陣列分別儲存邊和頂點的方式將圖儲存起來,這就是邊集陣列:
/************************************ * 建立人:movin * 建立時間:2021/7/3 19:52:23 * 版權所有:個人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 邊集陣列的邊表節點 /// </summary> public class EdgesetArrayEdgeNode { /// <summary> /// 尾節點下標 /// </summary> public int tailIndex; /// <summary> /// 頭節點下標 /// </summary> public int headIndex; /// <summary> /// 權重 /// </summary> public int weight; public EdgesetArrayEdgeNode(int tailIndex, int headIndex, int weight = 0) { this.tailIndex = tailIndex; this.headIndex = headIndex; this.weight = weight; } } }
/************************************ * 建立人:movin * 建立時間:2021/7/3 19:52:55 * 版權所有:個人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 邊集陣列頂點 /// </summary> public class EdgesetArrayVertex { /// <summary> /// 頂點中可以儲存資料,目前暫定儲存int型別,可以根據需要自行修改 /// </summary> public int Content { get; set; } public EdgesetArrayVertex(int content) { Content = content; } } }
/************************************ * 建立人:movin * 建立時間:2021/7/3 19:50:50 * 版權所有:個人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { /// <summary> /// 邊集陣列結構的圖 /// 無向圖 /// </summary> public class EdgesetArrayGraph { /// <summary> /// 所有的頂點儲存為一個一維陣列 /// </summary> public EdgesetArrayVertex[] vertices; /// <summary> /// 所有邊集節點儲存為一個數組 /// </summary> public EdgesetArrayEdgeNode[] edgeNodes; /// <summary> /// 頂點數量 /// </summary> public int Count { get; private set; } public EdgesetArrayGraph(int vertexCount) { //引數校驗不合格丟擲引數異常 if (vertexCount <= 0) { throw new ArgumentException(); } vertices = new EdgesetArrayVertex[vertexCount]; edgeNodes = new EdgesetArrayEdgeNode[vertexCount]; } } }
三.總結
對於圖的儲存(假設圖的頂點數為n,邊/弧數為e),一般都需要將圖分為頂點和邊(弧)兩部分分別儲存,頂點和邊(弧)都需要首先抽象為類或結構體.不論採用哪種儲存方式,頂點都是使用一個數組儲存,區別在於頂點中是否需要儲存邊表的第一個節點的下標.邊\弧的儲存可以採用一維陣列的形式儲存(邊集陣列),這樣儲存查詢或者計算頂點的度的時候都需要對陣列進行遍歷,時間複雜度為O(e).可以採用二維陣列的形式儲存邊/弧(鄰接矩陣),這樣在查詢邊/弧或者計算節點的度的時候時間複雜度為O(n),顯然對於稀疏圖鄰接矩陣的方式浪費空間也浪費時間.可以使用鏈式結構儲存(鄰接表\十字連結串列\鄰接多重表):無論哪種鏈式結構儲存的邊/弧,都是使用連結串列儲存某個頂點的所有關聯邊/弧(這個頂點的邊表),一個邊表結點對應一條邊/弧, 在頂點資料結構中儲存這個連結串列的頭結點指標,區別只是連線的形式不同.鄰接表是最簡單的鏈式結構儲存邊/弧的形式,但是對於有向圖來說,一個鄰接表只能儲存入度的弧或者出度的弧,無法同時儲存,所以將鄰接表進行優化後就形成了十字連結串列,十字連結串列在邊表結點中同時儲存入度和出度的下一個結點的指標,十字連結串列是適用於有向圖的儲存的優化,但是同樣的結構也可以用來儲存無向圖,只需要對頂點的資料結構稍加優化就形成了鄰接多重表,十字連結串列的頂點中需要同時儲存入度和出度的第一個弧結點的指標,而鄰接多重表只需要在頂點資料結構中儲存第一條邊的結點指標即可.