数据结构与算法 - 图

初识图

图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成。通常表示为 G[V,E] 其中G表示一个图,V是图G中的顶点集合,E是图G中边的集合。

上述是一个普通图,其中顶点与顶点之间的连线是没有方向的也就是互相连通的,这个是无向图,当然我们还有有向图:

图中所示顶点与顶点之间是有方向的。

图的存储思考

大概了解了图的展示形态,那么问题来了,如果我们想把这种结构存储到计算机中,该怎么办:如何去设计顶点的数据结构呢?节点与节点如何关联呢?

不卖关子,直接上菜:邻接矩阵邻接表 这两种对应存储结构中的顺序存储和链式存储。

邻接矩阵

由于图的顶点是随机的,所以我们需要标记任意两个顶点之间是否有连线(有则标记1无则0),打个比方,如上图我们知道V0-V1是连通的 我们标记 arc [0] [1] = 1 相反V1和V3没有连通 arc [1] [3] = 0 所以其实其本质是一个二维数组,用矩阵表示是这样:

可以看出对角线的数值都是 0,因为自己是不能指向自己的。

这里我们是用0和1标记任意两个顶点是否连通,如果顶点与顶点之间是有向的,而且他们之间的连线是有权值的(你就当距离的概念理解好了),这个时候我们如何去设计呢?

这里我们就不能用0了,因为权值可以为0,如果两个顶点之间无法达到我们标记他们之间的值为无穷大∞ ,无穷大当然到不了啦…

基于上述我们设计出邻接矩阵存储的数据结构:

#define MAXVEX 100 /* 最大顶点数,应由用户定义 */
#define INFINITYC 0

typedef int Status;    /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef char VertexType; /* 顶点类型应由用户定义  */
typedef int EdgeType; /* 边上的权值类型应由用户定义 */
typedef struct
{

    VertexType vexs[MAXVEX]; /* 顶点表 */
    EdgeType arc[MAXVEX][MAXVEX];/* 邻接矩阵,可看作边表 */
    int numNodes, numEdges; /* 图中当前的顶点数和边数  */
}MGraph;

使用邻接矩阵实现一个图

void CreateMGraph(MGraph *G){

    int i,j,k,w;
    printf("输入顶点数和边数:\n");
    //1. 输入顶点数/边数
    scanf("%d,%d",&G->numNodes,&G->numEdges);
    printf("顶点数:%d,边数:%d\n",G->numNodes,G->numEdges);

    //2.输入顶点信息/顶点表
    for(i = 0; i<= G->numNodes;i++)
        scanf("%c",&G->vexs[i]);

    //3.初始化邻接矩阵
    for(i = 0; i G->numNodes;i++)
         for(j = 0; j G->numNodes;j++)
             G->arc[i][j] = INFINITYC;

    //4.输入边表信息
    for(k = 0; k G->numEdges;k++){
        printf("输入边(vi,vj)上的下标i,下标j,权w\n");
        scanf("%d,%d,%d",&i,&j,&w);

        G->arc[i][j] = w;
        //如果无向图,矩阵对称;
        G->arc[j][i] = G->arc[i][j];

    }
    /*5.打印邻接矩阵*/
    for (int i = 0; i G->numNodes; i++) {
        printf("\n");
        for (int j = 0; j G->numNodes; j++) {
            printf("%d ",G->arc[i][j]);
        }
    }
    printf("\n");
}

邻接表

任何一种数据结构既可以使用顺序存储,当然也可以使用链式存储,图也是,图的链式存储一般称作 邻接表 其实也是单链表的一种体现。

同时是这个图我们看看如何去实现它的链式存储:

注意这里每条单链表的意思不是说V0指向V1然后V1指向V2,而是所有的后继元素都是被V0指向的,也就是V0指向V1,V2,V3,不知你是否理解。

邻接表的数据结构:

//邻接表的节点
typedef struct Node{
    int adj_vex_index;  //弧头的下标,也就是被指向的下标
    Element data;       //权重值
    struct Node * next; //边指针
}EdgeNode;

