圖的鄰接矩陣表示
圖基概念(Graph)
-
包含
- 一組頂點:通常用V (Vertex) 表示頂點集合
- 一組邊:通常用E (Edge) 表示邊的集合
- 邊是頂點對:(v, w) ∈E ,其中v, w ∈ V有向邊<v, w> 表示從v指向w的邊(單行線)不考慮重邊和自回路
-
無向圖:邊是無向邊(v, w)
-
有向圖:邊是有向邊<v, w>
-
連通:如果從V到W存在一條(無向)路徑,則稱V和W是連通的
-
連通圖(Connected Graph):如果對於圖的任一兩個頂點v、w∈V,v和w都是連通的,則稱該圖為連通圖。圖中任意兩頂點均連通。
-
連通分量(Connected Component):無向圖中的極大連通子圖。
- 極大頂點數:再加1個頂點就不連通了
- 極大邊數:包含子圖中所有頂點相連的所有邊
-
強連通:有向圖中頂點V和W之間存在雙向路徑,則稱V和W是強連通的。
-
強連通圖:有向圖中任意兩頂點均強連通。
-
強連通分量:有向圖的極大強連通子圖。
-
路徑:V到W的路徑是一系列頂點{V, v1, v2, …,vn, W}的集合,其中任一對相鄰的頂點間都有圖中的邊。路徑的長度是路徑中的邊數(如果帶權,則是所有邊的權重和)。
-
如果V到W之間的所有頂點都不同,則稱簡單路徑
-
回路:起點等於終點的路徑
鄰接矩陣
-
圖的鄰接矩陣存儲方式就是用一個二維數組來表示。鄰接矩陣G[N][N]——N個頂點從0到N-1編號頂點i、j有邊,則G[i][j] = 1 或邊的權重
-
鄰接矩陣的優點
- 直觀、簡單、好理解
- 方便檢查任意一對頂點間是否存在邊
- 方便找任一頂點的所有“鄰接點”(有邊直接相連的頂點)
- 方便計算任一頂點的“度”(從該點發出的邊數為“出度”,指向該點的邊數為“入度”)
- 無向圖:對應行(或列)非0元素的個數
- 有向圖:對應行非0元素的個數是“出度”;對應列非0元素的個數是“入度”
-
鄰接矩陣的缺點
- 浪費空間—— 存稀疏圖(點很多而邊很少)有大量無效元素
- 對稠密圖(特別是完全圖)還是很合算的
- 浪費時間—— 統計稀疏圖中一共有多少條邊
BFS廣度優先搜索(Breadth First Search, BFS)
-
運用隊列,將頂點V的每個鄰接點進隊。(類似於樹的層先遍歷)
-
若有N個頂點、E條邊,時間復雜度是
- 用鄰接表存儲圖,有O(N+E)
- 用鄰接矩陣存儲圖,有O(N^2)
DFS深度優先搜索索(Depth First Search, DFS)
-
用遞歸(類似於樹的先序遍歷)。
-
ListComponents 圖不連通時,列出各連通分量。
-
若有N個頂點、E條邊,時間復雜度是
- 用鄰接表存儲圖,有O(N+E)
- 用鄰接矩陣存儲圖,有O(N^2) -
深度優先遍歷算法的非遞歸實現需要了解深度優先遍歷的執行過程,設計一個棧來模擬遞歸實現中系統設置的工作棧,算法的偽代碼描述為:
- 假設圖采用鄰接矩陣作為存儲結構,具體算法如下:
測試代碼:
/*!
* \file 圖的鄰接矩陣表示.cpp
*
* \author ranjiewen
* \date 2017/04/10 23:20
*
*
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
using namespace std;
/* 圖的鄰接矩陣表示法 */
#define MaxVertexNum 100 /*最大頂點數設為100*/
#define INFINITY 65535 /*設為雙字節無符號整數的最大值為65535*/
typedef int Vertex; /*用頂點下標表示頂點,為整型*/
typedef int WeightType; /*邊的權值設為整型*/
typedef char DataType; /*頂點存儲的數據類型設為字符型*/
/*邊的定義*/
typedef struct ENode* PtrToENode;
struct ENode
{
Vertex V1, V2; //有向邊<v1,v2>
WeightType Weight;//權重
};
typedef PtrToENode Edge;
/*圖結點的定義*/
typedef struct GNode *PtrToGNode;
struct GNode
{
int Nv; //頂點樹
int Ne; //邊數
WeightType G[MaxVertexNum][MaxVertexNum]; //鄰接矩陣
DataType Data[MaxVertexNum];// 存頂點的數據
//注意:很多情況下,頂點無數據,此時Data[]可以不用出現
};
typedef PtrToGNode MGraph; /*用鄰接矩陣存儲的圖類型*/
bool Visited[MaxVertexNum] = { false };
MGraph CreateGraph(int VertexNum)
{
/*初始化一個有VertexNum個頂點但沒有邊的圖*/
Vertex V, W; /*頂點的下標*/
MGraph Graph;
Graph = (MGraph)malloc(sizeof(struct GNode)); /*建立圖*/
Graph->Nv = VertexNum;
Graph->Ne = 0;
//初始化鄰接矩陣
//注意:這里默認頂點編號從0開始到(Graph->Nv - 1)
for (V = 0; V < Graph->Nv;V++)
{
for (W = 0; W < Graph->Nv;W++)
{
Graph->G[V][W] = INFINITY;
}
}
return Graph;
}
void InsertEdge(MGraph Graph,Edge E)
{
//插入邊<v1,v2>
Graph->G[E->V1][E->V2] = E->Weight;
//若是無向圖,還要插入邊<v2,v1>
Graph->G[E->V2][E->V1] = E->Weight;
}
MGraph BuildGraph()
{
MGraph Graph;
Edge E;
Vertex V;
int Nv, i;
scanf("%d", &Nv); /*讀入頂點個數*/
Graph = CreateGraph(Nv); /* 初始化有Nv個頂點但沒有邊的圖 */
scanf("%d", &(Graph->Ne)); /*讀入邊數*/
if (Graph->Ne!=0) //如果有邊
{
E = (Edge)malloc(sizeof(struct ENode)); //建立邊結點
//讀入邊,格式為:起點,中點,權重;插入鄰接矩陣
for (i = 0; i < Graph->Ne;i++)
{
scanf("%d %d %d", &E->V1, &E->V2, &E->Weight);
//注意:如果權重不是整型,weight的讀入格式要改變
InsertEdge(Graph, E);
}
}
//如果頂點有數據的話,讀入數據
for (V = 0; V < Graph->Nv;V++)
{
//scanf("%c", &(Graph->Data[V]));
}
return Graph;
}
/* 鄰接矩陣存儲的圖 - BFS */
/* IsEdge(Graph, V, W)檢查<V, W>是否圖Graph中的一條邊,即W是否V的鄰接點。 */
/* 此函數根據圖的不同類型要做不同的實現,關鍵取決於對不存在的邊的表示方法。*/
/* 例如對有權圖, 如果不存在的邊被初始化為INFINITY, 則函數實現如下: */
bool IsEdge(MGraph Graph, Vertex V, Vertex W)
{
return Graph->G[V][W] < INFINITY ? true : false;
}
void InitVisited()
{
for (int i = 0; i < MaxVertexNum;i++)
{
Visited[i] = false;
}
}
void Visit(Vertex v)
{
printf("%d ", v);
}
//連通下的DFS和BFS
void BFS(MGraph Graph, Vertex S, void(*Visit)(Vertex))
{
/* 以S為出發點對鄰接矩陣存儲的圖Graph進行BFS搜索 */
queue<Vertex> Q;
Vertex V, W;
Visit(S);
Visited[S] = true;
Q.push(S);
while (!Q.empty()) {
V = Q.front();
Q.pop();
for (W = 0; W < Graph->Nv; W++) /* 對圖中的每個頂點W */
/* 若W是V的鄰接點並且未訪問過 */
if (!Visited[W] && IsEdge(Graph, V, W))
{
/* 訪問頂點W */
Visit(W);
Visited[W] = true;
Q.push(W);
}
}
}
void DFS(MGraph Graph, Vertex S, void(*Visit)(Vertex))
{
/* 以V為出發點對鄰接表存儲的圖Graph進行DFS搜索 */
Visited[S] = true;
Visit(S);
for (Vertex w = 0; w < Graph->Nv; w++) {
if (IsEdge(Graph, S, w) && Visited[w] == false) {
DFS(Graph, w, Visit);
}
}
}
//非連通下的遍歷
Vertex listDFS(MGraph Graph, void(*Visit)(Vertex))
{
Vertex i;
for (i = 0; i < Graph->Nv; i++) {
if (Visited[i] == false)
break;
}
if (i == Graph->Nv)
return 0;
DFS(Graph, i, Visit);
printf("\n");
return listDFS(Graph, Visit);
}
void DFSListComponents(MGraph Graph, void(*Visit)(Vertex))
{
for (Vertex i = 0; i < Graph->Nv; i++) {
if (Visited[i] == false) {
DFS(Graph, i, Visit);
printf("\n");
}
}
}
void BFSListComponents(MGraph Graph, void(*Visit)(Vertex))
{
for (Vertex i = 0; i < Graph->Nv; i++) {
if (Visited[i] == false) {
BFS(Graph, i, Visit);
printf("\n");
}
}
}
int main()
{
MGraph graph;
graph = BuildGraph();
InitVisited();
listDFS(graph, &Visit);
InitVisited();
DFSListComponents(graph, &Visit);
InitVisited();
//BFS(graph,0,&Visit);
BFSListComponents(graph, &Visit);
return 0;
}