王道数据结构源码实战ch6图

邻接矩阵结构体定义

typedef struct
{
	int VexNum; //顶点数
	int ArcNum; //边数
    VertexType Vex[MaxVertexNum];   //顶点表
    EdgeType Edge [MaxVertexNum][MaxVertexNum];  //边表   
} MGraph;

邻接表结构体定义

  • 这里为了方便自顶向下的理解,实际写代码时应先写内部最小的结构体,最后再写最大的。

邻接表总的结构体(定点数+边数+头部结构体数组)

typedef struct   //邻接表
{
    int vexnum;  //顶点数
    int edgnum; //边数
    VNode vexs[MAX];  //邻接表的头部组成的数组
} ALGraph;

邻接表的头部节点结构体(顶点信息+链表节点指针)

typedef struct VNode   //邻接表的头部(表的顶点)
{
    char data;                 //存放顶点信息(可能很复杂)
    ArcNode *first_edge;   //指向第一条跟着这个顶点的弧
} VNode;

存放弧的链表节点结构体(指向的顶点+链表节点指针)

typedef struct ArcNode   //邻接表中链表的节点
{
    int adjvex;                  //该弧指向的顶点的下标,好处是节省空间,不需要存储节点的其他复杂信息,就能找到该节点
    struct ArcNode* next_edge;   //指向存放下一条弧的节点的指针
} ArcNode;

创建无向图

  • 可以输入外部数据或使用写死的数据,得到一个顶点表和一个边表

动态分配内存创建图pG,分别初始化顶点数、边数、邻接表的头部节点,邻接表的弧节点(由于是无向图,弧数是边数的两倍,制作节点时,每条边需要分成方向相反的两条弧)

ALGraph* creat_example_algraph()   //创建无向图
{
    //写死的一组数据
    char vexs[]= {'A','B','C','D','E','F','G'};  //7个顶点
    char edges[][2]=
    {
        {'A','C'},
        {'A','D'},
        {'A','F'},
        {'B','C'},
        {'C','D'},
        {'E','G'},
        {'F','G'},
    };
    int vlen=LENGTH(vexs);  //节点数
    int elen=LENGTH(edges);  //边数

    //开始创建图pG的邻接表
    char c1,c2;   //分别存放某条边起点和终点
    int pos1,pos2; //分别存放某条边起点和终点的顶点 下标
    ArcNode* node1,*node2;  //存放边的链表节点
    ALGraph* pG;  //代表图(指向图的指针)

    pG=(ALGraph*)malloc(sizeof(ALGraph));
    //给图pG申请空间
    memset(pG,0,sizeof(ALGraph));   //全部置0

    pG->vexnum=vlen;    //初始化定点数
    pG->edgnum=elen;   //初始化边数

    for(int i=0; i<pG->vexnum; i++) //初始化邻接表的头部
    {
        pG->vexs[i].data=vexs[i];         //放入顶点的信息(非编号)
        pG->vexs[i].first_edge=NULL;  //指针域置空
    }

    for(int i=0; i<pG->edgnum; i++) //初始化邻接表的边
    {
        c1=edges[i][0];   //拿第i条边的起点
        c2=edges[i][1];   //拿第i条边的终点

        pos1=get_position(*pG,c1);   //起点的下标。对pG解引用,所以传入图结构体本身
        pos2=get_position(*pG,c2);   //终点的下标

        //初始化node1
        node1=(ArcNode*)calloc(1,sizeof(ArcNode));
        node1->adjvex=pos2;      //存放边的终点顶点的下标
        //node1->next_edge=NULL    //calloc自动清空内存,并把指针置为NULL,所以指针不用手动置为NULL了

        //因为边的起点是下标为pos1的顶点,所以接在它的头的后面
        if(pG->vexs[pos1].first_edge==NULL)
            pG->vexs[pos1].first_edge=node1;     //邻接表头后面没节点,直接把当前node1接上去
        else
            link_last(pG->vexs[pos1].first_edge,node1);  //否则把当前node1接到这个链表的尾部

        //初始化node2,因为是无向图,所以要反向再最一遍
        node2=(ArcNode*)calloc(1,sizeof(ArcNode));
        node2->adjvex=pos1;
        if(pG->vexs[pos2].first_edge==NULL)
            pG->vexs[pos2].first_edge=node2;
        else
            link_last(pG->vexs[pos2].first_edge,node2);
    }
    return pG;
}

