數據結構 圖的接口定義與實現分析(超詳細註解)

若是您對圖的定義尚不清楚,能夠點此查看關於圖的定義和描述html

咱們在這裏討論的圖的接口有11個,涉及到8個函數接口和3個宏定義。數據結構

函數接口包括初始化圖、銷燬圖、插入頂點、插入邊、移除頂點、移除邊、取出頂點鄰接表、判斷頂點是否鄰接;宏接口包括返回鄰接表的結構鏈表、返回頂點個數、返回邊個數。函數

graph_init


void graph_init(Graph *graph, int (*match)(const void *key1,const void *key2), void (*destroy)(void *data));spa

返回值 :無指針

描述初始化由參數gragh指定的圖。該函數必須在執行其餘與圖相關的操做以前調用。code

match參數指定用來判斷兩個頂點是否匹配的函數,該函數會用在其餘的圖操做中。當key1=key2時,match函數返回1;不然返回0。htm

destroy參數所指定的函數提供一種釋放動態分配數據空間的方法。好比,若是圖包含由malloc動態分配的空間,則destroy應該設置爲free,當銷燬圖時以此來釋放動態分配的內存。對於包含多個動態分配成員的結構化數據,destroy參數應該設置爲一個用戶定義的析構函數,用來對每個動態分配的成員以及結構體自身作資源回收操做。若是圖不包含須要動態釋放空間的數據,destroy參數應該設置爲NULL。blog

複雜度: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);
}
相關文章
相關標籤/搜索