//顶点节点表
typedef struct vNode{
    Element data;          //顶点的权值
    EdgeNode * firstedge;  //顶点下一个是谁?
}VertexNode, Adjlist[M];

//总图的一些信息
typedef struct Graph{
    Adjlist adjlist;       //顶点表
    int arc_num;           //边的个数
    int node_num;          //节点个数
    BOOL is_directed;      //是不是有向图
}Graph, *GraphLink;

使用邻接表实现一个图

void creatGraph(GraphLink *g){
    int i,j,k;
    EdgeNode *p;

    //1. 顶点,边,是否有向
    printf("输入顶点数目,边数和有向?:\n");
    scanf("%d %d %d", &(*g)->node_num, &(*g)->arc_num, &(*g)->is_directed);

    //2.顶点表
     printf("输入顶点信息:\n");
    for (i = 0; i < (*g)->node_num; i++) {
        getchar();
        scanf("%c", &(*g)->adjlist[i].data);
        (*g)->adjlist[i].firstedge = NULL;
    }

    //3.
    printf("输入边信息:\n");
    for (k = 0; k < (*g)->arc_num; k++){
        getchar();
        scanf("%d %d", &i, &j);

        //①新建一个节点
        p = (EdgeNode *)malloc(sizeof(EdgeNode));
        //②弧头的下标
        p->adj_vex_index = j;
        //③头插法插进去,插的时候要找到弧尾,那就是顶点数组的下标i
        p->next = (*g)->adjlist[i].firstedge;
        //④将顶点数组[i].firstedge 设置为p
        (*g)->adjlist[i].firstedge = p;

        //j->i
        if(!(*g)->is_directed)
        {
            // j -----> i
            //①新建一个节点
            p = (EdgeNode *)malloc(sizeof(EdgeNode));
            //②弧头的下标i
            p->adj_vex_index = i;
            //③头插法插进去,插的时候要找到弧尾,那就是顶点数组的下标i
            p->next = (*g)->adjlist[j].firstedge;
            //④将顶点数组[i].firstedge 设置为p
            (*g)->adjlist[j].firstedge = p;
        }
    }
}

void putGraph(GraphLink g){
    int i;
    printf("邻接表中存储信息:\n");
    //遍历一遍顶点坐标,每个再进去走一次
    for (i = 0; i < g->node_num; i++) {
        EdgeNode * p = g->adjlist[i].firstedge;
        while (p) {
            printf("%c->%c ", g->adjlist[i].data, g->adjlist[p->adj_vex_index].data);
            p = p->next;
        }
        printf("\n");
    }
}

深度优先搜索

图的深度优先遍历和树的先序遍历比较类似。

它的思想是:

  1. 任意选择一个顶点作为起始点。
  2. 若当前访问的顶点的邻接顶点有未被访问的,任选一个访问之;反之退回到最近访问过的顶点;直到与起始顶点想通的全部顶点都访问完毕。
  3. 若此时图中尚有顶点未被访问,则再选其中一个顶点作为起始顶点并访问之,转2;反之遍历结束。

显然深度优先搜索是一个递归的过程。

我们按照思路对上图进行遍历,首先我们看第一次遍历完是什么样:

是否找到了所有的顶点?显然不是,缺少了7,那么接下来怎么办:我们从最后的节点开始往回遍历,找到每个节点连通的所有节点,如果节点已经被遍历了不管,如果没有被访问那么我们加进去即可:

邻接矩阵的深度优先搜索

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

typedef int Status;    /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Boolean; /* Boolean是布尔类型,其值是TRUE或FALSE */

typedef char VertexType; /* 顶点类型应由用户定义 */
typedef int EdgeType; /* 边上的权值类型应由用户定义 */

#define MAXSIZE 9 /* 存储空间初始分配量 */
#define MAXEDGE 15
#define MAXVEX 9
#define INFINITYC 65535