创建有向图

  • 只需要跟据边表里的每条弧的前后顺序,建立邻接表,不需要反方向的弧。
ALGraph* creat_example_algraph_directed()   //创建有向图
{
    //写死的一组数据
    char vexs[]= {'A','B','C','D','E','F','G'};  //7个顶点
    char edges[][2]=
    {
        {'A','C'},
        {'A','D'},
        {'A','F'},
        {'B','C'},
        {'C','D'},
        {'E','G'},
        {'F','G'},
    };
    int vlen=LENGTH(vexs);  //节点数
    int elen=LENGTH(edges);  //边数

    //开始创建图pG的邻接表
    char c1,c2;   //分别存放某条边起点和终点
    int pos1,pos2; //分别存放某条边起点和终点的顶点 下标
    ArcNode* node1,*node2;  //存放边的链表节点
    ALGraph* pG;  //代表图(指向图的指针)

    pG=(ALGraph*)malloc(sizeof(ALGraph));
    //给图pG申请空间
    memset(pG,0,sizeof(ALGraph));   //全部置0

    pG->vexnum=vlen;    //初始化定点数
    pG->edgnum=elen;   //初始化边数

    for(int i=0; i<pG->vexnum; i++) //初始化邻接表的头部
    {
        pG->vexs[i].data=vexs[i];         //放入顶点的信息(非编号)
        pG->vexs[i].first_edge=NULL;  //指针域置空
    }

    for(int i=0; i<pG->edgnum; i++) //初始化邻接表的边
    {
        c1=edges[i][0];   //拿第i条边的起点
        c2=edges[i][1];   //拿第i条边的终点

        pos1=get_position(*pG,c1);   //起点的下标。对pG解引用,所以传入图结构体本身
        pos2=get_position(*pG,c2);   //终点的下标

        //初始化node1
        node1=(ArcNode*)calloc(1,sizeof(ArcNode));
        node1->adjvex=pos2;      //存放边的终点顶点的下标
        //node1->next_edge=NULL    //calloc自动清空内存,并把指针置为NULL,所以指针不用手动置为NULL了

        //因为边的起点是下标为pos1的顶点,所以接在它的头的后面
        if(pG->vexs[pos1].first_edge==NULL)
            pG->vexs[pos1].first_edge=node1;     //邻接表头后面没节点,直接把当前node1接上去
        else
            link_last(pG->vexs[pos1].first_edge,node1);  //否则把当前node1接到这个链表的尾部
    }
    return pG;
}

深度优先遍历(DFS)

  • 为了考虑非连通图的情况,对每个连通分量都进行DFS
    打个比方:在一个小岛上探索完所有点时,直接空降到另一个小岛上,直到探索完所有地方。

0.首先建立一个访问标记数组并置空,表示都没访问过

void DFSTraverse(ALGraph G)  //假如图不是连通的,就需要重新找点多次DFS
{
    //初始化所有顶点都没被访问
    int visited[MAX];
    for(int i=0; i<G.vexnum; i++) //对每个连通分量使用DFS
        visited[i]=0;
    cout<<"DFS:"<<endl;
    for(int i=0; i<G.vexnum; i++)
    {
        if(!visited[i])
            DFS(G,i,visited);
    }
    cout<<endl;
}
  • 对于某个连通分量,进行DFS

1.使用下标为i的点作为遍历起点,首先把这个点标记已访问,输出
2.使用一个工作指针进行遍历,如果当前顶点邻接表后面的第一条弧指向的顶点没被访问过,每次递归的取当前顶点(上一层顶点后面的第一条弧指向的顶点)邻接表后面的第一条弧指向的顶点,一路走到底。
3.无路可走回退到本层时,取本顶点邻接表下一条弧继续,直到没有顶点未被访问。

