图的基本操作——建立、深度和广度优先遍历

图的基本操作——以邻接表为存储结构的图的建立、遍历

由于笔者处于初学者学习阶段,这里记录的都是学习过程中通过查阅资料和自己的理解亲自实现的算法,记录于此以备以后查阅,希望这篇文章能对你有所帮助,同时由于水平有限,难免有不足之处,希望大家多多批评指教。
首先,通俗的来讲,图就是一些顶点由边相互连接,相互联系,根据不同的需要可以讲这些定点和边赋予特定的含义,从而解决特定的问题。如果深入学习数据结构,会发现它非常复杂,这里我们从最基本的开始学习与实现。

有向图连通图的建立

首先,声明部分如下:

///图的邻接表存储结构声明
#define MAXV 10000
typedef char ElemType;
typedef int InfoType;
typedef struct ANode///边的节点结构类型
{
    int adjvex; ///边的终点位置,是数组下标;
    struct ANode *nextarc;///指向下一条边的指针;
    InfoType info;///边的相关信息,对于带权图可以存放权值;
}ArcNode;

typedef struct Vnode///邻接表头结点的类型;
{
    ElemType data;///顶点信息;(就是边的起点)
    ArcNode *firstarc;///指向第一条边;
}VNode;
//typedef VNode AdjList[MAXV];///AdjList是邻接表类型;
typedef struct
{
    VNode adjlist[MAXV];///邻接表的节点类型;
    int n,e;///分别表示图的顶点数和边数;
}AGraph;///图的邻接表类型;

为了便于理解,读者看此声明可以从下向上看,AGraph是图的结构类型,它包括了图的所有节点组成的数组和图的顶点数、边数,其中,图的节点数组中的每一个变量都代表表头节点,这个表头结点即是结构体VNode,它包括顶点的内容和指向它所连接的边表节点的指针(即代表一条边),这个边表节点的即是ArcNode,它又包括边表节点的数组下标(笔者刚开始实现这个算法的时候,觉着为什么要写数组下标呢,直接写指向的节点的内容不就好了?其实仔细思考就会发现,如果这是一个没重复节点的图,这样完全可以,但要是有重复节点,那这条边的终点是此是彼?当然还有更大的问题——影响遍历的实现),如果是有权图,还要有一个存放权值的变量,这样图的声明就结束了。
如果真正从心里理解了图的邻接表存储结构,那么实现起来只是时间问题了(大多数开发软件都有变量 函数联想功能,读者几乎不必担心拼写错误或者忘词,只是如果想要输出漂亮的格式,要稍微在回车等字符的处理上下一点功夫)。
创建和输出函数的实现如下:

int LocateV(AGraph g,char x)
{
    int i;
    for(i=0;i<g.n;i++)
    {
        if(g.adjlist[i].data==x) return i;
    }
    return -1;
}

void CreateG(AGraph *g)///建立邻接表为存储结构的图;
{
    int i,j=0,k=0;
    ElemType v1,v2;
    ArcNode *s;
    printf("输入图的节点的个数:\n");
    scanf("%d",&g->n);
    printf("输入节点信息:\n");
    getchar();
    for(i=0;i<g->n;i++)
    {
        scanf("%c",&g->adjlist[i].data);
        getchar();
        g->adjlist[i].firstarc=NULL;///输入的同时将表头节点的指针置空;
    }
    printf("输入边的条数:\n");
    scanf("%d",&g->e);
    printf("输入边的信息,包括起点和终点:\n");
    getchar();
    for(i=0;i<g->e;i++)
    {
        scanf("%c %c",&v1,&v2);
        getchar();
        //printf("v1=%c,v2=%c\n",v1,v2);
        k=LocateV(*g,v1);
        j=LocateV(*g,v2);
        //printf("k=%d,j=%d\n",k,j);
        if(k>=0&&j>=0)
        {
            s=new ArcNode;
            s->adjvex=j;
            s->nextarc=g->adjlist[k].firstarc;
            g->adjlist[k].firstarc=s;
        }
    }
}

void PrintG(AGraph g)///打印图的邻接表结构;
{
    ArcNode *p;
    for(int i=0;i<g.n;i++)
    {
        printf("\n%c",g.adjlist[i].data);
        p=g.adjlist[i].firstarc;
        while(p!=NULL)
        {
            printf("-->%d",p->adjvex);
            p=p->nextarc;
        }
    }
    printf("\n");
}

这里的得LocateV函数是一个返回给定顶点值的下标的函数,还有一点是插入边表节点的时候使用的是头插法最为便捷,如果非要使用尾插法,必须另设指针记忆当前最后一个节点的位置。

深度优先遍历

深度优先遍历,顾名思义,就是从一个节点出发,随机找一个与它邻接且未被访问的节点进行访问(个人感觉就是一条路走到黑,到头了就后退,找其他未访问过的节点,再一条路走到黑。。。)话不多说,贴代码:

///深度优先遍历(递归算法)
int visited[MAXV]={0};///访问标记数组,初始置零表示所有元素均未被访问;
void DFS(AGraph *g,int v)///v是初始顶点编号;
{
    ArcNode *p;
    visited[v]=1;
    printf("%d%c; ",v,g->adjlist[v].data);
    p=g->adjlist[v].firstarc;
    while(p!=NULL)
    {
        if(visited[p->adjvex]==0)
            DFS(g,p->adjvex);///若顶点未被访问,则访问它;
        p=p->nextarc;
    }
}

///深度优先遍历非递归算法一

void DFS1(AGraph *g,int v)
{
    int visit[MAXV],i,j;
    int St[MAXV],top=-1;
    ArcNode *p;
    for(i=0;i<g->n;i++)
        visit[i]=0;
    top++;
    St[top]=v;
    visit[v]=1;///初始节点进栈;
    while(top>-1)
    {
        j=St[top];
        top--;
        printf("%d%c; ",j,g->adjlist[j].data);
        p=g->adjlist[j].firstarc;///p指向当前节点的第一个邻接点;
        while(p!=NULL)
        {
            if(visit[p->adjvex]==0)
            {
                top++;
                St[top]=p->adjvex;
                visit[p->adjvex]=1;
            }
            p=p->nextarc;
        }///根据栈的先进后出原理,此循环结束后,第一个出栈的应该是当前节点的最后一个邻接点;
        /*所有邻接点都进栈之后再逐个出栈输出*/
    }
    printf("\n");
}

///深度优先遍历非递归算法二
void DFS2(AGraph *g,int v)
{
    ArcNode *p;
    ArcNode *St[MAXV];
    int visit[MAXV];
    int top=-1,w,i;
    for(i=0;i<g->n;i++)
        visit[i]=0;
    printf("%d%c; ",v,g->adjlist[v].data);
    visit[v]=1;
    top++;
    St[top]=g->adjlist[v].firstarc;///第一个邻接点进栈;
    while(top>-1)
    {
        p=St[top];
        top--;
        while(p!=NULL)
        {
            w=p->adjvex;///该邻接点的下标为w;
            if(visit[w]==0)///如果该节点未被访问,输出该节点,并将其下一个邻接点进栈;
            {
                printf("%d%c; ",w,g->adjlist[w].data);
                visit[w]=1;
                top++;
                St[top]=g->adjlist[w].firstarc;
                break;///每进一个栈,都直接输出,这是与上面深度非递归算法一的主要区别;
            }
            p=p->nextarc;
        }
    }
    printf("\n");
}

如果理解递归的含义,按照一条路走到黑的思想,写出递归算法很简单,不再赘述。至于非递归算法一和二,算法区别在于处理邻接点的顺序,对于第一种算法,是所有邻接点进栈后,根据先进后出的原理,从最后一个进栈的邻接点出栈、输出、将其邻接点进栈,以此类推。第二个算法则是一进栈则出栈、输出、邻接点进栈,以此类推。可以知道,算法二的输出应该和递归算法的输出相同。

广度优先遍历

广度优先遍历就是从初始节点出发,遍历其所有邻接点,再遍历邻接点的所有邻接点嗒嗒嗒哒哒

///广度优先遍历算法
void BFS(AGraph *g,int v)
{
    ArcNode *p;
    int Qu[MAXV];
    int Front=0,Rear=0;
    int visit[MAXV];
    int w,i;
    for(i=0;i<g->n;i++)
        visit[i]=0;
    printf("%d%c; ",v,g->adjlist[v].data);
    visit[v]=1;
    Rear=(Rear+1)%MAXV;
    Qu[Rear]=v;///已访问的节点进队;
    while(Front!=Rear)
    {
        Front=(Front+1)%MAXV;
        w=Qu[Front];
        p=g->adjlist[w].firstarc;
        while(p!=NULL)
        {
            if(visit[p->adjvex]==0)///若当前邻接点未被访问;
                {
                    printf("%d%c; ",p->adjvex,g->adjlist[p->adjvex].data);///输出
                    visit[p->adjvex]=1;///标记已访问;
                    Rear=(Rear+1)%MAXV;
                    Qu[Rear]=p->adjvex;///进队
                }
                p=p->nextarc;
        }
    }
    printf("\n");
}

这就是所有算法啦,主函数如下:

int main()
{
    AGraph G;
    CreateG(&G);
    printf("图的邻接表结构为:\n");
    PrintG(G);
    printf("深度优先遍历递归算法的输出:\n");
    DFS(&G,0);
    printf("\n");
    printf("深度优先遍历非递归算法一的输出:\n");
    DFS1(&G,0);
    printf("深度优先遍历非递归算法二的输出:\n");
    DFS2(&G,0);
    printf("广度优先遍历算法的输出:\n");
    BFS(&G,0);
    return 0;
}

运行结果如下:
《图的基本操作——建立、深度和广度优先遍历》
建立的图是这样的:
《图的基本操作——建立、深度和广度优先遍历》
今天的整理就到这里啦,还是那句话,看到这篇文章的希望你能有所收获,不足之处也要多多指教。

    原文作者:数据结构之图
    原文地址: https://blog.csdn.net/Reborn_Yu/article/details/84758135
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