typedef struct
{
    VertexType vexs[MAXVEX]; /* 顶点表 */
    EdgeType arc[MAXVEX][MAXVEX];/* 邻接矩阵,可看作边表 */
    int numVertexes, numEdges; /* 图中当前的顶点数和边数 */
}MGraph;

/*4.1 构建一个邻接矩阵*/
void CreateMGraph(MGraph *G)
{
    int i, j;

    //1. 确定图的顶点数以及边数
    G->numEdges=15;
    G->numVertexes=9;

    /*2.读入顶点信息,建立顶点表 */
    G->vexs[0]='A';
    G->vexs[1]='B';
    G->vexs[2]='C';
    G->vexs[3]='D';
    G->vexs[4]='E';
    G->vexs[5]='F';
    G->vexs[6]='G';
    G->vexs[7]='H';
    G->vexs[8]='I';

    /*3. 初始化图中的边表*/
    for (i = 0; i G->numVertexes; i++)
    {
        for ( j = 0; j G->numVertexes; j++)
        {
            G->arc[i][j]=0;
        }
    }

    /*4.将图中的连接信息输入到边表中*/
    G->arc[0][1]=1;
    G->arc[0][5]=1;

    G->arc[1][2]=1;
    G->arc[1][8]=1;
    G->arc[1][6]=1;

    G->arc[2][3]=1;
    G->arc[2][8]=1;

    G->arc[3][4]=1;
    G->arc[3][7]=1;
    G->arc[3][6]=1;
    G->arc[3][8]=1;

    G->arc[4][5]=1;
    G->arc[4][7]=1;

    G->arc[5][6]=1;

    G->arc[6][7]=1;

    /*5.无向图是对称矩阵.构成对称*/
    for(i = 0; i G->numVertexes; i++)
    {
        for(j = i; j G->numVertexes; j++)
        {
            G->arc[j][i] =G->arc[i][j];
        }
    }
}

/*4.2 DFS遍历*/
Boolean visited[MAXVEX]; /* 访问标志的数组 */
//1. 标识顶点是否被标记过;
//2. 选择从某一个顶点开始(注意:非连通图的情况)
//3. 进入递归,打印i点信息,标识; 边表
//4. [i][j] 是否等于1,没有变遍历过visted
void DFS(MGraph G,int i){
    //1.
    visited[i] = TRUE;
    printf("%c",G.vexs[i]);

    //2.0~numVertexes
    for(int j = 0; j G.numVertexes;j++){
        if(G.arc[i][j] == 1 && !visited[j])
            DFS(Gj);
    }
}

void DFSTravese(MGraph G){
    //1.初始化
    for(int i=0;i<G.numVertexes;i++){
        visited[i] = FALSE;
    }

    //2.某一个顶点
    for(int i = 0;i<G.numVertexes;i++){
        if(!visited[i]){
            DFS(Gi);
        }
    }
}

int main(int argcconst char * argv[]) {
    // insert code here...
    printf("邻接矩阵的深度优先遍历!\n");
    MGraph G;
    CreateMGraph(&G);
    DFSTravese(G);
    printf("\n");
    return 0;
}

邻接表的深度优先搜索

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

#define MAXSIZE 9 /* 存储空间初始分配量 */
#define MAXEDGE 15
#define MAXVEX 9
#define INFINITYC 65535

typedef int Status;    /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Boolean; /* Boolean是布尔类型,其值是TRUE或FALSE */

typedef char VertexType; /* 顶点类型应由用户定义 */
typedef int EdgeType; /* 边上的权值类型应由用户定义 */

/* 邻接矩阵结构 */
typedef struct
{
    VertexType vexs[MAXVEX]; /* 顶点表 */
    EdgeType arc[MAXVEX][MAXVEX];/* 邻接矩阵,可看作边表 */
    int numVertexes, numEdges; /* 图中当前的顶点数和边数 */
}MGraph;

/* 邻接表结构****************** */
typedef struct EdgeNode /* 边表结点 */
{
    int adjvex;    /* 邻接点域,存储该顶点对应的下标 */
    int weight;        /* 用于存储权值,对于非网图可以不需要 */
    struct EdgeNode *next; /* 链域,指向下一个邻接点 */
}EdgeNode;

typedef struct VertexNode /* 顶点表结点 */
{
    int in;    /* 顶点入度 */
    char data; /* 顶点域,存储顶点信息 */
    EdgeNode *firstedge;/* 边表头指针 */
}VertexNode, AdjList[MAXVEX];

typedef struct
{
    AdjList adjList;
    int numVertexes,numEdges; /* 图中当前顶点数和边数 */
}graphAdjList,*GraphAdjList;

/*4.1 构建一个邻接矩阵*/
void CreateMGraph(MGraph *G)
{
    int i, j;

    //1. 确定图的顶点数以及边数
    G->numEdges=15;
    G->numVertexes=9;

    /*2.读入顶点信息,建立顶点表 */
    G->vexs[0]='A';
    G->vexs[1]='B';
    G->vexs[2]='C';
    G->vexs[3]='D';
    G->vexs[4]='E';
    G->vexs[5]='F';
    G->vexs[6]='G';
    G->vexs[7]='H';
    G->vexs[8]='I';

    /*3. 初始化图中的边表*/
    for (i = 0; i G->numVertexes; i++)
    {
        for ( j = 0; j G->numVertexes; j++)
        {
            G->arc[i][j]=0;
        }
    }

    /*4.将图中的连接信息输入到边表中*/
    G->arc[0][1]=1;
    G->arc[0][5]=1;

    G->arc[1][2]=1;
    G->arc[1][8]=1;
    G->arc[1][6]=1;

    G->arc[2][3]=1;
    G->arc[2][8]=1;

    G->arc[3][4]=1;
    G->arc[3][7]=1;
    G->arc[3][6]=1;
    G->arc[3][8]=1;

    G->arc[4][5]=1;
    G->arc[4][7]=1;

    G->arc[5][6]=1;

    G->arc[6][7]=1;

    /*5.无向图是对称矩阵.构成对称*/
    for(i = 0; i G->numVertexes; i++)
    {
        for(j = i; j G->numVertexes; j++)
        {
            G->arc[j][i] =G->arc[i][j];
        }
    }
}

/*4.2 利用邻接矩阵构建邻接表*/
void CreateALGraph(MGraph G,GraphAdjList *GL){

    //1.创建邻接表,并且设计邻接表的顶点数以及弧数
    *GL = (GraphAdjList)malloc(sizeof(graphAdjList));
    (*GL)->numVertexes = G.numVertexes;
    (*GL)->numEdges = G.numEdges;

    //2. 从邻接矩阵中将顶点信息输入
    for (int i = 0; i G.numVertexesi++) {
        //顶点入度为0
        (*GL)->
adjList[i].in = 0;
        //顶点信息
        (*GL)->adjList[i].data = G.vexs[i];
        //顶点边表置空
        (*GL)->adjList[i].firstedge = NULL;
    }

    //3. 建立边表
    EdgeNode *e;
    for (int i = 0; i G.numVertexesi++) {
        for (int j = 0; j < G.numVertexesj++) {
            if (G.arc[i][j] == 1) {

                //创建边表中的邻近结点 i->
j
                e = (EdgeNode *)malloc(sizeof(EdgeNode));
                //邻接序号为j
                e->adjvex = j;
                //将当前结点的指向adjList[i]的顶点边表上
                e->next = (*GL)->adjList[i].firstedge;
                (*GL)->adjList[i].firstedge = e;
                //顶点j 上的入度++;
                (*GL)->adjList[j].in++;

//                //创建边表中的邻近结点 j->i
//                e = (EdgeNode *)malloc(sizeof(EdgeNode));
//                //邻接序号为j
//                e->adjvex = i;
//                //将当前结点的指向adjList[i]的顶点边表上
//                e->next = (*GL)->adjList[j].firstedge;
//                (*GL)->adjList[j].firstedge = e;
//                //顶点j 上的入度++;
//                (*GL)->adjList[i].in++;
            }
        }
    }
}


Boolean visited[MAXSIZE]; /* 访问标志的数组 */
/* 邻接表的深度优先递归算法 */
void DFS(GraphAdjList GL, int i)
{
    EdgeNode *p;
    visited[i] = TRUE;

    //2.打印顶点 A
    printf("%c ",GL->adjList[i].data);

    p = GL->adjList[i].firstedge;

    //3.
    while (p) {
        if(!visited[p->adjvex])
            DFS(GL,p->adjvex);

        p = p->next;
    }

}

/* 邻接表的深度遍历操作 */
void DFSTraverse(GraphAdjList GL)
{
    //1. 将访问记录数组默认置为FALSE
    for (int i = 0; i GL->numVertexes; i++) {
        /*初始化所有顶点状态都是未访问过的状态*/
        visited[i] = FALSE;
    }

    //2. 选择一个顶点开始DFS遍历. 例如A
    for(int i = 0; i GL->numVertexes; i++)
        //对未访问过的顶点调用DFS, 若是连通图则只会执行一次.
        if(!visited[i])
            DFS(GL, i);
}


int main(int argc, const char * argv[]) {
    // insert code here...
    printf("邻接表的深度优先遍历!\n");
    MGraph G;
    GraphAdjList GL;
    CreateMGraph(&G);
    CreateALGraph(G,&GL);


    DFSTraverse(GL);
    printf("\n");
    return 0;
}

广度优先搜索

思路:从图的某一结点出发,首先依次访问该结点的所有邻接顶点 Vi1, Vi2, …, Vin 再按这些顶点被访问的先后次序依次访问与它们相邻接的所有未被访问的顶点,重复此过程,直至所有顶点均被访问为止。

其实跟树的层序遍历很像,我们把图稍微变变你再看看:

图还是那个图,只不过我挪了挪,像不像二叉树,哈哈~~接下来阐述一下思路,首先遍历根节点,然后找到左子树和右子树,接着遍历左子树和右子树,依次类推,不过这里为了方便借用一下前面学过的一种数据结构 队列

1
1入队---- 队列不为空-----1出队的同时 26入队-----2出队的同时85入队列,其实其思想就是谁出队谁的子节点入队以此类推。

邻接矩阵的广度优先搜索

话不多说,直接上代码

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

typedef int Status;    /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Boolean; /* Boolean是布尔类型,其值是TRUE或FALSE */

typedef char VertexType; /* 顶点类型应由用户定义 */
typedef int EdgeType; /* 边上的权值类型应由用户定义 */

#define MAXSIZE 9 /* 存储空间初始分配量 */
#define MAXEDGE 15
#define MAXVEX 9
#define INFINITYC 65535

typedef struct
{
    VertexType vexs[MAXVEX]; /* 顶点表 */
    EdgeType arc[MAXVEX][MAXVEX];/* 邻接矩阵,可看作边表 */
    int numVertexes, numEdges; /* 图中当前的顶点数和边数 */
}MGraph;

/*4.1 构建一个邻接矩阵*/
void CreateMGraph(MGraph *G)
{
    int i, j;

    //1. 确定图的顶点数以及边数
    G->numEdges=15;
    G->numVertexes=9;

    /*2.读入顶点信息,建立顶点表 */
    G->vexs[0]='A';
    G->vexs[1]='B';
    G->vexs[2]='C';
    G->vexs[3]='D';
    G->vexs[4]='E';
    G->vexs[5]='F';
    G->vexs[6]='G';
    G->vexs[7]='H';
    G->vexs[8]='I';

    /*3. 初始化图中的边表*/
    for (i = 0; i G->numVertexes; i++)
    {
        for ( j = 0; j G->numVertexes; j++)
        {
            G->arc[i][j]=0;
        }
    }

    /*4.将图中的连接信息输入到边表中*/
    G->arc[0][1]=1;
    G->arc[0][5]=1;

    G->arc[1][2]=1;
    G->arc[1][8]=1;
    G->arc[1][6]=1;

    G->arc[2][3]=1;
    G->arc[2][8]=1;

    G->arc[3][4]=1;
    G->arc[3][7]=1;
    G->arc[3][6]=1;
    G->arc[3][8]=1;

    G->arc[4][5]=1;
    G->arc[4][7]=1;

    G->arc[5][6]=1;

    G->arc[6][7]=1;

    /*5.无向图是对称矩阵.构成对称*/
    for(i = 0; i G->numVertexes; i++)
    {
        for(j = i; j G->numVertexes; j++)
        {
            G->arc[j][i] =G->arc[i][j];
        }
    }
}

/*
 4.2 ***需要用到的队列结构与相关功能函数***
 */

/* 循环队列的顺序存储结构 */
typedef struct
{
    int data[MAXSIZE];
    int front;        /* 头指针 */
    int rear;        /* 尾指针,若队列不空,指向队列尾元素的下一个位置 */
}Queue;

/* 初始化一个空队列Q */
Status InitQueue(Queue *Q)
{
    Q->front=0;
    Q->rear=0;
    return  OK;
}

/* 若队列Q为空队列,则返回TRUE,否则返回FALSE */
Status QueueEmpty(Queue Q)
{
    if(Q.front==Q.rear) /* 队列空的标志 */
    return TRUE;
    else
    return FALSE;
}

/* 若队列未满,则插入元素e为Q新的队尾元素 */
Status EnQueue(Queue *Q,int e)
{
    if ((Q->rear+1)%MAXSIZE == Q->front)    /* 队列满的判断 */
    return ERROR;
    Q->data[Q->rear]=e;            /* 将元素e赋值给队尾 */
    Q->rear=(Q->rear+1)%MAXSIZE;/* rear指针向后移一位置, */
    /* 若到最后则转到数组头部 */
    return  OK;
}

/* 若队列不空,则删除Q中队头元素,用e返回其值 */
Status DeQueue(Queue *Q,int *e)
{
    if (Q->front == Q->rear)            /* 队列空的判断 */
    return ERROR;
    *e=Q->data[Q->front];                /* 将队头元素赋值给e */
    Q->front=(Q->front+1)%MAXSIZE;    /* front指针向后移一位置, */
    /* 若到最后则转到数组头部 */
    return  OK;
}
/******** Queue End **************/

/*4.3 邻接矩阵广度优先遍历-代码实现*/
Boolean visited[MAXVEX]; /* 访问标志的数组 */
void BFSTraverse(MGraph G){

    int temp = 0;

    //1.
    Queue Q;
    InitQueue(&Q);

    //2.将访问标志数组全部置为"未访问状态FALSE"
    for (int i = 0 ; i G.numVertexesi++) {
        visited[i] = FALSE;
    }

    //3.对遍历邻接表中的每一个顶点(对于连通图只会执行1次,这个循环是针对非连通图)
    for (int i = 0 ; i < G.numVertexesi++) {

        if(!visited[i]){
            visited[i] = TRUE;
            printf("%c  ",G.vexs[i]);

            //4. 入队
            EnQueue(&Qi);
            while (!QueueEmpty(Q)) {
                //出队
                DeQueue(&Q, &i);
                for (int j = 0; j < G.numVertexesj++) {
                    if(G.arc[i][j] == 1 && !visited[j])
                    {    visited[j] = TRUE;
                        printf("%c   ",G.vexs[j]);
                        EnQueue(&Qj);
                    }
                }
            }
        }

    }


}

int main(int argcconst char * argv[]) {
    // insert code here...
    printf("邻接矩阵广度优先遍历!\n");
    MGraph G;
    CreateMGraph(&G);
    BFSTraverse(G);
    printf("\n");
    return 0;
}

邻接表的广度优先搜索

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

#define MAXSIZE 9 /* 存储空间初始分配量 */
#define MAXEDGE 15
#define MAXVEX 9
#define INFINITYC 65535

typedef int Status;    /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Boolean; /* Boolean是布尔类型,其值是TRUE或FALSE */

typedef char VertexType; /* 顶点类型应由用户定义 */
typedef int EdgeType; /* 边上的权值类型应由用户定义 */

/* 邻接矩阵结构 */
typedef struct
{
    VertexType vexs[MAXVEX]; /* 顶点表 */
    EdgeType arc[MAXVEX][MAXVEX];/* 邻接矩阵,可看作边表 */
    int numVertexes, numEdges; /* 图中当前的顶点数和边数 */
}MGraph;

/* 邻接表结构****************** */
typedef struct EdgeNode /* 边表结点 */
{
    int adjvex;    /* 邻接点域,存储该顶点对应的下标 */
    int weight;        /* 用于存储权值,对于非网图可以不需要 */
    struct EdgeNode *next; /* 链域,指向下一个邻接点 */
}EdgeNode;

typedef struct VertexNode /* 顶点表结点 */
{
    int in;    /* 顶点入度 */
    char data; /* 顶点域,存储顶点信息 */
    EdgeNode *firstedge;/* 边表头指针 */
}VertexNode, AdjList[MAXVEX];

typedef struct
{
    AdjList adjList;
    int numVertexes,numEdges; /* 图中当前顶点数和边数 */
}graphAdjList,*GraphAdjList;

/*4.1 构建一个邻接矩阵*/
void CreateMGraph(MGraph *G)
{
    int i, j;

    //1. 确定图的顶点数以及边数
    G->numEdges=15;
    G->numVertexes=9;

    /*2.读入顶点信息,建立顶点表 */
    G->vexs[0]='A';
    G->vexs[1]='B';
    G->vexs[2]='C';
    G->vexs[3]='D';
    G->vexs[4]='E';
    G->vexs[5]='F';
    G->vexs[6]='G';
    G->vexs[7]='H';
    G->vexs[8]='I';

    /*3. 初始化图中的边表*/
    for (i = 0; i G->numVertexes; i++)
    {
        for ( j = 0; j G->numVertexes; j++)
        {
            G->arc[i][j]=0;
        }
    }

    /*4.将图中的连接信息输入到边表中*/
    G->arc[0][1]=1;
    G->arc[0][5]=1;

    G->arc[1][2]=1;
    G->arc[1][8]=1;
    G->arc[1][6]=1;

    G->arc[2][3]=1;
    G->arc[2][8]=1;

    G->arc[3][4]=1;
    G->arc[3][7]=1;
    G->arc[3][6]=1;
    G->arc[3][8]=1;

    G->arc[4][5]=1;
    G->arc[4][7]=1;

    G->arc[5][6]=1;

    G->arc[6][7]=1;

    /*5.无向图是对称矩阵.构成对称*/
    for(i = 0; i G->numVertexes; i++)
    {
        for(j = i; j G->numVertexes; j++)
        {
            G->arc[j][i] =G->arc[i][j];
        }
    }
}

/*4.2 利用邻接矩阵构建邻接表*/
void CreateALGraph(MGraph G,GraphAdjList *GL){

    //1.创建邻接表,并且设计邻接表的顶点数以及弧数
    *GL = (GraphAdjList)malloc(sizeof(graphAdjList));
    (*GL)->numVertexes = G.numVertexes;
    (*GL)->numEdges = G.numEdges;

    //2. 从邻接矩阵中将顶点信息输入
    for (int i = 0; i G.numVertexesi++) {
        //顶点入度为0
        (*GL)->
adjList[i].in = 0;
        //顶点信息
        (*GL)->adjList[i].data = G.vexs[i];
        //顶点边表置空
        (*GL)->adjList[i].firstedge = NULL;
    }

    //3. 建立边表
    EdgeNode *e;
    for (int i = 0; i G.numVertexesi++) {
        for (int j = 0; j < G.numVertexesj++) {
            if (G.arc[i][j] == 1) {

                //创建边表中的邻近结点 i->
j
                e = (EdgeNode *)malloc(sizeof(EdgeNode));
                //邻接序号为j
                e->adjvex = j;
                //将当前结点的指向adjList[i]的顶点边表上
                e->next = (*GL)->adjList[i].firstedge;
                (*GL)->adjList[i].firstedge = e;
                //顶点j 上的入度++;
                (*GL)->adjList[j].in++;

                //                //创建边表中的邻近结点 j->i
                //                e = (EdgeNode *)malloc(sizeof(EdgeNode));
                //                //邻接序号为j
                //                e->adjvex = i;
                //                //将当前结点的指向adjList[i]的顶点边表上
                //                e->next = (*GL)->adjList[j].firstedge;
                //                (*GL)->adjList[j].firstedge = e;
                //                //顶点j 上的入度++;
                //                (*GL)->adjList[i].in++;
            }
        }
    }


}

/*
 5.2 ***需要用到的队列结构与相关功能函数***
 */
/* 循环队列的顺序存储结构 */
typedef struct
{
    int data[MAXSIZE];
    int front;        /* 头指针 */
    int rear;        /* 尾指针,若队列不空,指向队列尾元素的下一个位置 */
}Queue;

/* 初始化一个空队列Q */
Status InitQueue(Queue *Q)
{
    Q->front=0;
    Q->rear=0;
    return  OK;
}

/* 若队列Q为空队列,则返回TRUE,否则返回FALSE */
Status QueueEmpty(Queue Q)
{
    if(Q.front==Q.rear) /* 队列空的标志 */
    return TRUE;
    else
    return FALSE;
}

/* 若队列未满,则插入元素e为Q新的队尾元素 */
Status EnQueue(Queue *Q,int e)
{
    if ((Q->rear+1)%MAXSIZE == Q->front)    /* 队列满的判断 */
    return ERROR;
    Q->data[Q->rear]=e;            /* 将元素e赋值给队尾 */
    Q->rear=(Q->rear+1)%MAXSIZE;/* rear指针向后移一位置, */
    /* 若到最后则转到数组头部 */
    return  OK;
}

/* 若队列不空,则删除Q中队头元素,用e返回其值 */
Status DeQueue(Queue *Q,int *e)
{
    if (Q->front == Q->rear)            /* 队列空的判断 */
    return ERROR;
    *e=Q->data[Q->front];                /* 将队头元素赋值给e */
    Q->front=(Q->front+1)%MAXSIZE;    /* front指针向后移一位置, */
    /* 若到最后则转到数组头部 */
    return  OK;
}
/* *********************** Queue End ******************************* */
/*5.3 邻接表广度优先遍历*/
Boolean visited[MAXSIZE]; /* 访问标志的数组 */
void BFSTraverse(GraphAdjList GL){

    //1.创建结点
    EdgeNode *p;

    Queue Q;
    InitQueue(&Q);


    //2.将访问标志数组全部置为"未访问状态FALSE"
    for(int i = 0; i GL->numVertexes; i++)
        visited[i] = FALSE;

    //3.对遍历邻接表中的每一个顶点(对于连通图只会执行1次,这个循环是针对非连通图)
    for(int i = 0 ;i GL->numVertexes;i++){
        //4.判断当前结点是否被访问过.
        if(!visited[i]){
            visited[i] = TRUE;
            //打印顶点
            printf("%c ",GL->adjList[i].data);

            EnQueue(&Q, i);
            while (!QueueEmpty(Q)) {
                DeQueue(&Q, &i);
                p = GL->adjList[i].firstedge;
                while (p) {
                    //判断
                    if(!visited[p->adjvex]){
                        visited[p->adjvex] = TRUE;
                         printf("%c ",GL->adjList[p->adjvex].data);
                        EnQueue(&Q, p->adjvex);
                    }
                    p = p->next;
                }
            }

        }
    }

}

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("邻接表广度优先遍历\n");
    MGraph G;
    GraphAdjList GL;
    CreateMGraph(&G);
    CreateALGraph(G,&GL);

    BFSTraverse(GL);
    printf("\n");
    return 0;
}

总结

这篇文章主要是认识一下图示如何展示的,以及我们如何构思去存储一个图的结构和后续的图的深度优先搜索和广度优先搜索的各种实现,希望能够讲明白。