日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

数据结构--图(Graph)详解(二)

發布時間:2024/4/11 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 数据结构--图(Graph)详解(二) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

數據結構–圖(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; //枚舉圖的 4 種類型typedef struct {VRType adj; //對于無權圖,用 1 或 0 表示是否相鄰;對于帶權圖,直接為權值。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 。

  • 具體C實現代碼
#include <stdio.h> #define MAX_VERtEX_NUM 20 //頂點的最大個數 #define VRType int //表示頂點之間的關系的變量類型 #define InfoType char //存儲弧或者邊額外信息的指針變量類型 #define VertexType int //圖中頂點的數據類型typedef enum{DG,DN,UDG,UDN}GraphKind; //枚舉圖的 4 種類型typedef struct {VRType adj; //對于無權圖,用 1 或 0 表示是否相鄰;對于帶權圖,直接為權值。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;//遍歷一維數組,找到變量vfor (; i<G->vexnum; i++) {if (G->vexs[i]==v) {break;}}//如果找不到,輸出提示語句,返回-1if (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]));}//初始化二維矩陣,全部歸0,指針指向NULLfor (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)所示:

  • 簡易的Cpp實現
#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("非法頂點");//return -1;}}private://頂點的集合 vector<V> _vertexs;//邊的集合:鄰接矩陣vector<vector<W>> _edge;//通過頂點找下標;保存映射關系unordered_map<V,int> _vertexsIndexMap; };//end of class Graph }//end of namespace matrix

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 int//圖中弧或者邊包含的信息的類型typedef 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 個表結點。在圖中邊或者弧稀疏的時候,使用鄰接表要比前一節介紹的鄰接矩陣更加節省空間。

  • 簡易Cpp實現
#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("非法頂點");//return -1;}}private:vector<V> _vertexs;//邊的集合:鄰接表vector<Edge*> _linktable;unordered_map<V,int> _vertexsIndexMap;};//end if class Graph }//end of namespace linktable

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;//遍歷一維數組,找到變量vfor (; i<G->vexnum; i++) {if (G->xlist[i].data==v) {break;}}//如果找不到,輸出提示語句,返回 -1if (i>G->vexnum) {printf("no such vertex.\n");return -1;}return i; }//構建十字鏈表函數 void CreateDG(OLGraph *G){//輸入有向圖的頂點數和弧數scanf("%d,%d",&(G->vexnum),&(G->arcnum));//使用一維數組存儲頂點數據,初始化指針域為NULLfor (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);//確定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結點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; //分別指向與ivex、jvex相關的下一個邊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型常量 bool visited[MAX_VERtEX_NUM]; //設置全局數組,記錄標記頂點是否被訪問過 typedef struct {VRType adj; //對于無權圖,用 1 或 0 表示是否相鄰;對于帶權圖,直接為權值。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;//遍歷一維數組,找到變量vfor (; i<G->vexnum; i++) {if (G->vexs[i]==v) {break;}}//如果找不到,輸出提示語句,返回-1if (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) {//查找與數組下標為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) {//從前一個訪問位置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;//標記為truevisitVex( G, v); //訪問第v 個頂點//從該頂點的第一個邊開始,一直到最后一個邊,對處于邊另一端的頂點調用DFS函數for(int w = FirstAdjVex(G,v); w>=0; w = NextAdjVex(G,v,w)){//如果該頂點的標記位false,證明未被訪問,調用深度優先搜索函數if(!visited[w]){DFS(G,w);}} }//深度優先搜索 void DFSTraverse(MGraph G){//int v;//將用做標記的visit數組初始化為falsefor( v = 0; v < G.vexnum; ++v){visited[v] = false;}//對于每個標記為false的頂點調用深度優先搜索函數for( v = 0; v < G.vexnum; v++){//如果該頂點的標記位為false,則調用深度優先搜索函數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型常量bool visited[MAX_VERtEX_NUM]; //設置全局數組,記錄標記頂點是否被訪問過typedef struct Queue{VertexType data;struct Queue * next; }Queue;typedef struct {VRType adj; //對于無權圖,用 1 或 0 表示是否相鄰;對于帶權圖,直接為權值。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;//遍歷一維數組,找到變量vfor (; i<G->vexnum; i++) {if (G->vexs[i]==v) {break;}}//如果找不到,輸出提示語句,返回-1if (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) {//查找與數組下標為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) {//從前一個訪問位置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; }//頂點元素v進隊列 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;//將用做標記的visit數組初始化為falsefor( v = 0; v < G.vexnum; ++v){visited[v] = false;}//對于每個標記為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)详解(二)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。