9.图的存储与遍历

什么是图

图是由一系列顶点和若干连结顶点集合内两个顶点的边组成的数据结构。数学意义上的图,指的是由一系列点与边构成的集合,这里我们只考虑有限集,通常我们用G = (V, E)表示一个图结构,其中V表示点集,E表示边集。

在顶点集合所包含的若干个顶点之间,可能存在着某种两两关系——如果某两个点之间的确存在这样的关系的话,我们就在这两个点之间连边,这样就得到了边集的一个成员,也就是一条边。对应到社交网络中,顶点就是网络中的用户,边就是用户之间的好友关系。

简而言之,一条有向边必然是从一个点指向另一个点,而相反方向的边在有向图中则不一定存在;而有的时候我们并不在意构成一条边的两个顶点具体谁先谁后,这样得到的一条边就是无向边。对于图而言,如果图中所有的边都是无向图,反之称为有向图。

一般而言,我们在数据结构中所讨论的图都是有向图,因为有向图相比无向图更具有代表性。实际上,无向图可以由有向图来表示。如果AB两个点之间存在无向边的话,那用有向图也可以表示AB两点之间同时存在ABBA两条有向边。

图的相关概念

很少边或弧(如e < n log ne指边数,n指点数)的图称为稀疏图,反之称为稠密图

顶点的 是指依附于某个顶点的边数。在有向图中,我们需要学习顶点的 入度出度 这两个概念。顶点的入度是指以顶点为弧头的弧的数目,也就是以该顶点为终点的弧的数目;顶点的出度是指以顶点为弧尾的弧的数目,也就是以该顶点为起点的弧的数目。需要注意的是,在有向图里,顶点的度为入度与出度之和。

图的存储

邻接矩阵

邻接矩阵存储结构就是用一维数组存储图中顶点的信息,用矩阵表示图中各顶点之间的邻接关系。

对于有n个顶点的图G = (V,E)来说,我们可以用一个n * n的矩阵A来表示G中各顶点的相邻关系,如果vivj之间存在边(或弧),则A|i|j| = 1 ,否则A[i][j] = 0

图的邻接矩阵是唯一的,矩阵的大小只与顶点个数N有关。是一个N * N的矩阵。前面我们已经介绍过,在无向图里,如果顶点vivj之间有边,则可认为顶点vivj有边,顶点vjvi也有边。对应到邻接矩阵里,则有A[i][j] = A[j][i]

我们可以发现,无向图的邻接矩阵是一个对称矩阵。在邻接矩阵上,我们可以直观的看出两个顶点之间是否有边(或弧),并能容易求出每个顶点的度,入度和出度。

首先来学习邻接矩阵的实现,我们都知道,邻接矩阵是一个由1和0构成的矩阵。处于第i行、第j列上的元素10分别代表顶点ij之间存在或不存在一条有向边。显然在构造邻接矩阵的时候,我们需要实现一个整形的二维数组。由于当前的图还是空的,因此我们还要把这个二维数组中的每个元素都初始化为 0。

在构造好了一个图的结构后,我们需要把图中各边的情况对应在邻接矩阵上。实际上,这一步的实现非常简单,当从顶点 x 到 y 上存在边时,我们只要把二维数组对应的位置置为 1 就好了。

/*************************************************************************
	> File Name: 11.graph_matrix.c
	> Author: 陈杰
	> Mail: 15193172746@163.com
	> Created Time: 2021年04月13日 星期二 09时59分22秒
  > 邻接矩阵存储有向图
************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX_N 500
typedef struct Graph {
    int mat[MAX_N][MAX_N];
    int n;
}Graph;
/*
* 初始化一个邻接矩阵
* @param len: 邻接矩阵的边界
* */
Graph *init(int len) {
    Graph *g = (Graph *)malloc(sizeof(Graph));
    g->n = len;
    memset(g->mat, 0, sizeof(g->mat));
    return g;
}
/*
* 插入一个边
* @param g: 要操作的矩阵指针
* @param a: 插入关系,a==1时为无向边
* @param x: 初始点编号
* @param y: 终止点编号
* */
void insert(Graph *g, int a, int x, int y) {
    if(x < 1 || y < 1 || x > g->n || y > g->n) return;
    if(a) g->mat[y][x] = 1;
    g->mat[x][y] = 1;
}
/*
* 打印输出
* @param g: 要操作的矩阵指针
* */
void output(Graph *g) {
    for(int i = 0; i < g->n; i++) {
        for(int j = 0; j < g->n; j++) {
            j == 0 || printf(" ");
            printf("%d", g->mat[i][j]);
        }
        printf("\n");
    }
}
int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    Graph *graph = init(n);
    int a, x, y;
    for(int i = 0; i < m; i++) {
        scanf("%d %d %d", &a, &x, &y);
        insert(graph, a, x, y);
    }
    output(graph);
    free(graph);
    return 0;
}
邻接表

