上次我們介紹了赫夫曼的實現,這次介紹圖這一章的第一個程序—— 圖的鄰接矩陣存儲表示各基本操作的實現。
還是老規矩:
程序在碼雲上可以下載。
地址:https://git.oschina.net/601345138/DataStructureCLanguage.git
圖相比於樹真是麻煩不少:從一對多邏輯關係變成了多對多。各項操作的複雜度都有了很大的提升。
圖究竟可以做哪些操作?先看看ADT定義:
ADT Graph {
數據對象V:V是具有相同特性的數據元素的集合,稱爲頂點集。
數據關係R:
R = {VR}
VR = {<v,w>| v,w∈V 且 P(v,w), <v,w>表示從 v 到 w 的弧。
謂詞 P(v,w) 定義了弧 <v,w>的意義或信息。}
基本操作P:
CreatGraph(&G, V, VR);
//初始條件:V是圖的頂點集,VR是圖中弧的集合。
//操作結果:按V和VR的定義構造圖G。
DestroyGraph(&G);
//初始條件:圖G存在。
//操作結果:銷燬圖G。
LocateVex(G, u);
//初始條件:圖G存在,u和G中頂點有共同特徵
//操作結果:若G中存在頂點u,則返回該頂點在圖中位置;否則返回其它信息。
GetVex(G, v);
//初始條件:圖G存在,v是G中某個頂點。
//操作結果:返回v的值。
PutVex(&G, v, value);
//初始條件:圖G存在,v是G中某個頂點。
//操作結果:對v賦值value。
FirstAdjVex(G, v);
//初始條件:若圖G存在,v是G中的某個頂點。
//操作結果:返回v的第一個鄰接點。若該頂點在G中沒有鄰接點,則返回空。
NextAdjVex(G, v, w);
//初始條件:圖G存在, v是G中的某個頂點,w是v的鄰接頂點。
//操作結果:返回v的(相對於w的)下一個鄰接點。
若w是v的最後一個鄰接點,則返回空。
InsertVex(&G, v);
//初始條件:圖G存在,v和圖中的頂點有相同特徵。
//操作結果:在圖G中增添新頂點v。
DeleteVex(&G, v);
//初始條件:圖G存在,v和w是G中的兩個頂點
//操作結果:刪除G中頂點v及其相關的弧。
InsertArc(&G, v, w);
//初始條件:圖G存在,v和w是G中的兩個頂點。
//操作結果:在G中增添弧<v,w>,若G是無向的,則還增添對稱弧<w,v>。
DeleteArc(&G, v, w);
//初始條件:圖G存在,v和w是G中的兩個頂點。
//操作結果:在G中刪除弧<v,w>,若G是無向的,則還刪除對稱弧<w,v>。
DFSTraverse(G, v, Visit());
//初始條件:圖G存在,Visit是頂點的函數。
//操作結果:對圖進行深度優先遍歷,在遍歷過程中對每個頂點調用函數
Visit一次且僅一次。一旦Visit失敗則操作失敗。
BFSTraverse(G, v, Visit());
//初始條件:圖G存在,Visit是頂點的應用函數。
//操作結果:對圖進行廣度優先遍歷,在遍歷過程中對每個頂點調用函數
Visit一次且僅一次。一旦Visit失敗則操作失敗。
}ADT Graph
書上介紹了圖的四種存儲結構,但我們只實現其中兩種——鄰接矩陣和鄰接表,爲圖這一章後面的算法實現打好基礎。
這一次先介紹圖的鄰接矩陣表示法——用矩陣表示頂點的鄰接關係,用二維數組存儲矩陣。就像這樣:
本次程序用到的源文件清單:
my_constants.h 各種狀態碼
MGraph.h 圖的鄰接矩陣存儲結構表示定義
MGraph.cpp 基於鄰接矩陣的基本操作實現
DFS_BFS_Traverse_MGraph.cpp 基於鄰接矩陣的深度優先遍歷和廣度優先遍歷算法實現
SqQueue.cpp 順序隊列(循環隊列)的實現
基本操作及遍歷測試.cpp 主函數,調用基本操作完成演示
注意:所有源文件需放在同一個目錄下編譯。
本次程序的廣度優先遍歷用到了隊列,我使用了第三章的程序:循環隊列。
循環隊列在《數據結構編程筆記十:第三章 棧和隊列 循環隊列的實現》一文中有介紹。
具體程序我放在總結後面。如有需要請到那裏查看。
需要注意的是:我對書上P161頁的存儲結構做了改造,去掉了info指針。
原因是:鄰接矩陣中info指針完全沒必要設置,因爲無論是圖還是網,adj這個數據域都已經足夠存儲所有有用信息了。adj可以不像鄰接表一樣存儲鄰接點,因爲它已經隱含在鄰接矩陣的列名中了。作者設置info指針的本意是指向存儲網權值的內存空間。但是由於鄰接矩陣中的adj本身就能存儲網中的權值,也不會引起歧義,而且設置了info之後還會涉及很多內存的分配、釋放和指針操作。爲了使一個函數適用於四種圖,需要根據圖的不同類型對這個指針執行不同的操作。去掉info可以簡化很多操作。
接下來一起看看源碼實現吧!
源文件:my_constants.h
//************************自定義符號常量***************************
#define OVERFLOW -2 //內存溢出錯誤常量
#define OK 1 //表示操作正確的常量
#define ERROR 0 //表示操作錯誤的常量
#define TRUE 1 //表示邏輯真的常量
#define FALSE 0 //表示邏輯假的常量
//***********************自定義數據類型****************************
typedef int Status; //指定狀態碼的類型是int
源文件:MGraph.h
//-------------圖的數組(鄰接矩陣)存儲表示
//一個輸入的權值很難達到的最大值 ∞
#define INFINITY 65535
//最大頂點個數
#define MAX_VERTEX_NUM 20
//使用枚舉類型定義圖的四種基本類型
typedef enum {
//(有向圖=0,有向網=1,無向圖=2,無向網=3)
DG, DN, UDG, UDN
} GraphKind;
//指定頂點關係類型爲int
typedef int VRType;
//指定頂點類型爲int
typedef int VertexType;
typedef struct ArcCell{
//VRType是頂點關係類型。
//對無權圖,用1或0,表示相鄰與否,對帶權圖,則爲權值類型
VRType adj;
//我修改了書上的定義刪除了info指針。
//原因是:info指針完全沒必要設置,因爲無論是圖還是網,
// adj這個數據域都已經足夠存儲所有有用信息了。
// 作者設置info指針的本意是指向存儲網權值的內存空間。
// 但是由於adj本身就能存儲網中的權值,也不會引起歧義
// 而且設置了info之後還會涉及很多內存的分配、釋放和指針操作
// 爲了使一個函數適用於四種圖,需要根據圖的不同類型對這個指針
// 執行不同的操作。去掉info可以簡化操作。
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct{
//頂點向量:存儲了圖中所有的頂點
VertexType vexs[MAX_VERTEX_NUM];
//鄰接矩陣:存儲了頂點之間的鄰接關係以及權值
AdjMatrix arcs;
//圖當前的頂點數和弧數
int vexnum, arcnum;
//圖的種類標誌
GraphKind kind;
}MGraph;
源文件:MGraph.cpp
/*
函數:LocateVex
參數:MGraph G 圖G(鄰接矩陣存儲結構)
返回值:若G中存在頂點v,則返回該頂點在圖中位置;否則返回-1
作用:頂點定位函數,在圖G中查找頂點v的位置
*/
int LocateVex(MGraph G, int v){
//遍歷鄰接矩陣查找頂點v
for(int i = 0; i <= G.vexnum; ++i) {
//若找到結點返回i
if(G.vexs[i] == v) {
return i;
}//if
}//for
//否則返回-1,表示沒有找到
return -1;
}//LocateVex
/*
函數:CreateUDN
參數:MGraph &G 圖的引用
返回值:狀態碼,操作成功返回OK
作用:採用數組(鄰接矩陣)表示法,構造無向網G
*/
Status CreateUDN(MGraph &G){
//臨時變量,從鍵盤接收頂點名稱
VertexType v1, v2;
//臨時變量i用於保存頂點v1在圖中的序號
//臨時變量j用於保存頂點v2在圖中的序號
//臨時變量w用於接收輸入的權值
int w, i, j;
//確定無向網的頂點數和邊數
printf("請依次輸入無向網G的頂點數,弧數,用逗號隔開\n");
scanf("%d,%d", &G.vexnum, &G.arcnum);
//從鍵盤輸入各個頂點以構造各頂點向量
printf("請依次輸入無向網G的頂點名稱,用空格隔開\n");
for(int i = 0; i < G.vexnum; ++i) {
scanf("%d", &G.vexs[i]);
}//for
//初始化鄰接矩陣各存儲單元的值爲INFINITY,INFINITY表示兩頂點不可達
for(int i = 0; i < G.vexnum; ++i) {
for(int j = 0; j < G.vexnum; ++j) {
G.arcs[i][j].adj = INFINITY;
}//for
}//for
//輸入弧依附的頂點和權值,填充鄰接矩陣
printf("請依次輸入無向網G每條弧依附的兩頂點名稱及權值,輸完一組按回車\n");
for(int k = 0; k < G.arcnum; ++k){
//輸入弧依附的兩頂點v1和v2
scanf("%d", &v1);
scanf("%d", &v2);
//輸入弧上的權值
scanf("%d", &w);
//確定v1,v2在G中的位置i,j
i = LocateVex(G, v1);
j = LocateVex(G, v2);
//保存權值到鄰接矩陣的對應存儲單元中
G.arcs[i][j].adj = w;
//由於構造的是無向網,所以還需要置<v1,v2>的對稱弧<v2,v1>
//技巧:結構體直接賦值相當於結構體中各個分量的對應拷貝
G.arcs[j][i] = G.arcs[i][j];
}//for
//操作成功
return OK;
}// CreateUDN
/*
函數:CreateUDG
參數:MGraph &G 圖的引用
返回值:狀態碼,操作成功返回OK
作用:採用數組(鄰接矩陣)表示法,構造無向圖G
*/
Status CreateUDG(MGraph &G){
//接收用戶從鍵盤輸入的頂點
VertexType v1, v2;
//臨時變量i用於保存頂點v1在圖G中的序號
//臨時變量j用於保存頂點v2在圖G中的序號
int i, j;
//確定無向圖的頂點數和邊數,用作循環控制邊界
printf("請依次輸入無向圖G的頂點數,弧數,用逗號隔開\n");
scanf("%d,%d", &G.vexnum, &G.arcnum);
//輸入各頂點名稱並構造各頂點向量
printf("請依次輸入無向圖G的頂點名稱,用空格隔開\n");
for(int i = 0; i < G.vexnum; ++i) {
scanf("%d", &G.vexs[i]);
}//for
//初始化鄰接矩陣所有存儲單元的值爲0,0表示兩頂點之間不可達
for(int i = 0; i < G.vexnum; ++i) {
for(int j = 0; j < G.vexnum; ++j) {
G.arcs[i][j].adj = 0;
}//for
}//for
//輸入弧依附的頂點,填充鄰接矩陣
printf("請依次輸入無向圖G每條弧依附的兩頂點名稱,輸完一組按回車\n");
for(int k = 0; k < G.arcnum; ++k){
//輸入弧依附的兩頂點v1和v2
scanf("%d", &v1);
scanf("%d", &v2);
//確定v1,v2在G中的位置i和j
i = LocateVex(G, v1);
j = LocateVex(G, v2);
//設置v1->v2的值爲1,表示v1->v2是可達的
G.arcs[i][j].adj = 1;
//由於構造的是無向圖,所以還需要置<v1,v2>的對稱弧<v2,v1>
G.arcs[j][i] = G.arcs[i][j];
}//for
//操作成功
return OK;
}// CreateUDG
/*
函數:CreateDN
參數:MGraph &G 圖的引用
返回值:狀態碼,操作成功返回OK
作用:採用數組(鄰接矩陣)表示法,構造有向網G
*/
Status CreateDN(MGraph &G){
//臨時變量v1和v2用於接收從鍵盤輸入的兩頂點的值
VertexType v1, v2;
//臨時變量w用於保存用戶輸入的權值
//臨時變量i用於保存頂點v1在圖中的位置
//臨時變量j用於保存頂點v2在圖中的位置
int w, i, j;
//確定有向網的頂點數和邊數,用作循環控制邊界
printf("請依次輸入有向網G的頂點數,弧數,用逗號隔開\n");
scanf("%d,%d", &G.vexnum, &G.arcnum);
//輸入頂點名稱構造各頂點向量
printf("請依次輸入有向網G的頂點名稱,用空格隔開\n");
for(int i = 0; i < G.vexnum; ++i) {
scanf("%d", &G.vexs[i]);
}//for
//初始化鄰接矩陣各存儲單元的值爲INFINITY,INFINITY表示兩頂點不可達
for(int i = 0; i < G.vexnum; ++i) {
for(int j = 0; j < G.vexnum; ++j) {
G.arcs[i][j].adj = INFINITY;
}//for
}//for
//輸入弧依附的頂點和權值,填充鄰接矩陣
printf("請依次輸入有向網G每條弧依附的兩頂點名稱及權值,輸完一組按回車\n");
for(int k = 0; k < G.arcnum; ++k) {
//輸入弧依附的兩頂點v1和v2
scanf("%d", &v1);
scanf("%d", &v2);
//輸入弧上的權值
scanf("%d", &w);
//確定v1,v2在G中的位置
i = LocateVex(G, v1);
j = LocateVex(G, v2);
//保存權值到鄰接矩陣的對應存儲單元中
G.arcs[i][j].adj = w;
}//for
//操作成功
return OK;
}// CreateDN
/*
函數:CreateDG
參數:MGraph &G 圖的引用
返回值:狀態碼,操作成功返回OK
作用:採用數組(鄰接矩陣)表示法,構造有向圖G
*/
Status CreateDG(MGraph &G){
//臨時變量v1和v2用於接收從鍵盤輸入的兩頂點的值
VertexType v1, v2;
//臨時變量i用於保存頂點v1在圖中的位置
//臨時變量j用於保存頂點v2在圖中的位置
int i, j;
//確定有向網的頂點數和邊數,用作循環控制邊界
printf("請依次輸入有向圖G的頂點數,弧數,用逗號隔開\n");
scanf("%d,%d", &G.vexnum, &G.arcnum);
//輸入頂點名稱構造各頂點向量
printf("請依次輸入有向圖G的頂點名稱,用空格隔開\n");
for(int i = 0; i < G.vexnum; ++i) {
scanf("%d", &G.vexs[i]);
}//for
//初始化鄰接矩陣所有存儲單元的值爲0,0表示兩頂點之間不可達
for(int i = 0; i < G.vexnum; ++i) {
for(int j = 0; j < G.vexnum; ++j) {
G.arcs[i][j].adj=0;
}//for
}//for
//輸入弧依附的頂點,填充鄰接矩陣
printf("請依次輸入有向圖G每條弧依附的兩頂點名稱,輸完一組按回車\n");
for(int k = 0; k < G.arcnum; ++k) {
//輸入弧依附的兩頂點v1和v2
scanf("%d", &v1);
scanf("%d", &v2);
//確定v1,v2在G中的位置i和j
i = LocateVex(G, v1);
j = LocateVex(G, v2);
//設置v1->v2的值爲1,表示v1->v2是可達的
G.arcs[i][j].adj = 1;
}//for
//操作成功
return OK;
}// CreateDG
/*
函數:CreateGraph
參數:MGraph &G 圖的引用
返回值:狀態碼,操作成功返回OK
作用:採用數組(鄰接矩陣)表示法,構造圖G
*/
Status CreateGraph(MGraph &G){
//輸入構造圖的類型
printf("請輸入您想構造的圖的類型:有向圖輸入0,有向網輸入1,無向圖輸入2,無向網輸入3):");
scanf("%d", &G.kind);
//根據圖的不同類型調用圖的構造函數
switch(G.kind){
case DG: return CreateDG(G); //構造有向圖G
case DN: return CreateDN(G); //構造有向網G
case UDG: return CreateUDG(G); //構造無向圖G
case UDN: return CreateUDN(G); //構造無向網G
default : return ERROR;
}//switch
}//CreateGraph
/*
函數:DestroyGraph
參數:MGraph &G 圖的引用
返回值:狀態碼,操作成功返回OK
作用:銷燬圖G
*/
Status DestroyGraph(MGraph &G) {
//若是網
if(G.kind % 2) {
//重置鄰接矩陣所有頂點之間不可達
for(int i = 0; i < G.vexnum; i++) {
for(int j = 0; j < G.vexnum; j++) {
G.arcs[i][j].adj = INFINITY;
}//for
}//for
}//if
else { //若是圖
//重置鄰接矩陣所有頂點之間不可達
for(int i = 0; i < G.vexnum; i++) {
for(int j = 0; j < G.vexnum; j++) {
G.arcs[i][j].adj = 0;
}//for
}//for
}//else
//重置頂點數爲0
G.vexnum = 0;
//重置邊數爲0
G.arcnum = 0;
}//DestroyGraph
/*
函數:PrintAdjMatrix
參數:MGraph G 圖G
返回值:狀態碼,操作成功返回OK
作用:打印某個圖的鄰接矩陣
*/
Status PrintAdjMatrix(MGraph G){
//輸出左上角多餘的空白
printf(" ");
//輸出鄰接矩陣的上座標(全部頂點)
for(int i = 0; i < G.vexnum; i++) {
printf(" %3d ", G.vexs[i]);
}//for
printf("\n");
//輸出左上角多餘的空白
printf(" +");
//輸出一條橫線
for(int i = 0; i < G.vexnum; i++) {
printf("-----");
}//for
printf("\n");
//輸出鄰接矩陣的左座標(全部頂點)和內容
for(int i = 0; i < G.vexnum; i++) {
//輸出鄰接矩陣左邊的座標
printf(" %3d |", G.vexs[i]);
for(int j = 0; j < G.vexnum; j++) {
if(G.arcs[i][j].adj == INFINITY) {
printf(" ∞ ");
}//if
else {
printf(" %3d ", G.arcs[i][j].adj);
}//else
}//for
printf("\n |\n");
}//for
}//PrintAdjMatrix
/*
函數:GetVex
參數:MGraph G 圖G
int v v是G中某個頂點的序號
返回值:返回v的值
作用:得到序號爲v的頂點值
*/
VertexType& GetVex(MGraph G, int v) {
//檢查參數v是否合法:v是否越界
if(v >= G.vexnum || v < 0) {
exit(ERROR);
}//if
//返回序號爲v的頂點值
return G.vexs[v];
}//GetVex
/*
函數:PutVex
參數:MGraph &G 圖的引用
VertexType v v是G中某個頂點
VertexType value 將序號爲v的頂點的值修改爲value
返回值:狀態碼,操作成功返回OK,否則返回ERROR
作用:修改序號爲v的頂點值
*/
Status PutVex(MGraph &G, VertexType v, VertexType value) {
//k爲頂點v在圖G中的序號
int k = LocateVex(G, v);
//檢查圖中是否存在頂點v
if(k < 0) {
return ERROR;
}//if
//將頂點v的值置爲value
G.vexs[k] = value;
//操作成功
return OK;
}//PutVex
/*
函數:FirstAdjVex
參數:MGraph G 圖G
VertexType v 頂點v
返回值:若找到鄰接點,返回鄰接點的頂點位置,否則返回-1
作用:求頂點v在圖G中的第一個鄰接點
*/
int FirstAdjVex(MGraph G, VertexType v){
//j表示不可達,在圖中,0表示不可達,在網中INFINITY表示不可達
int j = 0;
//v是頂點,不是序號!需要定位
//k就是頂點v在頂點數組中的序號
int k = LocateVex(G, v);
//若是網,則INFINITY表示不可達
if(G.kind == DN || G.kind == UDN) {
j = INFINITY;
}//if
//在頂點v對應的第k行查找第一個鄰接點的序號i
for(int i = 0; i < G.vexnum; ++i) {
//若找到頂點v的第一個鄰接點則返回i
//G.arcs[k][i].adj != j 表示頂點G.arcs[k][i]可達
if(G.arcs[k][i].adj != j) {
return i;
}//if
}//for
//若未找到返回-1
return -1;
}//FirstAdjVex
/*
函數:NextAdjVex
參數:MGraph G 圖G
VertexType v 頂點v
返回值:若找到鄰接點,返回鄰接點的頂點位置,否則返回-1
作用:求頂點v在圖G中相對於鄰接點w的下一個鄰接點
*/
int NextAdjVex(MGraph G, VertexType v, VertexType w){
//j表示不可達,在圖中,0表示不可達,在網中INFINITY表示不可達
int j = 0;
//k1是頂點v在頂點數組中的位序
int k1 = LocateVex(G, v);
//k2是頂點w在頂點數組中的位序
int k2 = LocateVex(G, w);
//若是網,則使用INFINITY表示不可達
if(G.kind == DN || G.kind == UDN) {
j = INFINITY;
}//if
//在圖G中查找頂點v相對於頂點w的下一個鄰接點的位置
for(int i= k2 + 1; i < G.vexnum; ++i) {
//若找到則返回i
if(G.arcs[k1][i].adj != j) {
return i;
}//if
}//for
//若未找到返回-1
return -1;
}//NextAdjVex
/*
函數:InsertVex
參數:MGraph &G 圖的引用
VertexType v 頂點v
返回值:狀態碼,操作成功返回OK
作用:在圖G中增添新頂點v
*/
Status InsertVex(MGraph &G, VertexType v) {
//j表示不可達,在圖中,0表示不可達,在網中INFINITY表示不可達
int j = 0;
//若是網,則使用INFINITY表示不可達
if(G.kind % 2) {
j = INFINITY;
}//if
//構造新頂點向量
G.vexs[G.vexnum] = v;
//初始化鄰接矩陣
for(int i = 0; i <= G.vexnum; i++) {
//初始化矩陣的每個存儲單元爲不可達
G.arcs[G.vexnum][i].adj = G.arcs[i][G.vexnum].adj = j;
}//for
//圖G的頂點數加1
G.vexnum++;
//操作成功
return OK;
}//InsertVex
/*
函數:DeleteVex
參數:MGraph &G 圖的引用
VertexType v 頂點v
返回值:狀態碼,操作成功返回OK
作用:刪除G中頂點v及其相關的弧
*/
Status DeleteVex(MGraph &G, VertexType v) {
//m表示不可達,在圖中,0表示不可達,在網中INFINITY表示不可達
VRType m = 0;
//若是網,則使用INFINITY表示不可達
if(G.kind % 2) {
m = INFINITY;
}//if
//k爲待刪除頂點v的序號
int k = LocateVex(G, v);
//檢查v是否是圖G的頂點
if(k < 0) { //v不是圖G的頂點
//操作失敗
return ERROR;
}//if
//刪除邊的信息
for(int j = 0; j < G.vexnum; j++) {
//有入弧
if(G.arcs[j][k].adj != m) {
//刪除弧
G.arcs[j][k].adj = m;
//弧數-1
G.arcnum--;
}//if
//有出弧
if(G.arcs[k][j].adj != m) {
//刪除弧
G.arcs[k][j].adj = m;
//弧數-1
G.arcnum--;
}//if
}//for
//序號k後面的頂點向量依次前移
for(int j = k + 1; j < G.vexnum; j++) {
G.vexs[j-1] = G.vexs[j];
}//for
//移動待刪除頂點之右的矩陣元素
for(int i = 0; i < G.vexnum; i++) {
for(int j = k + 1; j < G.vexnum; j++) {
G.arcs[i][j - 1] = G.arcs[i][j];
}//for
}//for
//移動待刪除頂點之下的矩陣元素
for(int i = 0; i < G.vexnum; i++) {
for(int j = k + 1; j < G.vexnum; j++) {
G.arcs[j - 1][i] = G.arcs[j][i];
}//for
}//for
//圖的頂點數-1
G.vexnum--;
//操作成功
return OK;
}//DeleteVex
/*
函數:InsertArc
參數:MGraph &G 圖的引用
VertexType v 頂點v(弧尾)
VertexType w 頂點w(弧頭)
返回值:狀態碼,操作成功返回OK
作用:在G中增添弧<v,w>,若G是無向的,則還增添對稱弧<w,v>
*/
Status InsertArc(MGraph &G, VertexType v, VertexType w) {
//弧尾頂點v在圖中的序號v1
int v1 = LocateVex(G, v);
//弧頭頂點w在圖中的序號w1
int w1 = LocateVex(G, w);
//檢查頂點v和頂點w在圖中是否存在
if(v1 < 0 || w1 < 0) { //v或w有一個不是圖G中的頂點
//操作失敗
return ERROR;
}//if
//弧或邊數加1
G.arcnum++;
//如果是圖G是網,還需要輸入權值
//if(G.kind % 2) <=> if(G.kind % 2 != 0)
if(G.kind % 2) {
printf("請輸入此弧或邊的權值: ");
scanf("%d", &G.arcs[v1][w1].adj);
}//if
else { // 圖
G.arcs[v1][w1].adj = 1;
}//else
//如果是無向圖或無向網還需要置對稱的邊
if(G.kind > 1) {
G.arcs[w1][v1].adj = G.arcs[v1][w1].adj;
}//if
//操作成功
return OK;
}//InsertArc
/*
函數:DeleteArc
參數:MGraph &G 圖的引用
VertexType v 頂點v(弧尾)
VertexType w 頂點w(弧頭)
返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR
作用:在G中刪除弧<v,w>,若G是無向的,則還刪除對稱弧<w,v>
*/
Status DeleteArc(MGraph &G, VertexType v, VertexType w) {
//j表示不可達,在圖中,0表示不可達,在網中INFINITY表示不可達
int j = 0;
//若是網,則使用INFINITY表示不可達
if(G.kind % 2) {
j = INFINITY;
}//if
//弧頭頂點v在圖中的序號v1
int v1 = LocateVex(G, v);
//弧尾頂點w在圖中的序號w1
int w1 = LocateVex(G, w);
//檢查頂點v和頂點w在圖中是否存在
if(v1 < 0 || w1 < 0) { //v或w有一個不是圖G中的頂點
return ERROR;
}//if
//將頂點v與頂點w之間設爲不可達
G.arcs[v1][w1].adj = j;
//如果是無向圖或網,還需要刪除對稱弧<w,v>
if(G.kind >= 2) {
G.arcs[w1][v1].adj = j;
}//if
//弧數-1
G.arcnum--;
//操作成功
return OK;
}//DeleteArc
源文件:DFS_BFS_Traverse_MGraph.cpp
//-----------------------深度優先遍歷DFS-----------------------------
//訪問標誌數組
int visited[MAX_VERTEX_NUM];
//函數變量
Status (*VisitFunc)(int v);
/*
函數:Print
參數:int v 被訪問的頂點v
返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR
作用:元素訪問函數
*/
Status Print(int v){
//設置元素訪問方式爲控制檯打印
printf(" %3d ", v);
//操作成功
return OK;
}//Print
/*
函數:DFSTraverse
參數:MGraph G 圖G
int v 從序號爲v的頂點出發
返回值:無
作用:從第v個頂點出發遞歸地深度優先遍歷圖G
*/
void DFS(MGraph G, int v){
//先將訪問標誌數組該元素的訪問標誌改爲True,
//含義是序號爲v的頂點已被訪問
visited[v] = TRUE;
//訪問第v個頂點
VisitFunc(G.vexs[v]);
//依次訪問v的各個鄰接頂點(向v的鄰接點延伸)
for(int w = FirstAdjVex(G, G.vexs[v]); w >= 0;
w = NextAdjVex(G, G.vexs[v], G.vexs[w])){
//對v的尚未被訪問的鄰接點w遞歸調用DFS
if(!visited[w]) {
DFS(G, w);
}//if
}//for
}//DFS
/*
函數:DFSTraverse
參數:MGraph G 圖G
Status (*Visit)(int v) 函數指針,指向元素訪問函數
返回值:無
作用:對圖G作深度優先遍歷,調用Visit()函數訪問結點
*/
void DFSTraverse(MGraph G, Status (*Visit)(int v)){
//使用全局變量VisitFunc,使DFS不必設函數指針參數
VisitFunc = Visit;
//預置標誌數組visited所有值爲FALSE
for(int v = 0; v < G.vexnum; ++v) {
visited[v] = FALSE;
}//for
//深度優先遍歷主循環
//寫成循環是爲了保證在圖分爲多個連通分量時能對
//每個連通分量進行遍歷
for(int v = 0; v < G.vexnum; ++v) {
//若該頂點未被訪問,則調用DFS()訪問該節點
if(!visited[v]) {
DFS(G, v);
}//if
}//for
}//DFSTraverse
//----------------廣度優先遍歷 (需要使用隊列)BFS------------
//說明:隊列的相關代碼包含在"SqQueue.cpp"中,關於隊列的詳細方法請閱讀該文件
/*
函數:BFSTraverse
參數:MGraph G 圖G
Status (*Visit)(int v) 函數指針,指向元素訪問函數
返回值:無
作用:按廣度優先非遞歸遍歷圖G,使用輔助隊列Q和訪問標誌數組visited
*/
void BFSTraverse(MGraph G, Status (*Visit)(int v)){
int u;
//廣度優先遍歷使用到遍歷
SqQueue Q;
//預置標誌數組visited所有值爲FALSE
for(int v = 0; v < G.vexnum; ++v) {
visited[v] = FALSE;
}//for
//初始化輔助隊列Q,得到一個空隊列
InitQueue_Sq(Q);
//廣度優先遍歷主循環
for(int v = 0; v < G.vexnum; ++v) {
//v尚未訪問
if(!visited[v]){
//設置v已經被訪問
visited[v] = TRUE;
//訪問第v頂點
Visit(G.vexs[v]);
//v入隊列
EnQueue_Sq(Q, v);
//隊列不空
while(!QueueEmpty_Sq(Q)){
//隊頭元素出隊並置爲u
DeQueue_Sq(Q, u);
//依次訪問第u頂點的鄰接頂點
for(int w = FirstAdjVex(G, G.vexs[u]); w >= 0;
w = NextAdjVex(G, G.vexs[u], G.vexs[w])){
//w爲v尚未訪問的鄰接頂點
if(!visited[w]){
//設置第w頂點已被訪問
visited[w] = TRUE;
//訪問第w頂點
Visit(G.vexs[w]);
}//if
}//for
}//while
}//if
}//for
//銷燬循環隊列
DestoryQueue_Sq(Q);
}//BFSTraverse
源文件:基本操作及遍歷測試.cpp
//**************************引入頭文件*****************************
#include <stdio.h> //使用了標準庫函數
#include <stdlib.h> //使用了動態內存分配函數
#include "my_constants.h" //引入自定義的符號常量,主要是狀態碼
#include "SqQueue.cpp" //由於廣度優先遍歷要使用隊列,插入刪除少訪問多,故引入順序隊列
#include "MGraph.h" //引入圖的鄰接矩陣表示法的基本定義
#include "MGraph.cpp" //引入圖的主要操作
#include "DFS_BFS_Traverse_MGraph.cpp" //引入圖的深度優先和廣度優先遍歷算法實現
//----------------------主函數----------------------
int main(int argc, char *argv[]){
printf("\n-------------圖的鄰接矩陣表示法基本操作測試程序--------------\n\n");
//圖G
MGraph G;
//臨時變量,保存輸入的頂點
VertexType v1, v2;
//臨時變量,保存輸入的頂點數
int n;
//圖的創建
printf("->測試圖的創建:\n");
CreateGraph(G);
//打印鄰接矩陣
printf("\n->創建成功後圖的鄰接矩陣:\n\n");
PrintAdjMatrix(G);
//測試圖的遍歷(深度、廣度)
printf("\n->測試圖的遍歷:\n");
printf("->深度優先遍歷結果:");
DFSTraverse(G, Print);
printf("\n->廣度優先遍歷結果:");
BFSTraverse(G, Print);
printf("\n");
//測試插入頂點和邊的操作
printf("\n->測試插入新頂點\n ");
printf("->請輸入頂點的值: ");
scanf("%d", &v1);
InsertVex(G, v1);
printf("->測試插入邊\n");
printf("->插入與新頂點(有向圖或網中的弧尾)有關的弧或邊,請輸入弧或邊數: ");
scanf("%d", &n);
for(int k = 0; k < n; k++) {
printf("->請輸入另一頂點(弧頭)的值: ");
scanf("%d", &v2);
//以v1爲弧尾,v2爲弧頭插入弧
InsertArc(G, v1, v2);
}//for
printf("->插入頂點和弧後圖的鄰接矩陣:\n\n");
PrintAdjMatrix(G);
//測試刪除頂點
printf("\n->刪除頂點及相關的弧或邊,請輸入頂點的值: ");
scanf("%d", &v1);
DeleteVex(G, v1);
printf("->刪除頂點和弧後圖的鄰接矩陣:\n\n");
PrintAdjMatrix(G);
//測試銷燬
printf("\n->測試銷燬圖: ");
DestroyGraph(G);
printf("成功!\n");
printf("演示結束,程序退出!\n");
return 0;
}
測試採用的是書上P174頁的無向網。
運行輸入的數據以及程序的輸出如下:
-------------圖的鄰接矩陣表示法基本操作測試程序--------------
->測試圖的創建:
請輸入您想構造的圖的類型:有向圖輸入0,有向網輸入1,無向圖輸入2,無向網輸入3):3
請依次輸入無向網G的頂點數,弧數,用逗號隔開
6,10
請依次輸入無向網G的頂點名稱,用空格隔開
1 2 3 4 5 6
請依次輸入無向網G每條弧依附的兩頂點名稱及權值,輸完一組按回車
1 2 6
1 4 5
1 3 1
3 2 5
3 4 5
3 5 6
3 6 4
2 5 3
4 6 2
5 6 6
->創建成功後圖的鄰接矩陣:
1 2 3 4 5 6
+------------------------------
1 | ∞ 6 1 5 ∞ ∞
|
2 | 6 ∞ 5 ∞ 3 ∞
|
3 | 1 5 ∞ 5 6 4
|
4 | 5 ∞ 5 ∞ ∞ 2
|
5 | ∞ 3 6 ∞ ∞ 6
|
6 | ∞ ∞ 4 2 6 ∞
|
->測試圖的遍歷:
->深度優先遍歷結果: 1 2 3 4 6 5
->廣度優先遍歷結果: 1 2 3 4 5 6
->測試插入新頂點
->請輸入頂點的值: 7
->測試插入邊
->插入與新頂點(有向圖或網中的弧尾)有關的弧或邊,請輸入弧或邊數: 2
->請輸入另一頂點(弧頭)的值: 2
請輸入此弧或邊的權值: 10
->請輸入另一頂點(弧頭)的值: 5
請輸入此弧或邊的權值: 15
->插入頂點和弧後圖的鄰接矩陣:
1 2 3 4 5 6 7
+-----------------------------------
1 | ∞ 6 1 5 ∞ ∞ ∞
|
2 | 6 ∞ 5 ∞ 3 ∞ 10
|
3 | 1 5 ∞ 5 6 4 ∞
|
4 | 5 ∞ 5 ∞ ∞ 2 ∞
|
5 | ∞ 3 6 ∞ ∞ 6 15
|
6 | ∞ ∞ 4 2 6 ∞ ∞
|
7 | ∞ 10 ∞ ∞ 15 ∞ ∞
|
->刪除頂點及相關的弧或邊,請輸入頂點的值: 7
->刪除頂點和弧後圖的鄰接矩陣:
1 2 3 4 5 6
+------------------------------
1 | ∞ 6 1 5 ∞ ∞
|
2 | 6 ∞ 5 ∞ 3 ∞
|
3 | 1 5 ∞ 5 6 4
|
4 | 5 ∞ 5 ∞ ∞ 2
|
5 | ∞ 3 6 ∞ ∞ 6
|
6 | ∞ ∞ 4 2 6 ∞
|
->測試銷燬圖: 成功!
演示結束,程序退出!
--------------------------------
Process exited with return value 0
Press any key to continue . . .
總結:就用這樣一幅圖總結吧,雖然不怎麼美觀。
圖變網,加權值,改距離 有向變無向 ,置對稱弧結點
加權值 0,1變 ∞
有向圖 <------------------------------> 有向網
| 去權值 ∞變 0,1 |
| |
去對稱弧結點 | 置對稱弧結點 去對稱弧結點 | 置對稱弧結點
| |
| 加權值 0,1變 ∞ |
無向圖 <------------------------------> 無向網
去權值 ∞變 0,1
下次的文章會介紹圖的鄰接表存儲結構的以及基於這種存儲結構基本操作的實現。感謝大家的關注,再見!
附:循環隊列精簡後的源碼:
源文件:SqQueue.cpp
//廣度優先遍歷用到隊列的實現
#define MAXQSIZE 100 //隊列的最大長度
typedef int Status;
typedef int QElemType;
typedef struct { //循環隊列的C語言描述
QElemType *base; //初始化動態分配存儲空間
int front; //頭指針,若隊列不空,指向隊頭元素
int rear; //尾指針,若隊列不空,指向隊尾元素的下一個位置
}SqQueue;
//----------------------循環隊列的主要操作------------------------
/*
函數:InitQueue_Sq
參數:SqQueue &Q 循環隊列引用
返回值:狀態碼,操作成功返回OK
作用:構建一個空隊列 Q
*/
Status InitQueue_Sq(SqQueue &Q) {
//申請內存空間,若失敗則提示並退出程序
//if(!(Q.base = (QElemType *)malloc(MAXQSIZE * sizeof(QElemType))))
//相當於以下兩行代碼:
//Q.base = (QElemType *)malloc(MAXQSIZE * sizeof(QElemType))
//if(!Q.base) <=> if(Q.base == NULL)
if(!(Q.base = (QElemType *)malloc(MAXQSIZE * sizeof(QElemType)))){
printf("內存分配失敗,程序即將退出!\n");
exit(OVERFLOW);
}//if
//由於剛申請的空間沒有元素入隊,所以隊列爲空
//Q.front == Q.rear是隊列爲空的標誌
Q.front = Q.rear = 0;
//操作成功
return OK;
}//InitQueue_Sq
/*
函數:DestoryQueue_Sq
參數:SqQueue &Q 循環隊列引用
返回值:狀態碼,操作成功返回OK
作用:銷燬循環隊列 Q
*/
Status DestoryQueue_Sq(SqQueue &Q) {
//循環隊列採用連續的存儲空間,直接找到首地址釋放空間即可
if(Q.base) {
free(Q.base);
}//if
//指針置空,釋放掉指針變量本身佔用的空間
Q.base = NULL;
//隊頭和隊尾指針歸零,隊列爲空
Q.front = Q.rear = 0;
//操作成功
return OK;
}//DestoryQueue_Sq
/*
函數:QueueEmpty_Sq
參數:SqQueue Q 循環隊列Q
返回值:狀態碼,隊列爲空返回TRUE,否則返回FALSE
作用:判斷循環隊列 Q是否爲空
*/
Status QueueEmpty_Sq(SqQueue Q) {
//Q.rear == Q.front是隊列爲空的標誌
if(Q.rear == Q.front) {
return TRUE;
}//if
else {
return FALSE;
}//else
}//QueueEmpty_Sq
/*
函數:EnQueue_Sq
參數:SqQueue Q 循環隊列Q
QElemType e 被插入元素e
返回值:狀態碼,操作成功返回OK,否則返回ERROR
作用:插入元素e爲Q的新的隊尾元素e
*/
Status EnQueue_Sq(SqQueue &Q, QElemType e) {
//由於循環隊列使用順序存儲結構,插入時需要判斷隊列是否滿
//判斷隊列滿的標誌:(Q.rear + 1) % MAXQSIZE == Q.front
if((Q.rear + 1) % MAXQSIZE == Q.front) { //隊列滿
//操作失敗
return ERROR;
}//if
//把e插入到隊尾
Q.base[Q.rear] = e;
//每插入一個新隊尾元素,尾指針增一
Q.rear = (Q.rear + 1) % MAXQSIZE;
//操作成功
return OK;
}//EnQueue_Sq
/*
函數:DeQueue_Sq
參數:SqQueue Q 循環隊列Q
QElemType &e 帶回被刪除的元素e
返回值:狀態碼,操作成功返回OK,否則返回ERROR
作用:隊列不空,則刪除Q的隊頭元素,用e返回其值
*/
Status DeQueue_Sq(SqQueue &Q, QElemType &e) {
//在空隊列中執行出隊操作沒有意義,所以要先判斷隊列是否爲空
//if(QueueEmpty_Sq(Q)) <=> if(QueueEmpty_Sq(Q) == TRUE)
if(QueueEmpty_Sq(Q)) { //隊列空
//操作失敗
return ERROR;
}//if
//保存被刪除元素的值
e = Q.base[Q.front];
//每當刪除隊頭元素時,頭指針增1
Q.front = (Q.front + 1) % MAXQSIZE;
//操作成功
return OK;
}//DeQueue_Sq