static void DFS(ALGraph G,int i,int visited[])
{
    ArcNode* node;  //工作指针,指向存放边的链表节点
    visited[i]=1;  //访问标记
    cout<<G.vexs[i].data<<" ";
    node=G.vexs[i].first_edge;  //从当前顶点的后面跟的第一条边开始遍历
    while(node!=NULL)
    {
        if(!visited[i])
            DFS(G,node->adjvex,visited);  //没访问过,深入下一个顶点访问
        node=node->next_edge;      //回退到这一层时,访问这个顶点的后面跟的下一条边
    }
}

广度优先遍历(BFS)

0.首先建立一个访问标记数组并置空,和一个存放顶点的**辅助队列

对于每个连通分量,进行BFS,使用下标为i的点作为遍历起点

1.首先把这个点标记已访问,输出,并入队
2.当队列非空时,做以下事情:
i队头元素出队,依次访问队头元素所有的弧(从邻接表中顶点的第一条弧开始)指向的顶点(3个步骤,同1),并依次把这些顶点入队(访问一个入队一个)。

void BFS(ALGraph G)
{
    int front=0,rear=0;
    int queue[MAX];     //建立辅助队列
    int visited[MAX];      //标技数组
    ArcNode* node;

    for(int i=0; i<G.vexnum; i++) //标记数组置0
        visited[i]=0;
    cout<<"BFS:"<<endl;

    for(int i=0; i<G.vexnum; i++)  //对每个连通分量遍历
    {
        
        if(!visited[i])     //标记打印入队
        {
            visited[i]=1;
            cout<<G.vexs[i].data<<" ";
            queue[rear++]=i;    //尾指针原本指向尾部元素的后面,所以先入队,指针再++
        }

        while(front!=rear)  //队列非空
        {
            int j=queue[front++]; //出队
            node=G.vexs[j].first_edge;   //取刚刚出队的顶点跟着的第一条边
            while(node!=NULL)    //把刚刚出队的顶点的每一条边都访问
            {
                int k=node->adjvex;
                if(!visited[k])     //与第一个顶点的访问完全一样
                {
                    visited[k]=1;
                    cout<<G.vexs[k].data<<" ";
                    queue[rear++]=k;
                }
                node=node->next_edge;  //下一条边
            }
        }
    }
    cout<<endl;
}

完整代码

#include<bits/stdc++.h>
using namespace std;

#define MaxVertexNum 100
#define MAX 100
typedef char VertexType;  //顶点一般用字母表示
typedef int EdgeType;  //边的权值一般为数字

#define isLetter(a)  ((((a)>='a')&&((a)<='z')) || (((a)>='A')&&((a)<='Z')))
#define LENGTH(a)   (sizeof(a)/sizeof(a[0]))    //宏定义

//邻接矩阵结构体定义
typedef struct
{
    int VexNum; //顶点数
    int ArcNum; //边数
    VertexType Vex[MaxVertexNum];   //顶点表
    EdgeType Edge [MaxVertexNum][MaxVertexNum];  //边表
} MGraph;

//邻接表结构体定义
typedef struct ArcNode   //邻接表中链表的节点
{
    int adjvex;                  //该边指向的顶点的下标,好处是节省空间,不需要存储节点的其他复杂信息,就能找到该节点
    struct ArcNode* next_edge;   //指向下一条边的指针
} ArcNode;

typedef struct VNode   //邻接表的头部(表的顶点)
{
    char data;                 //存放顶点信息(可能很复杂)
    ArcNode *first_edge;   //指向第一条跟着这个顶点的边
} VNode;

typedef struct   //邻接表
{
    int vexnum;  //顶点数
    int edgnum; //边数
    VNode vexs[MAX];  //邻接表的头部组成的数组
} ALGraph;

//由图的顶点信息来获得顶点编号,为后续存储邻接表节省空间
static int get_position(ALGraph g,char ch)  //不改变图,所以不需要传指针或者引用
{
    for(int i=0; i<g.vexnum; i++)   //遍历存放邻接表头部的数组
        if(g.vexs[i].data==ch)
            return i;   //返回对应顶点在数组中的下标
    return -1;
}

static void link_last(ArcNode *head,ArcNode*node)  //在邻接表头部顶点head的最后面插入node
{
    ArcNode* p=head;  //用工作指针p遍历
    while(p->next_edge!=NULL)
        p=p->next_edge;
    p->next_edge=node;
}

// 读取一个输入字符
static char read_char()
{
    char ch;
    do
    {
        ch = getchar();
    }
    while(!isLetter(ch));     //先读一个,直到不是字母为止,对比单独的getchar(),可以防止把回车误读入
    return ch;
}

void print_adjacency_table(ALGraph G)  //打印邻接表
{
    ArcNode *node;  //工作指针,找到每个头,后面接的第1个顶点
    cout<<"adjacency table"<<endl;
    for(int i=0; i<G.vexnum; i++)
    {
        printf("%d(%c): ", i, G.vexs[i].data);  //先打印每个顶点的下标号和本身的信息
        node=G.vexs[i].first_edge;
        while(node!=NULL)
        {
            printf("%d(%c) ", node->adjvex, G.vexs[node->adjvex].data);   //打印每条边终点顶点是编号,和本身的信息
            node=node->next_edge;  //向后走
        }
        cout<<endl;
    }
}

//创建邻接表对应的图(自己输入)
ALGraph* create_lgraph()
{
    char c1, c2;
    int v, e;
    int i, p1, p2;
    ArcNode *node1, *node2;
    ALGraph* pG;

    // 输入"顶点数"和"边数"
    printf("input vertex number: ");
    scanf("%d", &v);
    printf("input edge number: ");
    scanf("%d", &e);
    if ( v < 1 || e < 1 || (e > (v * (v-1))))
    {
        printf("input error: invalid parameters!\n");
        return NULL;
    }

    if ((pG=(ALGraph*)malloc(sizeof(ALGraph))) == NULL )
        return NULL;
    memset(pG, 0, sizeof(ALGraph));

    // 初始化"顶点数"和"边数"
    pG->vexnum = v;
    pG->edgnum = e;
    // 初始化"邻接表"的顶点
    for(i=0; i<pG->vexnum; i++)
    {

        printf("vertex(%d): ", i);
        pG->vexs[i].data = read_char();
        pG->vexs[i].first_edge = NULL;
    }

    // 初始化"邻接表"的边
    for(i=0; i<pG->edgnum; i++)
    {
        // 读取边的起始顶点和结束顶点
        printf("edge(%d): ", i);
        c1 = read_char();
        c2 = read_char();

        p1 = get_position(*pG, c1);
        p2 = get_position(*pG, c2);

        // 初始化node1
        node1 = (ArcNode*)calloc(1,sizeof(ArcNode));
        node1->adjvex = p2;
        // 将node1链接到"p1所在链表的末尾"
        if(pG->vexs[p1].first_edge == NULL)
            pG->vexs[p1].first_edge = node1;
        else
            link_last(pG->vexs[p1].first_edge, node1);
        // 初始化node2
        node2 = (ArcNode*)calloc(1,sizeof(ArcNode));
        node2->adjvex = p1;
        // 将node2链接到"p2所在链表的末尾"
        if(pG->vexs[p2].first_edge == NULL)
            pG->vexs[p2].first_edge = node2;
        else
            link_last(pG->vexs[p2].first_edge, node2);
    }

    return pG;
}

ALGraph* creat_example_algraph()   //创建无向图
{
    //写死的一组数据
    char vexs[]= {'A','B','C','D','E','F','G'};  //7个顶点
    char edges[][2]=
    {
        {'A','C'},
        {'A','D'},
        {'A','F'},
        {'B','C'},
        {'C','D'},
        {'E','G'},
        {'F','G'},
    };
    int vlen=LENGTH(vexs);  //节点数
    int elen=LENGTH(edges);  //边数

    //开始创建图pG的邻接表
    char c1,c2;   //分别存放某条边起点和终点
    int pos1,pos2; //分别存放某条边起点和终点的顶点 下标
    ArcNode* node1,*node2;  //存放边的链表节点
    ALGraph* pG;  //代表图(指向图的指针)

    pG=(ALGraph*)malloc(sizeof(ALGraph));
    //给图pG申请空间
    memset(pG,0,sizeof(ALGraph));   //全部置0

    pG->vexnum=vlen;    //初始化定点数
    pG->edgnum=elen;   //初始化边数

    for(int i=0; i<pG->vexnum; i++) //初始化邻接表的头部
    {
        pG->vexs[i].data=vexs[i];         //放入顶点的信息(非编号)
        pG->vexs[i].first_edge=NULL;  //指针域置空
    }

    for(int i=0; i<pG->edgnum; i++) //初始化邻接表的边
    {
        c1=edges[i][0];   //拿第i条边的起点
        c2=edges[i][1];   //拿第i条边的终点

        pos1=get_position(*pG,c1);   //起点的下标。对pG解引用,所以传入图结构体本身
        pos2=get_position(*pG,c2);   //终点的下标

        //初始化node1
        node1=(ArcNode*)calloc(1,sizeof(ArcNode));
        node1->adjvex=pos2;      //存放边的终点顶点的下标
        //node1->next_edge=NULL    //calloc自动清空内存,并把指针置为NULL,所以指针不用手动置为NULL了

        //因为边的起点是下标为pos1的顶点,所以接在它的头的后面
        if(pG->vexs[pos1].first_edge==NULL)
            pG->vexs[pos1].first_edge=node1;     //邻接表头后面没节点,直接把当前node1接上去
        else
            link_last(pG->vexs[pos1].first_edge,node1);  //否则把当前node1接到这个链表的尾部

        //初始化node2,因为是无向图,所以要反向再最一遍
        node2=(ArcNode*)calloc(1,sizeof(ArcNode));
        node2->adjvex=pos1;
        if(pG->vexs[pos2].first_edge==NULL)
            pG->vexs[pos2].first_edge=node2;
        else
            link_last(pG->vexs[pos2].first_edge,node2);
    }
    return pG;
}

ALGraph* creat_example_algraph_directed()   //创建有向图
{
    //写死的一组数据
    char vexs[]= {'A','B','C','D','E','F','G'};  //7个顶点
    char edges[][2]=
    {
        {'A','C'},
        {'A','D'},
        {'A','F'},
        {'B','C'},
        {'C','D'},
        {'E','G'},
        {'F','G'},
    };
    int vlen=LENGTH(vexs);  //节点数
    int elen=LENGTH(edges);  //边数

    //开始创建图pG的邻接表
    char c1,c2;   //分别存放某条边起点和终点
    int pos1,pos2; //分别存放某条边起点和终点的顶点 下标
    ArcNode* node1,*node2;  //存放边的链表节点
    ALGraph* pG;  //代表图(指向图的指针)

    pG=(ALGraph*)malloc(sizeof(ALGraph));
    //给图pG申请空间
    memset(pG,0,sizeof(ALGraph));   //全部置0

    pG->vexnum=vlen;    //初始化定点数
    pG->edgnum=elen;   //初始化边数

    for(int i=0; i<pG->vexnum; i++) //初始化邻接表的头部
    {
        pG->vexs[i].data=vexs[i];         //放入顶点的信息(非编号)
        pG->vexs[i].first_edge=NULL;  //指针域置空
    }

    for(int i=0; i<pG->edgnum; i++) //初始化邻接表的边
    {
        c1=edges[i][0];   //拿第i条边的起点
        c2=edges[i][1];   //拿第i条边的终点

        pos1=get_position(*pG,c1);   //起点的下标。对pG解引用,所以传入图结构体本身
        pos2=get_position(*pG,c2);   //终点的下标

        //初始化node1
        node1=(ArcNode*)calloc(1,sizeof(ArcNode));
        node1->adjvex=pos2;      //存放边的终点顶点的下标
        //node1->next_edge=NULL    //calloc自动清空内存,并把指针置为NULL,所以指针不用手动置为NULL了

        //因为边的起点是下标为pos1的顶点,所以接在它的头的后面
        if(pG->vexs[pos1].first_edge==NULL)
            pG->vexs[pos1].first_edge=node1;     //邻接表头后面没节点,直接把当前node1接上去
        else
            link_last(pG->vexs[pos1].first_edge,node1);  //否则把当前node1接到这个链表的尾部

    }
    return pG;
}


static void DFS(ALGraph G,int i,int visited[])
{
    ArcNode* node;  //工作指针,指向存放边的链表节点
    visited[i]=1;  //访问标记
    cout<<G.vexs[i].data<<" ";
    node=G.vexs[i].first_edge;  //从当前顶点的后面跟的第一条边开始遍历
    while(node!=NULL)
    {
        if(!visited[i])
            DFS(G,node->adjvex,visited);  //没访问过,深入下一个顶点访问
        node=node->next_edge;      //回退到这一层时,访问这个顶点的后面跟的下一条边
    }
}

void DFSTraverse(ALGraph G)  //假如图不是连通的,就需要重新找点多次DFS
{
    //初始化所有顶点都没被访问
    int visited[MAX];
    for(int i=0; i<G.vexnum; i++) //对每个连通分量使用DFS
        visited[i]=0;
    cout<<"DFS:"<<endl;
    for(int i=0; i<G.vexnum; i++)
    {
        if(!visited[i])
            DFS(G,i,visited);
    }
    cout<<endl;
}

void BFS(ALGraph G)
{
    int front=0,rear=0;
    int queue[MAX];     //建立辅助队列
    int visited[MAX];      //标技数组
    ArcNode* node;

    for(int i=0; i<G.vexnum; i++) //标记数组置0
        visited[i]=0;
    cout<<"BFS:"<<endl;

    for(int i=0; i<G.vexnum; i++)  //对每个连通分量遍历
    {
        
        if(!visited[i])     //标记打印入队
        {
            visited[i]=1;
            cout<<G.vexs[i].data<<" ";
            queue[rear++]=i;    //尾指针原本指向尾部元素的后面,所以先入队,指针再++
        }

        while(front!=rear)  //队列非空
        {
            int j=queue[front++]; //出队
            node=G.vexs[j].first_edge;   //取刚刚出队的顶点跟着的第一条边
            while(node!=NULL)    //把刚刚出队的顶点的每一条边都访问
            {
                int k=node->adjvex;
                if(!visited[k])     //与第一个顶点的访问完全一样
                {
                    visited[k]=1;
                    cout<<G.vexs[k].data<<" ";
                    queue[rear++]=k;
                }
                node=node->next_edge;  //下一条边
            }
        }
    }
    cout<<endl;
}

int main()
{
//打印有向图的邻接表
    cout<<"Undirected graph"<<endl;
    ALGraph *pG2;
    pG2=creat_example_algraph_directed();
    print_adjacency_table(*pG2);
    cout<<endl;

//打印无向图的邻接表
    cout<<"Directed graph"<<endl;
    ALGraph *pG1;
    pG1=creat_example_algraph();
    print_adjacency_table(*pG1);
    //遍历
    DFSTraverse(*pG1);
    BFS(*pG1);
    cout<<endl;

    //无向图读入外部数据测试
    /*
    7
    7
    A
    B
    C
    D
    E
    F
    G
    A
    C
    A
    D
    A
    F
    B
    C
    C
    D
    E
    G
    F
    G
    */
    cout<<"Undirected graph"<<endl;
    ALGraph *pG3;
    pG3=create_lgraph();
    print_adjacency_table(*pG3);
    DFSTraverse(*pG3);
    BFS(*pG3);

    return 0;
}

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WYF19999

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值