邻接表是图的一种顺序存储与链式存储相结合的存储方法。我们给图 G 中的每个顶点建立一个单链表,第 i 个单链表中的结点表示依附于顶点 vi 的边(对于有向图是以 vi 为起点的弧)。所有单链表的表头结点都存储在一个一维数组中,以便于顶点的访问。下图为图 G1 对应的邻接表。

在无向图的邻接表中,顶点 vi 的度为第 i 个单链表中的结点数;而在有向图中,第 i 个单链表中的结点数表示的是顶点 vi 的出度,如果要求入度,则要遍历整个邻接表。另外,在邻接表中,我们很容易就能知道某一顶点和哪些顶点相连接。

邻接矩阵存储结构最大的优点就是简单直观,易于理解和实现。其适用范围广泛,有向图、无向图、混合图、带权图等都可以直接用邻接矩阵表示。另外,对于很多操作,比如获取顶点度数,判断某两点之间是否有连边等,都可以在常数时间内完成。然而,它的缺点也是显而易见的:当边数很少的时候,就会造成空间的浪费。

因此,具体使用哪一种存储方式,要根据图的特点来决定:如果是稀疏图,我们一般用邻接表来存储,这样可以节省空间;如果是稠密图,考虑到邻接表中要附加链域,我们一般用邻接矩阵来存储。

在构造邻接表需要借助链表的结构。对图中每个顶点的信息,我们都会分别使用一个链表来进行存储。因此,我们需要初始化一个有 n 个元素的链表数组,n 为图中顶点数量。

我们要在邻接表中存储的图的信息,实际上就是顶点之间存在的有向边。当从顶点a到顶点b存在一条有向边时,我们只需要在头结点为a的链表后插入一个节点b,值得注意的是,当一条边从顶点b到顶点a时,我们同样需要在以b为头结点的链表后插入一个节点a

/*************************************************************************
	> File Name: 11.graph_linklist.c
	> Author: 陈杰
	> Mail: 15193172746@163.com
	> Created Time: 2021年04月13日 星期二 10时37分10秒
  > 邻接表存储有向图
************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX_N  500
/*
* 定义一个链表用来存储每个元素所关联的结点元素
* */
typedef struct Node{
    int vertex;
    struct Node *next;
} Node, *LinkList;
/*
* 再指定链表插入一个新结点
* @param head: 要插入的链表头节点
* @param index: 要插入的值
* */
LinkList insert_node(LinkList head, int index) {
    Node *node = (Node *)malloc(sizeof(Node));
    node->vertex = index;
    node->next = head;
    return node;
}
/*
* 邻接表结构
* */
typedef struct Graph {
    LinkList edges[MAX_N];
    int n;
}Graph;
/*
* 初始化一个邻接矩阵
* @param n:数据范围
* */
Graph *init(int n){
    Graph *g = (Graph *)malloc(sizeof(Graph));
    g->n = n;
    for(int i = 0; i <= n; i++) g->edges[i] = NULL;
    return g;
}
/*
* 插入一条边到邻接表
* @param g: 要操作的图的指针
* @param a: 边的类型,当a==1时为无向边
* @param x: 起始点编号
* @param y: 终止点编号
* */
void insert(Graph *g, int a, int x, int y) {
    if(x < 1 || y < 1 || x > g->n || y > g->n) return;
    g->edges[x] = insert_node(g->edges[x], y);
    if(a) g->edges[y] = insert_node(g->edges[y], x);
}
/*
* 打印邻接表
* @param g: 要操作的图的指针
* */
void output(Graph *g) {
    for(int i = 1; i <= g->n; i++) {
        printf("%d:", i);
        for(Node *node = g->edges[i]; node != NULL; node = node->next) {
            printf("%d", node->vertex);
        }
        printf("\n");
    }
}
/*
* 邻接表清理
* @param g: 要操作的图的指针
* */
void clear(Graph *g) {
    for(int i = 1; i <= g->n; i++) {
        Node *node = g->edges[i];
        while(node != NULL) {
            Node *delete_node = node;
            node = node->next;
            free(delete_node);
        }
    }
    free(g);
}
int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    Graph *graph = init(n);
    for(int i = 0; i < m; i++) {
        int a, x, y;
        scanf("%d %d %d", &a, &x, &y);
        insert(graph, a, x, y);
    }
    output(graph);
    clear(graph);
    return 0;
}

图的遍历

从图中某个顶点出发访问到图中每个顶点,且使得每个顶点刚好被访问一次,这一过程被称为图的遍历。常用的两种图的遍历算法:深度优先搜索和广度优先搜索。

图的遍历和生成树

图的遍历和树或森林的遍历的核心思路很像:都是化繁为简。此前我们通过对二叉树进行遍历,将二叉树转换为一个线性序列,把树这种非线性结构转化为线性序列,这就是化繁为简的方式。这样一来,二叉树的很多问题都可以转化到线性序列上进行求解。

线性序列是树或森林的一个特例,而树或森林则是图结构的一个特例。仿照树的遍历,我们在对图进行遍历的过程中,首先将图这种复杂的非线性结构,转化为图的一种特例,即一棵树——生成树(也叫支撑树)。

这样的一棵生成树,具有两个重要的性质:其一,它的根节点便是我们对图进行遍历时的起始顶点;其二,它包含图中的所有顶点。对于一个图,可能会有很多种合法的生成树。

深度优先搜索算法

深度优先搜索(Depth-First-Search,简称DFS) 是一种常见的用于遍历或搜索树和图的算法,主要用来判断起点与终点之间的连通性问题。

开始我们假设图上所有顶点都未被访问,选择图中任一顶点,开始执行以下操作:

  1. 访问当前顶点v,并将顶点标为已访问。
  2. 遍历与顶点v相邻的所有顶点c,然后对顶点v所有尚未被访问的相邻顶点c,递归地执行第一步操作。如果当前顶点已经没有未访问的相邻顶点了,则说明该分支搜索结束,沿通路回溯到顶点v
  3. 此时如果还有相邻顶点没有被访问,则从该顶点继续开始深度优先搜索。直到所有顶点都被访问。

从操作方法上我们可以看出,深度优先搜索遍历算法总是沿着图的某一深度进行遍历,尽可能深的搜索与当前相邻的顶点,如果相邻的顶点都已被访问则回溯到上一层,直至所有顶点都已被访问。

仔细分析后我们发现,对一个连通图进行深度优先搜索,其实是在对该图的一个生成树进行搜索,这里我们把这棵生成树称为深度优先搜索树。

对于算法的具体实现,因深度优先搜索的优先遍历深度更大的顶点,所以我们可以借助栈这一数据结构来实现:

  1. 将要访问的第一个顶点v入栈,然后首先对其进行访问。
  2. 将顶点v出栈,依次将与顶点v相邻且未被访问的顶点c压入栈中。
  3. 重复第一步操作,直至栈为空。

为了方便,我们通常以递归的形式实现深度优先搜索。

邻接矩阵的深度优先遍历
/*************************************************************************
	> File Name: 11.graph_matrix_dfs.c
	> Author: 陈杰
	> Mail: 15193172746@163.com
	> Created Time: 2021年04月13日 星期二 11时39分53秒
  > 题目描述
  读入一个用邻接矩阵存储的无向图,输出它的深度优先遍历序列。(以 1 为起点,按照编号越小权值越大的规则)

  > 输入
  第一行一个正整数 N,表示节点个数。(5≤N≤20)
  接下来输入一个邻接矩阵,a[i,j]=0表示 i,j 之间不存在边,=1说明存在边。

  > 输出
  按照遍历顺序输出遍历点的编号。
************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include <string.h>
#define MAX_N 500
typedef struct Graph {
    int mat[MAX_N][MAX_N];
    int n;
} Graph;
/*
* 初始化一个邻接矩阵
* @param len: 邻接矩阵的边界
* */
Graph *init(int len) {
    Graph *g = (Graph *)malloc(sizeof(Graph));
    g->n = len;
    memset(g->mat, 0, sizeof(g->mat));
    return g;
}
/*
* 有向边插入
* @param g: 要操作的矩阵指针
* @param x: 初始点编号
* @param y: 终止点编号
* */
void insert(Graph *g, int x, int y) {
    if(x < 1 || y < 1 || y > g->n || y > g->n) return;
    g->mat[x][y] = 1;
}

int mark[25];
/*
* 邻接矩阵深搜
* @param g: 要操作的矩阵指针
* @param s: 起始点
* @param n: 数据上限
* */
void dfs(Graph *g, int s, int n) {
    mark[s] = 1;
    s == 1 || printf("-");;
    printf("%d", s);
    for(int i = 1; i <= n; i++) {
        if(mark[i] == 0 && g->mat[s][i]) {
            dfs(g, i, n);
        }
    }
}
int main() {
    int n;
    scanf("%d", &n);
    Graph *g = init(n);
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            int t;
            scanf("%d", &t);
            if(t) insert(g, i, j);
        }
    }
    for(int i = 1; i <= n; i++) {
        if(mark[i] == 0) dfs(g, i,n);
    }
    printf("\n");
    free(g);
    return 0;
}
邻接表的深搜
/*************************************************************************
	> File Name: 11.graph_linklist_dfs.c
	> Author: 陈杰
	> Mail: 15193172746@163.com
	> Created Time: 2021年04月13日 星期二 10时37分10秒
  > 题目描述
  读入一个用邻接矩阵存储的无向图,输出它的深度优先遍历序列。(以 1 为起点,按照编号越小权值越大的规则)

  > 输入
  第一行一个正整数 N,表示节点个数。(5≤N≤20)
  接下来输入一个邻接矩阵,a[i,j]=0表示 i,j 之间不存在边,=1说明存在边。

  > 输出
  按照遍历顺序输出遍历点的编号。
************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX_N  500
/*
* 定义一个链表用来存储每个元素所关联的结点元素
* */
typedef struct Node{
    int vertex;
    struct Node *next;
} Node, *LinkList;
/*
* 再指定链表插入一个新结点
* @param head: 要插入的链表头节点
* @param index: 要插入的值
* */
LinkList insert_node(LinkList head, int index) {
    Node *node = (Node *)malloc(sizeof(Node));
    node->vertex = index;
    node->next = NULL;
    if(head == NULL) return node;
    Node *current = head;
    while(current->next) current = current->next;
    current->next = node;
    return head;
}
/*
* 邻接表结构
* */
typedef struct Graph {
    LinkList edges[MAX_N];
    int n;
}Graph;
/*
* 初始化一个邻接矩阵
* @param n:数据范围
* */
Graph *init(int n){
    Graph *g = (Graph *)malloc(sizeof(Graph));
    g->n = n;
    for(int i = 0; i <= n; i++) g->edges[i] = NULL;
    return g;
}
/*
* 插入一条边到邻接表
* @param g: 要操作的图的指针
* @param x: 起始点编号
* @param y: 终止点编号
* */
void insert(Graph *g, int x, int y) {
    if(x < 1 || y < 1 || x > g->n || y > g->n) return;
    g->edges[x] = insert_node(g->edges[x], y);
}
/*
* 深度优先遍历邻接表
* @param g: 要操作的图的指针
* @param index: 起始点编号
* */
int mark[25];
void dfs(Graph *g, int index) {
    index == 1 || printf("-");
    printf("%d", index);
    mark[index] = 1;
    for(Node *node = g->edges[index]; node; node = node->next) {
        if(mark[node->vertex] == 0) {
            dfs(g, node->vertex);
        }
    }
}
/*
* 邻接表清理
* @param g: 要操作的图的指针
* */
void clear(Graph *g) {
    for(int i = 1; i <= g->n; i++) {
        Node *node = g->edges[i];
        while(node != NULL) {
            Node *delete_node = node;
            node = node->next;
            free(delete_node);
        }
    }
    free(g);
}
int main() {
    int n;
    scanf("%d", &n);
    Graph *graph = init(n);
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            int t;
            scanf("%d", &t);
            if(t) insert(graph, i, j);
        }
    }
    for(int i = 1; i <= n; i++) {
        if(mark[i] == 0) dfs(graph, i);
    }
    printf("\n");
    clear(graph);
    return 0;
}
广度优先搜索算法

**广度优先搜索(Breadth-First-Search, 简称BFS)**是一种连通图的常用遍历策略,通常用于求起点到各点的最短路径,以及求两点之间的最优路径等问题。

对于一个连通图,我们假设一开始所有顶点均未被访问,广度优先搜索的主要操作如下:

  1. 选择图中任意一个顶点v作为起点进行访问,并将顶点v标记为已访问。
  2. 遍历并访问与顶点v相邻且未被访问的所有顶点c1,c2ck,接着遍历并访问与顶点c1,c2ck相邻且未被访问的顶点,也就是依次访问所有相邻顶点的相邻顶点,以此类推,直到所有顶点均被访问。

从遍历的过程我们可以看到,广度优先搜索总是从某一起点出发逐层进行搜索,每一层的顶点到起点的边数都是一样的。这一过程正好解释了为什么广度优先搜索可以求出图中一点到各点的最短距离,以及求某两点之间的最优路径等问题。

同样我们可以从遍历过程中发现,对一个连通图进行广度优先搜索,其实是在对该图的一个生成树进行搜索,这里我们把这个生成树称为广度优先搜索树。

结合队列先进先出的特性,我们可以借助它来具体实现广度优先搜索:

  1. 任意选择一个顶点v作为起点,加入队列。
  2. 访问队首元素v并标记,将其从队列中删除。
  3. 遍历与定点v相邻且未被访问的所有顶点c1,c2ck,并依次加入到队列中。
  4. 重复第二步和第三步操作,直到队列为空。
邻接矩阵的广度优先遍历
/*************************************************************************
	> File Name: 11.graph_matrix_bfs.c
	> Author: 陈杰
	> Mail: 15193172746@163.com
	> Created Time: 2021年04月13日 星期二 16时12分54秒
  > 题目描述
  读入一个用邻接矩阵存储的无向图,输出它的广度优先遍历序列。(以 1 为起点,按照编号越小权值越大的规则)

  > 输入
  第一行一个正整数 N,表示节点个数。(5≤N≤20)
  接下来输入一个邻接矩阵,a[i,j]=0表示 i,j 之间不存在边,=1说明存在边。

  > 输出
  按照遍历顺序输出遍历点的编号。
************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX_N 500
typedef struct Graph {
    int mat[MAX_N][MAX_N];
    int n;
} Graph;
/*
* 初始化一个邻接矩阵
* @param len: 邻接矩阵的边界
* */
Graph *init(int len) {
    Graph *g = (Graph *)malloc(sizeof(Graph));
    g->n = len;
    memset(g->mat, 0, sizeof(g->mat));
    return g;
}
/*
* 有向边插入
* @param g: 要操作的矩阵指针
* @param x: 初始点编号
* @param y: 终止点编号
* */
void insert(Graph *g, int x, int y) {
    if(x < 1 || y < 1 || x > g->n || y > g->n) return;
    g->mat[x][y] = 1;
}
/*
* 定义一个队列
* */
typedef struct Queue {
    int *data;
    int head, tail, size, length;
} Queue;
/*
* 初始化一个队列
* @param size: 队列的大小
* */
Queue *queue_init(int size) {
    Queue *q = (Queue *)malloc(sizeof(Queue));
    q->data = (int *)malloc(sizeof(int) * size);
    q->head = q->tail = q->length = 0;
    q->size = size;
    return q;
}
/*
* 判空
* @param q: 要操作的队列指针
* */
int empty(Queue *q) {
    return q->length == 0;                          // 判断队列长度是否为零
}
/*
* 扩容
* @param q: 要操作的队列指针
* */
int expand(Queue *q) {
    if(q == NULL) return 0;
    int extr_size = q->size;
    int *p = NULL;
    while(extr_size) {                              // 空间使用过大,可能造成申请失败
        p = (int *)malloc(sizeof(int) * (q->size + extr_size));
        if(p) {
            for(int j = 0; j < q->length; j++) p[j] = q->data[(q->head + j) % q->size];
            free(q->data);
            q->data = p;
            q->head = 0;
            q->tail = q->length;
            q->size += extr_size;
            return 1;                  
        }
        extr_size >>= 1;                            // 扩容大小减半
    }
    return 0;
}
/*
* 入队
* @param q: 要操作的队列指针
* @param val: 要入队的新元素值
* */
int push(Queue *q, int val) {
    if(q == NULL) return 0;                         // 判断队列是否存在
    if(q->tail == q->length && !expand(q)) return 0;// 队列已满且扩容失败
    q->data[q->tail++] = val;
    if(q->tail == q->size) q->tail = 0;             // tail值达到size时赋值为0
    // q->tail = q->tail % q->size;
    q->length++;
    return 1;
}
/*
* 出队
* @param q: 要操作的队列指针
* */
int pop(Queue *q) {
    if(q == NULL || empty(q)) return 0;             // 队列不存在或者为空
    q->head++;
    if(q->head == q->size) q->head = 0;             // head值达到size值时赋值为0
    // q->head = q->head % q->size;
    q->length--;
    return 1;
}
/*
* 获得队首元素值
* @param q: 要操作的队列指针
* */
int front(Queue *q) {
    return q->data[q->head];
}
/*
* 队列销毁
* @param q: 要操作的队列指针
* */
void clear_queue(Queue *q) {
    if(q == NULL) return;
    free(q->data);
    free(q);
}
int mark[25];
/*
* 广度优先遍历
* @param g: 要操作的矩阵指针
* @param s: 起始点
* @param n: 数据上限
* */
void bfs(Graph *g, int s, int n) {
    Queue *que = queue_init(n);
    push(que, s);
    mark[s] = 1;
    while(!empty(que)) {
        int temp = front(que);
        temp == 1 || printf("-");
        printf("%d", temp);
        pop(que);
        for(int i = 1; i <= n; i++) {
            if(g->mat[temp][i] && mark[i] == 0) {
                push(que, i);
                mark[i] = 1;
            }
        }
    }
    clear_queue(que);
}
int main() {
    int n;
    scanf("%d", &n);
    Graph *g = init(n);
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            int t;
            scanf("%d", &t);
            if(t) insert(g, i, j);
        }
    }
    for(int i = 1; i <= n; i++) {
        if(mark[i] == 0) bfs(g, i,n);
    }
    printf("\n");
    free(g);
    return 0;
}
邻接表的广度优先遍历
/*************************************************************************
	> File Name: 11.graph_linklist_bfs.c
	> Author: 陈杰
	> Mail: 15193172746@163.com
	> Created Time: 2021年04月13日 星期二 16时57分31秒
  > 题目描述
  读入一个用邻接矩阵存储的无向图,输出它的广度优先遍历序列。(以 1 为起点,按照编号越小权值越大的规则)

  > 输入
  第一行一个正整数 N,表示节点个数。(5≤N≤20)
  接下来输入一个邻接矩阵,a[i,j]=0表示 i,j 之间不存在边,=1说明存在边。

  > 输出
  按照遍历顺序输出遍历点的编号。
 ************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX_N  500
/*
* 定义一个链表用来存储每个元素所关联的结点元素
* */
typedef struct Node{
    int vertex;
    struct Node *next;
} Node, *LinkList;
/*
* 再指定链表插入一个新结点
* @param head: 要插入的链表头节点
* @param index: 要插入的值
* */
LinkList insert_node(LinkList head, int index) {
    Node *node = (Node *)malloc(sizeof(Node));
    node->vertex = index;
    node->next = NULL;
    if(head == NULL) return node;
    Node *current = head;
    while(current->next) current = current->next;
    current->next = node;
    return head;
}
/*
* 邻接表结构
* */
typedef struct Graph {
    LinkList edges[MAX_N];
    int n;
}Graph;
/*
* 初始化一个邻接矩阵
* @param n:数据范围
* */
Graph *init(int n){
    Graph *g = (Graph *)malloc(sizeof(Graph));
    g->n = n;
    for(int i = 0; i <= n; i++) g->edges[i] = NULL;
    return g;
}
/*
* 插入一条边到邻接表
* @param g: 要操作的图的指针
* @param x: 起始点编号
* @param y: 终止点编号
* */
void insert(Graph *g, int x, int y) {
    if(x < 1 || y < 1 || x > g->n || y > g->n) return;
    g->edges[x] = insert_node(g->edges[x], y);
}
/*
* 定义一个队列
* */
typedef struct Queue {
    int *data;
    int head, tail, size, length;
} Queue;
/*
* 初始化一个队列
* @param size: 队列的大小
* */
Queue *queue_init(int size) {
    Queue *q = (Queue *)malloc(sizeof(Queue));
    q->data = (int *)malloc(sizeof(int) * size);
    q->head = q->tail = q->length = 0;
    q->size = size;
    return q;
}
/*
* 判空
* @param q: 要操作的队列指针
* */
int empty(Queue *q) {
    return q->length == 0;                          // 判断队列长度是否为零
}
/*
* 扩容
* @param q: 要操作的队列指针
* */
int expand(Queue *q) {
    if(q == NULL) return 0;
    int extr_size = q->size;
    int *p = NULL;
    while(extr_size) {                              // 空间使用过大,可能造成申请失败
        p = (int *)malloc(sizeof(int) * (q->size + extr_size));
        if(p) {
            for(int j = 0; j < q->length; j++) p[j] = q->data[(q->head + j) % q->size];
            free(q->data);
            q->data = p;
            q->head = 0;
            q->tail = q->length;
            q->size += extr_size;
            return 1;                  
        }
        extr_size >>= 1;                            // 扩容大小减半
    }
    return 0;
}
/*
* 入队
* @param q: 要操作的队列指针
* @param val: 要入队的新元素值
* */
int push(Queue *q, int val) {
    if(q == NULL) return 0;                         // 判断队列是否存在
    if(q->tail == q->length && !expand(q)) return 0;// 队列已满且扩容失败
    q->data[q->tail++] = val;
    if(q->tail == q->size) q->tail = 0;             // tail值达到size时赋值为0
    // q->tail = q->tail % q->size;
    q->length++;
    return 1;
}
/*
* 出队
* @param q: 要操作的队列指针
* */
int pop(Queue *q) {
    if(q == NULL || empty(q)) return 0;             // 队列不存在或者为空
    q->head++;
    if(q->head == q->size) q->head = 0;             // head值达到size值时赋值为0
    // q->head = q->head % q->size;
    q->length--;
    return 1;
}
/*
* 获得队首元素值
* @param q: 要操作的队列指针
* */
int front(Queue *q) {
    return q->data[q->head];
}
/*
* 队列销毁
* @param q: 要操作的队列指针
* */
void clear_queue(Queue *q) {
    if(q == NULL) return;
    free(q->data);
    free(q);
}
/*
* 深度优先遍历邻接表
* @param g: 要操作的图的指针
* @param index: 起始点编号
* @param n: 数据上限
* */
int mark[25];
void bfs(Graph *g, int index, int n) {
    Queue *que = queue_init(n);
    push(que, index);
    mark[index] = 1;
    while(!empty(que)) {
        int temp = front(que);
        temp == 1 || printf("-");
        printf("%d", temp);
        pop(que);
        for(Node *node = g->edges[index]; node; node = node->next) {
            if(mark[node->vertex] == 0) {
                mark[node->vertex] = 1;
                push(que, node->vertex);
            }
        }
    }
    clear_queue(que);
}
/*
* 邻接表清理
* @param g: 要操作的图的指针
* */
void clear(Graph *g) {
    for(int i = 1; i <= g->n; i++) {
        Node *node = g->edges[i];
        while(node != NULL) {
            Node *delete_node = node;
            node = node->next;
            free(delete_node);
        }
    }
    free(g);
}
int main() {
    int n;
    scanf("%d", &n);
    Graph *graph = init(n);
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            int t;
            scanf("%d", &t);
            if(t) insert(graph, i, j);
        }
    }
    for(int i = 1; i <= n; i++) {
        if(mark[i] == 0) bfs(graph, i, n);
    }
    printf("\n");
    clear(graph);
    return 0;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值