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


如果您對圖的定義尚不清楚,可以點此查看關於圖的定義和描述

我們在這里討論的圖的接口有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);
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM