數據結構–圖(Graph)詳解(二)
文章目錄
- 數據結構--圖(Graph)詳解(二)
- 一、圖的存儲結構
- 1.圖的順序存儲法
- 2.圖的鄰接表存儲法
- 3.圖的十字鏈表存儲法
- 4.圖的鄰接多重表存儲法
- 二、圖的遍歷
- 1.深度優先搜索(DFS)
- 2.廣度優先搜索(BFS)
一、圖的存儲結構
1.圖的順序存儲法
使用圖結構表示的數據元素之間雖然具有“多對多”的關系,但是同樣可以采用順序存儲,也就是使用數組有效地存儲圖。
- 使用數組存儲圖時,需要使用兩個數組,一個數組存放圖中頂點本身的數據(一維數組),另外一個數組用于存儲各頂點之間的關系(二維數組)。
- 存儲圖中各頂點本身數據,使用一維數組就足夠了;存儲頂點之間的關系時,要記錄每個頂點和其它所有頂點之間的關系,所以需要使用二維數組。
- 不同類型的圖,存儲的方式略有不同,根據圖有無權,可以將圖劃分為兩大類:圖和網 。
- 圖,包括無向圖和有向圖;網,是指帶權的圖,包括無向網和有向網。
- 存儲方式的不同,指的是:在使用二維數組存儲圖中頂點之間的關系時,如果頂點之間存在邊或弧,在相應位置用 1 表示,反之用 0 表示;
- 如果使用二維數組存儲網中頂點之間的關系,頂點之間如果有邊或者弧的存在,在數組的相應位置存儲其權值;反之用 0 表示。
#define MAX_VERtEX_NUM 20
#define VRType int
#define InfoType char
#define VertexType int typedef enum{DG
,DN
,UDG
,UDN
}GraphKind
; typedef struct {VRType adj
; InfoType
* info
;
}ArcCell
,AdjMatrix
[MAX_VERtEX_NUM
][MAX_VERtEX_NUM
];typedef struct {VertexType vexs
[MAX_VERtEX_NUM
]; AdjMatrix arcs
; int vexnum
,arcnum
; GraphKind kind
;
}MGraph
;
例如,存儲圖 1 中的無向圖(B)時,除了存儲圖中各頂點本身具有的數據外,還需要使用二維數組存儲任意兩個頂點之間的關系。
由于 (B) 為無向圖,各頂點沒有權值,所以如果兩頂點之間有關聯,相應位置記為 1 ;反之記為 0 。構建的二維數組如圖 2 所示。
在此二維數組中,每一行代表一個頂點,依次從 V1 到 V5 ,每一列也是如此。比如 arcs[0][1] = 1 ,表示 V1 和 V2 之間有邊存在;而 arcs[0][2] = 0,說明 V1 和 V3 之間沒有邊。
對于無向圖來說,二維數組構建的二階矩陣,實際上是對稱矩陣,在存儲時就可以采用壓縮存儲的方式存儲下三角或者上三角。
通過二階矩陣,可以直觀地判斷出各個頂點的度,為該行(或該列)非 0 值的和。例如,第一行有兩個 1,說明 V1 有兩個邊,所以度為 2。
存儲圖 1 中的有向圖(A)時,對應的二維數組如圖 3 所示:
例如,arcs[0][1] = 1 ,證明從 V1 到 V2 有弧存在。
且通過二階矩陣,可以很輕松得知各頂點的出度和入度,出度為該行非 0 值的和,入度為該列非 0 值的和。
例如,V1 的出度為第一行兩個 1 的和,為 2 ; V1 的入度為第一列中 1 的和,為 1 。所以 V1 的出度為 2 ,入度為 1 ,度為兩者的和 3 。
#include <stdio.h>
#define MAX_VERtEX_NUM 20
#define VRType int
#define InfoType char
#define VertexType int typedef enum{DG
,DN
,UDG
,UDN
}GraphKind
; typedef struct {VRType adj
; InfoType
* info
;
}ArcCell
,AdjMatrix
[MAX_VERtEX_NUM
][MAX_VERtEX_NUM
];typedef struct {VertexType vexs
[MAX_VERtEX_NUM
]; AdjMatrix arcs
; int vexnum
,arcnum
; GraphKind kind
;
}MGraph
;
int LocateVex(MGraph
* G
,VertexType v
){int i
=0;for (; i
<G
->vexnum
; i
++) {if (G
->vexs
[i
]==v
) {break;}}if (i
>G
->vexnum
) {printf("no such vertex.\n");return -1;}return i
;
}
void CreateDG(MGraph
*G
){scanf("%d,%d",&(G
->vexnum
),&(G
->arcnum
));for (int i
=0; i
<G
->vexnum
; i
++) {scanf("%d",&(G
->vexs
[i
]));}for (int i
=0; i
<G
->vexnum
; i
++) {for (int j
=0; j
<G
->vexnum
; j
++) {G
->arcs
[i
][j
].adj
=0;G
->arcs
[i
][j
].info
=NULL;}}for (int i
=0; i
<G
->arcnum
; i
++) {int v1
,v2
;scanf("%d,%d",&v1
,&v2
);int n
=LocateVex(G
, v1
);int m
=LocateVex(G
, v2
);if (m
==-1 ||n
==-1) {printf("no this vertex\n");return;}G
->arcs
[n
][m
].adj
=1;}
}
void CreateDN(MGraph
*G
){scanf("%d,%d",&(G
->vexnum
),&(G
->arcnum
));for (int i
=0; i
<G
->vexnum
; i
++) {scanf("%d",&(G
->vexs
[i
]));}for (int i
=0; i
<G
->vexnum
; i
++) {for (int j
=0; j
<G
->vexnum
; j
++) {G
->arcs
[i
][j
].adj
=0;G
->arcs
[i
][j
].info
=NULL;}}for (int i
=0; i
<G
->arcnum
; i
++) {int v1
,v2
;scanf("%d,%d",&v1
,&v2
);int n
=LocateVex(G
, v1
);int m
=LocateVex(G
, v2
);if (m
==-1 ||n
==-1) {printf("no this vertex\n");return;}G
->arcs
[n
][m
].adj
=1;G
->arcs
[m
][n
].adj
=1;}
}
void CreateUDG(MGraph
*G
){scanf("%d,%d",&(G
->vexnum
),&(G
->arcnum
));for (int i
=0; i
<G
->vexnum
; i
++) {scanf("%d",&(G
->vexs
[i
]));}for (int i
=0; i
<G
->vexnum
; i
++) {for (int j
=0; j
<G
->vexnum
; j
++) {G
->arcs
[i
][j
].adj
=0;G
->arcs
[i
][j
].info
=NULL;}}for (int i
=0; i
<G
->arcnum
; i
++) {int v1
,v2
,w
;scanf("%d,%d,%d",&v1
,&v2
,&w
);int n
=LocateVex(G
, v1
);int m
=LocateVex(G
, v2
);if (m
==-1 ||n
==-1) {printf("no this vertex\n");return;}G
->arcs
[n
][m
].adj
=w
;}
}
void CreateUDN(MGraph
* G
){scanf("%d,%d",&(G
->vexnum
),&(G
->arcnum
));for (int i
=0; i
<G
->vexnum
; i
++) {scanf("%d",&(G
->vexs
[i
]));}for (int i
=0; i
<G
->vexnum
; i
++) {for (int j
=0; j
<G
->vexnum
; j
++) {G
->arcs
[i
][j
].adj
=0;G
->arcs
[i
][j
].info
=NULL;}}for (int i
=0; i
<G
->arcnum
; i
++) {int v1
,v2
,w
;scanf("%d,%d,%d",&v1
,&v2
,&w
);int m
=LocateVex(G
, v1
);int n
=LocateVex(G
, v2
);if (m
==-1 ||n
==-1) {printf("no this vertex\n");return;}G
->arcs
[n
][m
].adj
=w
;G
->arcs
[m
][n
].adj
=w
;}
}void CreateGraph(MGraph
*G
){scanf("%d",&(G
->kind
));switch (G
->kind
) {case DG
:return CreateDG(G
);break;case DN
:return CreateDN(G
);break;case UDG
:return CreateUDG(G
);break;case UDN
:return CreateUDN(G
);break;default:break;}
}
void PrintGrapth(MGraph G
)
{for (int i
= 0; i
< G
.vexnum
; i
++){for (int j
= 0; j
< G
.vexnum
; j
++){printf("%d ", G
.arcs
[i
][j
].adj
);}printf("\n");}
}int main() {MGraph G
;CreateGraph(&G
);PrintGrapth(G
);return 0;
}
注意:在此程序中,構建無向網和有向網時,對于之間沒有邊或弧的頂點,相應的二階矩陣中存放的是 0。
目的只是為了方便查看運行結果,而實際上如果頂點之間沒有關聯,它們之間的距離應該是無窮大(∞)。
例如,使用上述程序存儲圖 4(a)的有向網時,存儲的兩個數組如圖 4(b)所示:
#include <iostream>
#include <vector>
#include <queue>
#include <string>
#include <unordered_map>
using namespace std
;namespace matrix
{template<class V,class W,bool D
= false>class Graph {public:Graph(const V
* a
,int n
){_vertexs
.reserve(n
);_edge
.resize(n
);for(int i
= 0;i
< n
;i
++){_edge
[i
].resize(n
,W());_vertexs
.push_back(a
[i
]);_vertexsIndexMap
[a
[i
]] = i
;}} void AddEdge(const V
& src
,const V
& dest
,const W
& w
){int srcIndex
;int descIndex
;try {srcIndex
= GetVertexsIndex(src
);descIndex
= GetVertexsIndex(dest
);}catch(string er
){cout
<<"非法頂點"<<endl
;exit(1);}_edge
[srcIndex
][descIndex
] = w
;if(false == D
){_edge
[descIndex
][srcIndex
] = w
;}}void DFS(const V
& src
){int srcIndex
;try {srcIndex
= GetVertexsIndex(src
); }catch(string ex
){cout
<<ex
<<endl
;exit(1);}vector
<bool> visted(_vertexs
.size(),false);_DFS(srcIndex
,visted
);cout
<<endl
;}void BFS(const V
& src
){int srcIndex
;try {srcIndex
= GetVertexsIndex(src
);}catch(string ex
){cout
<<ex
<<endl
;exit(1);}vector
<bool> visted(_vertexs
.size(),false);queue
<int> q
;q
.push(srcIndex
);visted
[srcIndex
] = true; while(!q
.empty()){int front
= q
.front();q
.pop();cout
<<front
<<" : "<<_vertexs
[front
]<<" ";for(int i
= 0;i
< _edge
[front
].size();i
++){if(_edge
[front
][i
] != W() && visted
[i
] == false){q
.push(i
);visted
[i
] = true;}}}cout
<<endl
;}private:void _DFS(int srcIndex
,vector
<bool> visted
){visted
[srcIndex
] = true;cout
<<srcIndex
<<" : "<<_vertexs
[srcIndex
]<<" -> ";for(int i
= 0;i
< _edge
[srcIndex
].size();i
++){if(_edge
[srcIndex
][i
] != W() && visted
[i
] == false){_DFS(i
,visted
);}}}int GetVertexsIndex(const V
& v
){if(_vertexsIndexMap
.find(v
) != _vertexsIndexMap
.end()){return _vertexsIndexMap
[v
];} else {throw string("非法頂點");}}private:vector
<V
> _vertexs
;vector
<vector
<W
>> _edge
;unordered_map
<V
,int> _vertexsIndexMap
; };
}
2.圖的鄰接表存儲法
- 鄰接表既適用于存儲無向圖,也適用于存儲有向圖。
- 在具體講解鄰接表存儲圖的實現方法之前,先普及一個"鄰接點"的概念。在圖中,如果兩個點相互連通,即通過其中一個頂點,可直接找到另一個頂點,則稱它們互為鄰接點。
- 鄰接指的是圖中頂點之間有邊或者弧的存在。
- 鄰接表存儲圖的實現方式是,給圖中的各個頂點獨自建立一個鏈表,用節點存儲該頂點,用鏈表中其他節點存儲各自的臨界點。
- 與此同時,為了便于管理這些鏈表,通常會將所有鏈表的頭節點存儲到數組中(也可以用鏈表存儲)。
- 也正因為各個鏈表的頭節點存儲的是各個頂點,因此各鏈表在存儲臨界點數據時,僅需存儲該鄰接頂點位于數組中的位置下標即可。
例如,存儲圖 1a) 所示的有向圖,其對應的鄰接表如圖 1b) 所示:
拿頂點 V1 來說,與其相關的鄰接點分別為 V2 和 V3,因此存儲 V1 的鏈表中存儲的是 V2 和 V3 在數組中的位置下標 1 和 2。
從圖 1 中可以看出,存儲各頂點的節點結構分為兩部分,數據域和指針域。數據域用于存儲頂點數據信息,指針域用于鏈接下一個節點,如圖 2 所示:
在實際應用中,除了圖 2 這種節點結構外,對于用鏈接表存儲網(邊或弧存在權)結構,還需要節點存儲權的值,因此需使用圖 3 中的節點結構:
#define MAX_VERTEX_NUM 20
#define VertexType int
#define InfoType inttypedef struct ArcNode
{int adjvex
;struct ArcNode
* nextarc
;InfoType
* info
;
}ArcNode
;typedef struct VNode
{VertexType data
;ArcNode
* firstarc
;
}VNode
,AdjList
[MAX_VERTEX_NUM
];typedef struct {AdjList vertices
;int vexnum
,arcnum
;int kind
;
}ALGraph
;
- 鄰接表計算頂點的出度和入度
- 使用鄰接表計算無向圖中頂點的入度和出度會非常簡單,只需從數組中找到該頂點然后統計此鏈表中節點的數量即可。
- 而使用鄰接表存儲有向圖時,通常各個頂點的鏈表中存儲的都是以該頂點為弧尾的鄰接點,因此通過統計各頂點鏈表中的節點數量,只能計算出該頂點的出度,而無法計算該頂點的入度。
- 對于利用鄰接表求某頂點的入度,有兩種方式:
- 遍歷整個鄰接表中的節點,統計數據域與該頂點所在數組位置下標相同的節點數量,即為該頂點的入度;
- 建立一個逆鄰接表,該表中的各頂點鏈表專門用于存儲以此頂點為弧頭的所有頂點在數組中的位置下標。
比如說,建立一張圖 1a) 對應的逆鄰接表,如圖 4 所示:
對于具有 n 個頂點和 e 條邊的無向圖,鄰接表中需要存儲 n 個頭結點和 2e 個表結點。在圖中邊或者弧稀疏的時候,使用鄰接表要比前一節介紹的鄰接矩陣更加節省空間。
#include <iostream>
#include <vector>
#include <queue>
#include <string>
#include <unordered_map>
using namespace std
;namespace linktable
{template<class W>struct LinkEdge
{LinkEdge(const int& src
,const int& dest
,const W
& w
):_src(src
),_dest(dest
),_w(w
),_next(nullptr){}int _src
;int _dest
;W _w
;LinkEdge
<W
>* _next
;};template<class V,class W,bool D
= false>class Graph {typedef LinkEdge
<W
> Edge
;public:Graph(const V
* a
,int n
){_vertexs
.reserve(n
);_linktable
.resize(n
,nullptr);for(int i
= 0;i
< n
;i
++){_vertexs
.push_back(a
[i
]);_vertexsIndexMap
[a
[i
]] = i
;}}void AddEdge(const V
& src
,const V
& dest
,const W
& w
){int srcIndex
;int descIndex
;try {srcIndex
= GetVertexsIndex(src
); descIndex
= GetVertexsIndex(dest
);}catch(string ex
){cout
<<"非法邊界"<<endl
;exit(1);}_AddEdge(srcIndex
,descIndex
,w
);if(false == D
){_AddEdge(descIndex
,srcIndex
,w
);}}private:void _AddEdge(const int srcIndex
,const int destIndex
,const W
& w
){ Edge
* edge
= new Edge(srcIndex
,destIndex
,w
);edge
->_next
= _linktable
[srcIndex
];_linktable
[srcIndex
] = edge
;}int GetVertexsIndex(const V
& v
){if(_vertexsIndexMap
.find(v
) != _vertexsIndexMap
.end()){return _vertexsIndexMap
[v
];}else {throw string("非法頂點");}}private:vector
<V
> _vertexs
;vector
<Edge
*> _linktable
;unordered_map
<V
,int> _vertexsIndexMap
;};
}
3.圖的十字鏈表存儲法
- 與鄰接表不同,十字鏈表法僅適用于存儲有向圖和有向網。不僅如此,十字鏈表法還改善了鄰接表計算圖中頂點入度的問題。
十字鏈表存儲有向圖(網)的方式與鄰接表有一些相同,都以圖(網)中各頂點為首元節點建立多條鏈表,同時為了便于管理,還將所有鏈表的首元節點存儲到同一數組(或鏈表)中。
其中,建立個各個鏈表中用于存儲頂點的首元節點結構如圖 1 所示:
從圖 1 可以看出,首元節點中有一個數據域和兩個指針域(分別用 firstin 和 firstout 表示):
- firstin 指針用于連接以當前頂點為弧頭的其他頂點構成的鏈表;
- firstout 指針用于連接以當前頂點為弧尾的其他頂點構成的鏈表;
- data 用于存儲該頂點中的數據;
由此可以看出,十字鏈表實質上就是為每個頂點建立兩個鏈表,分別存儲以該頂點為弧頭的所有頂點和以該頂點為弧尾的所有頂點。
注意,存儲圖的十字鏈表中,各鏈表中首元節點與其他節點的結構并不相同,圖 1 所示僅是十字鏈表中首元節點的結構,鏈表中其他普通節點的結構如圖 2 所示:
從圖 2 中可以看出,十字鏈表中普通節點的存儲分為 5 部分內容,它們各自的作用是:
- tailvex 用于存儲以首元節點為弧尾的頂點位于數組中的位置下標;
- headvex 用于存儲以首元節點為弧頭的頂點位于數組中的位置下標;
- hlink 指針:用于鏈接下一個存儲以首元節點為弧頭的頂點的節點;
- tlink 指針:用于鏈接下一個存儲以首元節點為弧尾的頂點的節點;
- info 指針:用于存儲與該頂點相關的信息,例如量頂點之間的權值;
比如說,用十字鏈表存儲圖 3a) 中的有向圖,存儲狀態如圖 3b) 所示:
拿圖 3 中的頂點 V1 來說,通過構建好的十字鏈表得知,以該頂點為弧頭的頂點只有存儲在數組中第 3 位置的 V4(因此該頂點的入度為 1)
而以該頂點為弧尾的頂點有兩個,分別為存儲數組第 1 位置的 V2 和第 2 位置的 V3(因此該頂點的出度為 2)。
對于圖 3 各個鏈表中節點來說,由于表示的都是該頂點的出度或者入度,因此沒有先后次序之分。
- 圖 3 中十字鏈表的構建過程轉化為 C 語言代碼為:
#define MAX_VERTEX_NUM 20
#define InfoType int
#define VertexType inttypedef struct ArcBox
{int tailvex
,headvex
;struct ArcBox
*hlik
,*tlink
;InfoType
*info
;
}ArcBox
;typedef struct VexNode
{VertexType data
;ArcBox
*firstin
,*firstout
;
}VexNode
;typedef struct {VexNode xlist
[MAX_VERTEX_NUM
];int vexnum
,arcnum
;
}OLGraph
;int LocateVex(OLGraph
* G
,VertexType v
){int i
=0;for (; i
<G
->vexnum
; i
++) {if (G
->xlist
[i
].data
==v
) {break;}}if (i
>G
->vexnum
) {printf("no such vertex.\n");return -1;}return i
;
}
void CreateDG(OLGraph
*G
){scanf("%d,%d",&(G
->vexnum
),&(G
->arcnum
));for (int i
=0; i
<G
->vexnum
; i
++) {scanf("%d",&(G
->xlist
[i
].data
));G
->xlist
[i
].firstin
=NULL;G
->xlist
[i
].firstout
=NULL;}for (int k
=0;k
<G
->arcnum
; k
++) {int v1
,v2
;scanf("%d,%d",&v1
,&v2
);int i
=LocateVex(G
, v1
);int j
=LocateVex(G
, v2
);ArcBox
* p
=(ArcBox
*)malloc(sizeof(ArcBox
));p
->tailvex
=i
;p
->headvex
=j
;p
->hlik
=G
->xlist
[j
].firstin
;p
->tlink
=G
->xlist
[i
].firstout
;G
->xlist
[j
].firstin
=G
->xlist
[i
].firstout
=p
;}
}
4.圖的鄰接多重表存儲法
- 為了提高在無向圖中操作頂點的效率,學習一種適用于存儲無向圖的方法——鄰接多重表。
注意,鄰接多重表僅適用于存儲無向圖或無向網。
- 鄰接多重表存儲無向圖的方式,可看作是鄰接表和十字鏈表的結合。
- 同鄰接表和十字鏈表存儲圖的方法相同,都是獨自為圖中各頂點建立一張鏈表,存儲各頂點的節點作為各鏈表的首元節點,同時為了便于管理將各個首元節點存儲到一個數組中。
各首元節點結構如圖 1 所示:
圖 1 中各區域及其功能為:
- data:存儲此頂點的數據;
- firstedge:指針域,用于指向同該頂點有直接關聯的存儲其他頂點的節點。
從圖 1 可以看到,鄰接多重表采用與鄰接表相同的首元節點結構。但各鏈表中其他節點的結構與十字鏈表中相同,如圖 2 所示:
圖 2 節點中各區域及功能如下:
- mark:標志域,用于標記此節點是否被操作過,例如在對圖中頂點做遍歷操作時,為了防止多次操作同一節點,mark 域為 0表示還未被遍歷;mark 為 1 表示該節點已被遍歷;
- ivex 和 jvex:數據域,分別存儲圖中各邊兩端的頂點所在數組中的位置下標;
- ilink:指針域,指向下一個存儲與 ivex 有直接關聯頂點的節點;
- jlink:指針域,指向下一個存儲與 jvex 有直接關聯頂點的節點;
- info:指針域,用于存儲與該頂點有關的其他信息,比如無向網中各邊的權;
綜合以上信息,如果我們想使用鄰接多重表存儲圖 3a) 中的無向圖,則與之對應的鄰接多重表如圖 3b) 所示:
從圖 3 中,可直接找到與各頂點有直接關聯的其他頂點。比如說,與頂點 V1 有關聯的頂點為存儲在數組下標 1 處的 V2 和數組下標 3 處的 V4,而與頂點 V2 有關聯的頂點有 3 個,分別是 V1、V3 和 V5。
圖 3 中鄰接多重表的整體結構轉化為 C 語言代碼如下所示:
#define MAX_VERTEX_NUM 20
#define InfoType int
#define VertexType int typedef enum {unvisited
,visited
}VisitIf
; typedef struct EBox
{VisitIf mark
; int ivex
,jvex
; struct EBox
* ilink
,*jlink
; InfoType
*info
;
}EBox
;typedef struct VexBox
{VertexType data
; EBox
* firstedge
;
}VexBox
;typedef struct {VexBox adjmulist
[MAX_VERTEX_NUM
];int vexnum
,degenum
;
}AMLGraph
;
二、圖的遍歷
1.深度優先搜索(DFS)
- 深度優先搜索的過程類似于樹的先序遍歷,首先從例子中體會深度優先搜索。例如圖 1 是一個無向圖,采用深度優先算法遍歷這個圖的過程為:
- 首先任意找一個未被遍歷過的頂點,例如從 V1 開始,由于 V1 率先訪問過了,所以,需要標記 V1 的狀態為訪問過;
- 然后遍歷 V1 的鄰接點,例如訪問 V2 ,并做標記,然后訪問 V2 的鄰接點,例如 V4 (做標記),然后 V8 ,然后 V5 ;
- 當繼續遍歷 V5 的鄰接點時,根據之前做的標記顯示,所有鄰接點都被訪問過了。
- 此時,從 V5 回退到 V8 ,看 V8 是否有未被訪問過的鄰接點,如果沒有,繼續回退到 V4 , V2 , V1 ;
- 通過查看 V1 ,找到一個未被訪問過的頂點 V3 ,繼續遍歷,然后訪問 V3 鄰接點 V6 ,然后 V7 ;
- 由于 V7 沒有未被訪問的鄰接點,所有回退到 V6 ,繼續回退至 V3 ,最后到達 V1 ,發現沒有未被訪問的;
- 最后一步需要判斷是否所有頂點都被訪問,如果還有沒被訪問的,以未被訪問的頂點為第一個頂點,繼續依照上邊的方式進行遍歷。
根據上邊的過程,可以得到圖 1 通過深度優先搜索獲得的頂點的遍歷次序為:
V1 -> V2 -> V4 -> V8 -> V5 -> V3 -> V6 -> V7
-
所謂深度優先搜索,是從圖中的一個頂點出發,每次遍歷當前訪問頂點的臨界點,一直到訪問的頂點沒有未被訪問過的臨界點為止。
-
然后采用依次回退的方式,查看來的路上每一個頂點是否有其它未被訪問的臨界點。
-
訪問完成后,判斷圖中的頂點是否已經全部遍歷完成,如果沒有,以未訪問的頂點為起始點,重復上述過程。
-
深度優先搜索是一個不斷回溯的過程。
-
采用深度優先搜索算法遍歷圖的實現代碼為:
#include <stdio.h>
#define MAX_VERtEX_NUM 20
#define VRType int
#define InfoType char
#define VertexType int typedef enum{false,true}bool;
bool visited
[MAX_VERtEX_NUM
];
typedef struct {VRType adj
; InfoType
* info
;
}ArcCell
,AdjMatrix
[MAX_VERtEX_NUM
][MAX_VERtEX_NUM
];typedef struct {VertexType vexs
[MAX_VERtEX_NUM
]; AdjMatrix arcs
; int vexnum
,arcnum
;
}MGraph
;
int LocateVex(MGraph
* G
,VertexType v
){int i
=0;for (; i
<G
->vexnum
; i
++) {if (G
->vexs
[i
]==v
) {break;}}if (i
>G
->vexnum
) {printf("no such vertex.\n");return -1;}return i
;
}
void CreateDN(MGraph
*G
){scanf("%d,%d",&(G
->vexnum
),&(G
->arcnum
));for (int i
=0; i
<G
->vexnum
; i
++) {scanf("%d",&(G
->vexs
[i
]));}for (int i
=0; i
<G
->vexnum
; i
++) {for (int j
=0; j
<G
->vexnum
; j
++) {G
->arcs
[i
][j
].adj
=0;G
->arcs
[i
][j
].info
=NULL;}}for (int i
=0; i
<G
->arcnum
; i
++) {int v1
,v2
;scanf("%d,%d",&v1
,&v2
);int n
=LocateVex(G
, v1
);int m
=LocateVex(G
, v2
);if (m
==-1 ||n
==-1) {printf("no this vertex\n");return;}G
->arcs
[n
][m
].adj
=1;G
->arcs
[m
][n
].adj
=1;}
}int FirstAdjVex(MGraph G
,int v
)
{for(int i
= 0; i
<G
.vexnum
; i
++){if( G
.arcs
[v
][i
].adj
){return i
;}}return -1;
}int NextAdjVex(MGraph G
,int v
,int w
)
{for(int i
= w
+1; i
<G
.vexnum
; i
++){if(G
.arcs
[v
][i
].adj
){return i
;}}return -1;
}void visitVex(MGraph G
, int v
){printf("%d ",G
.vexs
[v
]);
}void DFS(MGraph G
,int v
){visited
[v
] = true;visitVex( G
, v
); for(int w
= FirstAdjVex(G
,v
); w
>=0; w
= NextAdjVex(G
,v
,w
)){if(!visited
[w
]){DFS(G
,w
);}}
}
void DFSTraverse(MGraph G
){int v
;for( v
= 0; v
< G
.vexnum
; ++v
){visited
[v
] = false;}for( v
= 0; v
< G
.vexnum
; v
++){if(!visited
[v
]){DFS( G
, v
);}}
}int main() {MGraph G
;CreateDN(&G
);DFSTraverse(G
);return 0;
}
2.廣度優先搜索(BFS)
- 廣度優先搜索類似于樹的層次遍歷。
- 從圖中的某一頂點出發,遍歷每一個頂點時,依次遍歷其所有的鄰接點,然后再從這些鄰接點出發,同樣依次訪問它們的鄰接點。
- 按照此過程,直到圖中所有被訪問過的頂點的鄰接點都被訪問到。
- 最后還需要做的操作就是查看圖中是否存在尚未被訪問的頂點,若有,則以該頂點為起始點,重復上述遍歷的過程。
- 還拿圖 1 中的無向圖為例,假設 V1 作為起始點,遍歷其所有的鄰接點 V2 和 V3
- 以 V2 為起始點,訪問鄰接點 V4 和 V5
- 以 V3 為起始點,訪問鄰接點 V6 、 V7
- 以 V4 為起始點訪問 V8
- 以 V5 為起始點,由于 V5 所有的起始點已經全部被訪問,所有直接略過
- V6 和 V7 也是如此。
- 以 V1 為起始點的遍歷過程結束后,判斷圖中是否還有未被訪問的點,由于圖 1 中沒有了,所以整個圖遍歷結束。
遍歷頂點的順序為:
V1 -> V2 -> v3 -> V4 -> V5 -> V6 -> V7 -> V8
- 廣度優先搜索的實現需要借助隊列這一特殊數據結構,實現代碼為:
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERtEX_NUM 20
#define VRType int
#define InfoType char
#define VertexType int typedef enum{false,true}bool; bool visited
[MAX_VERtEX_NUM
]; typedef struct Queue
{VertexType data
;struct Queue
* next
;
}Queue
;typedef struct {VRType adj
; InfoType
* info
;
}ArcCell
,AdjMatrix
[MAX_VERtEX_NUM
][MAX_VERtEX_NUM
];typedef struct {VertexType vexs
[MAX_VERtEX_NUM
]; AdjMatrix arcs
; int vexnum
,arcnum
;
}MGraph
;
int LocateVex(MGraph
* G
,VertexType v
){int i
=0;for (; i
<G
->vexnum
; i
++) {if (G
->vexs
[i
]==v
) {break;}}if (i
>G
->vexnum
) {printf("no such vertex.\n");return -1;}return i
;
}
void CreateDN(MGraph
*G
){scanf("%d,%d",&(G
->vexnum
),&(G
->arcnum
));for (int i
=0; i
<G
->vexnum
; i
++) {scanf("%d",&(G
->vexs
[i
]));}for (int i
=0; i
<G
->vexnum
; i
++) {for (int j
=0; j
<G
->vexnum
; j
++) {G
->arcs
[i
][j
].adj
=0;G
->arcs
[i
][j
].info
=NULL;}}for (int i
=0; i
<G
->arcnum
; i
++) {int v1
,v2
;scanf("%d,%d",&v1
,&v2
);int n
=LocateVex(G
, v1
);int m
=LocateVex(G
, v2
);if (m
==-1 ||n
==-1) {printf("no this vertex\n");return;}G
->arcs
[n
][m
].adj
=1;G
->arcs
[m
][n
].adj
=1;}
}int FirstAdjVex(MGraph G
,int v
)
{for(int i
= 0; i
<G
.vexnum
; i
++){if( G
.arcs
[v
][i
].adj
){return i
;}}return -1;
}int NextAdjVex(MGraph G
,int v
,int w
)
{for(int i
= w
+1; i
<G
.vexnum
; i
++){if(G
.arcs
[v
][i
].adj
){return i
;}}return -1;
}
void visitVex(MGraph G
, int v
){printf("%d ",G
.vexs
[v
]);
}
void InitQueue(Queue
** Q
){(*Q
)=(Queue
*)malloc(sizeof(Queue
));(*Q
)->next
=NULL;
}
void EnQueue(Queue
**Q
,VertexType v
){Queue
* element
=(Queue
*)malloc(sizeof(Queue
));element
->data
=v
;Queue
* temp
=(*Q
);while (temp
->next
!=NULL) {temp
=temp
->next
;}temp
->next
=element
;
}
void DeQueue(Queue
**Q
,int *u
){(*u
)=(*Q
)->next
->data
;(*Q
)->next
=(*Q
)->next
->next
;
}
bool QueueEmpty(Queue
*Q
){if (Q
->next
==NULL) {return true;}return false;
}
void BFSTraverse(MGraph G
){int v
;for( v
= 0; v
< G
.vexnum
; ++v
){visited
[v
] = false;}Queue
* Q
;InitQueue(&Q
);for( v
= 0; v
< G
.vexnum
; v
++){if(!visited
[v
]){visited
[v
]=true;visitVex(G
, v
);EnQueue(&Q
, G
.vexs
[v
]);while (!QueueEmpty(Q
)) {int u
;DeQueue(&Q
, &u
);u
=LocateVex(&G
, u
);for (int w
=FirstAdjVex(G
, u
); w
>=0; w
=NextAdjVex(G
, u
, w
)) {if (!visited
[w
]) {visited
[w
]=true;visitVex(G
, w
);EnQueue(&Q
, G
.vexs
[w
]);}}}}}
}int main() {MGraph G
;CreateDN(&G
);BFSTraverse(G
);return 0;
}
總結
以上是生活随笔為你收集整理的数据结构--图(Graph)详解(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。