如果您對圖的定義尚不清楚,可以點此查看關於圖的定義和描述。
我們在這里討論的圖的接口有11個,涉及到8個函數接口和3個宏定義。
函數接口包括初始化圖、銷毀圖、插入頂點、插入邊、移除頂點、移除邊、取出頂點鄰接表、判斷頂點是否鄰接;宏接口包括返回鄰接表的結構鏈表、返回頂點個數、返回邊個數。
graph_init
void graph_init(Graph *graph, int (*match)(const void *key1,const void *key2), void (*destroy)(void *data));
返回值 :無
描述:初始化由參數gragh指定的圖。該函數必須在執行其他與圖相關的操作之前調用。
match參數指定用來判斷兩個頂點是否匹配的函數,該函數會用在其他的圖操作中。當key1=key2時,match函數返回1;否則返回0。
destroy參數所指定的函數提供一種釋放動態分配數據空間的方法。比如,如果圖包含由malloc動態分配的空間,則destroy應該設置為free,當銷毀圖時以此來釋放動態分配的內存。對於包含多個動態分配成員的結構化數據,destroy參數應該設置為一個用戶定義的析構函數,用來對每一個動態分配的成員以及結構體自身做資源回收操作。如果圖不包含需要動態釋放空間的數據,destroy參數應該設置為NULL。
復雜度:O(1)
graph_destroy
void graph_destroy(Graph *graph);
返回值:無
描述:銷毀由參數graph所指定的圖。該函數調用后,任何其他的圖操作都不允許再執行,除非再次初始化。
graph_destroy將圖中所有頂點和邊都移除,如果destroy參數不為NULL的話,則調用destroy參數所指定的析構函數針對每個移除的頂點和邊做資源回收操作。
復雜度:O(V+E),這里V是圖中頂點個數,E是邊的個數。
graph_ins_vertex
int graph_ins_vertex(Graph *graph, const void *data);
返回值:如果插入頂點成功則返回0,如果頂點已經存在則返回1,否則返回-1。
描述:將頂點插入由參數graph所指定的圖中。
新頂點包含一個指向data的指針,因此只要頂點還在圖中,data所引用的內存就必須保持有效。由調用者管理data所引用的存儲空間。
復雜度:O(V),這里V代表圖中的頂點個數。
graph_ins_edge
int graph_ins_edge(Graph *graph, const void *data1, const void *data2);
返回值:如果插入邊成功,返回0;如果邊已經存在,返回1;否則返回-1;
描述:將由data1以及data2所指定的頂點構成的邊插入圖中。data1和data2必須是使用graph_ins_vertex已經插入圖中的頂點。新的邊由data1所指定的頂點的鄰接表中指向data2的指針來表示。因此,只要這條邊還在圖中,data2所引用的內存就必須合法。由調用者負責維護data2所引用的內存空間。
要在無向圖中插入邊(u,v),需要調用該函數兩次:第一次將插入由u到v的邊,第二次插入由v到u的邊。這種類型的表示法對於無向圖來說很普遍。
復雜度:O(V),這里V代表圖中的頂點個數。
graph_rem_vertex
int graph_rem_vertex(Graph *graph,void **data);
返回值:如果移除頂點成功,返回0,否則返回-1。
描述:從graph指定的圖中移除與data相匹配的頂點。在調用該函數之前,所有與該頂點相關的邊都必須移除。函數返回后,data指向已移除頂點中保存的數據。由調用者負責管理data所引用的存儲空間。
復雜度:O(V+E),這里V代表圖中頂點個數,而E代表邊的個數。
graph_rem_edge
int graph_rem_edge(Graph *graph, const void *data1, void **data2);
返回值:如果移除成功,返回0;否則返回-1。
描述:從graph指定的圖中移除從data1到data2的邊。函數返回后,data2指向由data1所指定的頂點的鄰接表中保存的數據。由調用者管理data2所引用的存儲空間。
復雜度:O(V),這里V代表圖中的頂點個數。
graph_adjlist
int graph_adjlist(const Graph *graph, const void *data, AdjList **adjlist);
返回值:如果取得鄰接表成功,返回0;否則返回-1。
描述:取出graph中由data所指定的頂點的鄰接表。返回的鄰接表以結構體AdjList的形式保存。
該結構體包含與data相匹配的頂點,以及其他與其鄰接的頂點。函數返回后得到一個指向鄰接表的指針,因此調用者必須保證不修改該鄰接表中的數據,否則將破壞原始圖中的數據。
復雜度:O(V),這里V代表圖中的頂點的個數。
graph_is_adjacent
int graph_is_adjacent(const Graph *graph, const void *data1, const void *data2);
返回值:如果第二個頂點與第一個頂點鄰接,返回1;否則返回0。
描述:判斷由data2所指定的頂點是否與graph中由data1所指定的頂點鄰接。
復雜度:O(V),這里V代表圖中的頂點個數。
graph_adjlists
List graph_adjlists(const Graph *graph);
返回值:由鄰接表結構所組成的鏈表。
描述:這是一個宏,用來返回由參數graph所指定的圖中的鄰接表結構鏈表。
該鏈表中的每一個元素都是AdjList結構體。由於返回的是圖中的鄰接表結構鏈表而不是其拷貝,因此用戶必須保證不對該鏈表做其他操作。
復雜度:O(1)
graph_vcount
int graph_vcount(const Graph *graph);
返回值:圖中頂點的個數。
描述:這是一個宏,用來返回graph所指定的圖中的頂點的個數。
復雜度:O(1)
graph_ecount
int graph_ecount(const Graph *graph);
返回值:圖中邊的個數。
描述:這是一個宏,用來計算graph指定的圖中邊的個數。
復雜度:O(1)
圖的實現代碼分析
我們通過鄰接表鏈表結構來表示圖。
鏈表中的每個結構體都包含兩個成員:一個頂點,以及與該頂點相鄰接的頂點的集合。鏈表中的結點由結構體AdjList表示。
圖的數據結構使用Graph代表。這個結構體包含5個成員:vcount代表圖中的頂點個數;ecount代表圖中邊的個數;match、destroy用來封裝初始化時傳遞給graph_init的函數;adjlists代表鄰接表鏈表。
我們為頂點的顏色屬性定義枚舉類型,這些顏色屬性在圖的操作中非常常用。
示例1:圖抽象數據類型的頭文件
/*graph.h*/ #ifndef GRAPH_H #define GRAPH_H #include <stdlib.h> #include "list.h" #include "set.h" /*為鄰接表鏈表定義成員結構體*/ typedef struct AdjList_ { void *vertex; set adjacent; }AdjList; /*為圖定義數據結構*/ typedef struct Graph_ { int vcount; int ecount; int (*match)(const void *key1,const void *key2); void (*destroy)(void *data); List adjlist; }Graph; /*使用枚舉類型來定義顏色屬性*/ typedef enum VertexColor_{white,gray,black} VertexColor; /*圖的接口部分*/ void graph_init(Graph *graph, int(*match)(const void *key1,const void *key2),void (*destroy)(void *data)); void graph_destroy(Graph *graph); int graph_ins_vertex(Graph *graph,const void *data); int graph_ins_edge(Graph *graph,const void *data1,const void *data2); int graph_rem_vertex(Graph *graph,void **data); int graph_rem_edge(Graph *graph,void *data1,void **data2); int graph_adjlist(const Graph *graph,const void *data,AdjList **adjlist); int graph_is_adjacent(const Graph *graph,const void *data1,const void *data2); #define graph_adjlists(graph)((graph)->adjlists) #define graph_vcount(graph)((graph)->vcount) #define graph_ecount(graph)((graph)->ecount) #endif // GRAPH_H
示例2:抽象數據類型圖的實現
/*graph.c*/ #include <stdlib.h> #include <string.h> #include "graph.h" #include "list.h" #include "set.h" /*graph_init 初始化圖*/ void graph_init(Graph *graph,int (*match)(const void *key1,const void key2),void (*destroy)(void *data)) { /*初始化圖結構*/ graph->vcount = 0; graph->ecount = 0; graph->match = match; graph->destroy = destroy; /*初始化鄰接表鏈表結構*/ list_init(&graph->adjlists,NULL); return; } /*graph_destroy 銷毀圖結構*/ void graph_destroy(Graph *graph) { AdjList *adjlist; /*刪除每一個鄰接表結構並將其銷毀*/ while(list_size(&graph->lists)>0) { if(list_rem_next(&graph->adjlists,NULL,(void **)&adjlist)==0) { /*移除頂點集合*/ set_destroy(&adjlist->adjacent); /*釋放頂點成員的內存空間*/ if(graph->destroy != NULL) graph->destroy(adjlist->vertex); /*釋放鄰接表結構*/ free(adjlist); } } /*銷毀鄰接表鏈表結構*/ list_destroy(&graph->adjlists); /*作為預防措施清理數據結構*/ memset(graph,0,sizeof(Graph)); return; } /*graph_ins_vertex 將頂點插入圖中*/ /*該函數會將一個AdjList結構體插入鄰接表鏈表結構中,並將AdjList結構體中的vertex成員指向由調用者傳入的數據*/ int graph_ins_vertex(Graph *graph,const void *data) { ListElmt *element; AdjList *adjlist; int retval; /*首先,要確保頂點不存在於列表中,如果頂點已經存在則返回1*/ for(element=list_head(&graph->adjlists); element != NULL; element=list_next(element)) { if(graph->match(data,((AdjList*)list_data(element))->vertex)) return 1; } /*插入頂點,為鄰接表分配空間*/ if((adjlist = (AdjList*)malloc(sizeof(AdjList)))==NULL) return -1; /*將adjlist結構中的成員指向傳入的數據data*/ adjlist->vertex=(void*)data; /*初始化一個頂點的鄰接頂點集合*/ set_init(&adjlist->adjacent,graph->match,NULL); /*調用list_ins_next()將結構體插入到鄰接表鏈表中*/ if((retval = list_ins_next(&graph->adjlists,list_tail(&graph->adjlists),adjlist))!=0) { return retval; } /*調整圖中的頂點統計數據*/ graph->vcount++; return 0; } /*graph_ins_edge 將一條邊插入圖中*/ /*插入從data1到data2的邊,即將data2插入到data1的鄰接表中*/ int graph_ins_edge(Graph *graph,const void *data1,const void *data2) { ListElmt *element; int retval; /*首先,要保證這兩個頂點都存在於圖中*/ for(element = list_head(&graph->adjlists); element != NULL; element = list_next(element)) { if(graph->match(data2,((AdjList *)list_data(element))->vertex)) break; } if(element == NULL) return -1; for(element = list_head(&graph->adjlists); element != NULL; element = list_next(element)) { if(graph->match(data2,((AdjList *)list_data(element))->vertex)) break; } if(element == NULL) return -1; /*將data2的頂點插入到data1的鄰接頂點集合中*/ if((retval = set_insert(&((AdjList *)list_data(element))->adjacent,data2)) !=0 ) { return retval; } /*調整圖中圖的統計數量*/ graph->ecount++; return 0; } /*graph_rem_vertex 將頂點從圖中移除*/ /*該函數將一個AdjList結構體從鄰接表結構鏈表中移除*/ int graph_rem_vertex(Graph *graph,void **data) { ListElmt *element, *temp, *prev; AdjList *adjlist; int found; /*首先確保頂點不存在於任何鄰接表中,但頂點要存在於鄰接表結構鏈表里,且它的鄰接表為空*/ prev=NULL; found=0; for(element = list_head(&graph->adjlists); element != NULL; element = list_next(element)) { /*不允許移除鄰接表中的頂點*/ if(set_is_member(&((AdjList *)list_data(element))->adjacent,*data)) return -1; /*找到將要移除的頂點,標記found=1,並使指針temp指向頂點*/ if(graph->match(*data,((AdjList *)list_data(element))->vertex)) { temp=element; found=1; } /*使prev指向目標元素之前的元素*/ if(!found) prev = element; } /*如果未找到要移除的頂點,返回*/ if(!found) return -1; /*如果頂點的鄰接表不為空,返回*/ if(set_size(&((AdjList *)list_data(temp))->adjacent)>0) return -1; /*滿足移除條件,移除頂點*/ if(list_rem_next(&graph->adjlists,prev,(void **)&adjlist) != 0) return -1; /*使*data指向被移除的頂點,釋放鄰接表數據結構的空間*/ *data=adjlist->vertex; free(adjlist); /*調整圖中的頂點統計數*/ graph->vcount--; return 0; } /*graph_rem_edge 將一條邊從圖中移除*/ /*該函數將由data2指定的頂點從data1所指定的頂點的鄰接表中移除*/ int graph_rem_edge(Graph *graph, void *data1, void **data2) { ListElmt *element; /*首先,確保頂點1存在於圖中*/ for(element = list_head(&graph->adjlists); element != NULL; element = list_next(element)) { if(graph->match(data1,((AdjList *)list_data(element))->vertex)) break; } if(element==NULL) return -1; /*通過驗證,調用set_remove()來將data2從data1的鄰接表中移除*/ if(set_remove(&((AdjList *)list_data(element))->adjacent,data2)!=0) return -1; /*最后調整圖中邊的數量*/ graph->ecount--; return 0; } /*graph->adjlist 返回一個AdjList結構體*/ /*返回一個AdjList結構體,其中包含指定頂點的鄰接頂點集合*/ int graph_adjlist(const Graph *graph, const void *data, AdjList **adjlist) { ListElmt *element,*prev; /*找到包含頂點的鏈表元素*/ prev = NULL; for(element = list_head(&graph->adjlists); element != NULL; element = list_next(element)) { if(graph->match(data,((AdjList *)list_data(element))->vertex)) break; prev = element; } if(element == NULL) return -1; /*返回的鄰接表保存到*adjlist*/ *adjlist = list_data(element); return 0; } /*graph_is_adjacent 判斷兩個頂點是否有鄰接關系*/ int graph_is_adjacent(const Graph *graph, const void *data1, const void *data2) { ListElmt *element, *prev; /*首先,在鄰接表鏈表結構中定位data1所指定的頂點*/ prev=NULL; for(element = list_head(&graph->adjlists); element != NULL; element = list_next(element)) { if(graph->match(data1,((AdjList *)list_data(element))->vertex)) break; prev=element; } if(element==NULL) return 0; /*調用set_is_member來查看data2是否存在於data1的鄰接頂點集合中*/ return set_is_member(&((AdjList *)list_data(element))->adjacent,data2); }