C数据结构——无向图(邻接表方式) 创建与基本使用

源码+注释

//
// Created by Lenovo on 2022-05-17-下午 4:37.
// 作者:小象
// 版本:1.0
//

#include <stdio.h>
#include <malloc.h>

#define TRUE 1
#define FALSE 0

#define MAX_ALVNUMS 100 // 最大顶点数

/*
 * 定义链队
 */
typedef int QElemType;

typedef struct QNode {
    QElemType data;
    struct QNode *next;
} QNode, *QueuePtr;

typedef struct {
    QueuePtr front; // 队头指针
    QueuePtr rear; // 队尾指针
} LinkQueue;

/*
 * 定义邻接表
 */
// 边结点
typedef struct EdgeNode { // 结构体名称(用于结构体自身调用)
    int adjvex; // 该边指向顶点的位置
    struct EdgeNode *next; // 指向下一条边的指针
} EdgeNode;

// 顶点信息
typedef struct VertexNode {
    char verxs;
    EdgeNode *firstEdge; // 指向第一条依附该边的边的指针
} AdjList[MAX_ALVNUMS]; // AdjList 表示邻接表类型

// 邻接表
typedef struct {
    AdjList adjList;
    int numVertexes, numEdges; // 图的当前顶点数和边数
} ALGraph;

int visited[MAX_ALVNUMS]; // 访问标志数组,其初值为"false"

void CreateALGraph(ALGraph *G); // 用邻接表表示法创建无向图
void PrintAMatrix(ALGraph G); // 打印邻接表
void DFSTraverse(ALGraph G, int v); // 非连通图 深度优先搜索遍历
void DFS_AL(ALGraph G, int v); // 邻接矩阵 深度优先搜索遍历(连通图)
void BFSTraverse(ALGraph G, int v); // 非连通图 广度优先搜索遍历
void BFS_AL(ALGraph G, int v); // 邻接矩阵 广度优先搜索遍历(连通图)
int InitQueue(LinkQueue *queue); // 链队的初始化
int EnQueue(LinkQueue *queue, QElemType elem); // 链队入队列
int DeQueue(LinkQueue *queue, QElemType *elem); // 链队出队列
int IsEmpty(LinkQueue *queue); // 判队空

/**
 * <h2>无向图的创建和遍历(无向网同理)</h2>
 * <h3>无向图邻接表特点: <br>
 * 两个顶点确定一条边的位置,此边两点就有必定相邻 <br>
 * @return 0
 */
int main() {

    ALGraph *G;
    G = (ALGraph *) malloc(sizeof(ALGraph)); // 为图G生成内存空间

    CreateALGraph(G); // 创建
    PrintAMatrix(*G); // 输出邻接表
    DFSTraverse(*G, 0); // 深度搜索
    printf("\n");
    BFSTraverse(*G, 1); // 深度搜索

    getchar();
}

// 使用邻接表表示法,创建无向图
void CreateALGraph(ALGraph *G) {

    // 输入总顶点数和边数
    printf("输入无向图的顶点数和边数,用空格分开:");
    scanf("%d %d", &(G->numVertexes), &(G->numEdges));
    getchar();

    // 依次输入顶点的信息,构造表头结点表
    for (int i = 0; i < G->numVertexes; i++) {
/*        printf("输入第%d个顶点信息:", (i + 1));
        scanf("%c", &G->verxs[i]);
        getchar();*/
        G->adjList[i].verxs = i; // 输入顶点值
        G->adjList[i].firstEdge = NULL; // 初始化表头结点的指针域为空
    }

    // 输入各边,构造邻接表
    int arrSub1[] = {0, 0, 1, 1, 2, 2, 3, 4, 5};
    int arrSub2[] = {1, 2, 3, 4, 5, 6, 7, 7, 6};
    for (int i = 0; i < G->numEdges; i++) {
/*        printf("输入第%d条边(空格分开):", (i + 1)); // 输入一条边依附的两个顶点
        scanf("%d %d", &sub1, &sub2); // 确定v1和v2在G中的位置,即顶点在G.verxs中的序号
        getchar();*/

        /*
         * 使用链表中头插法的方式建立邻接表
         */
        // 生成一个新的边结点 *edgeNode
        EdgeNode *edgeNode = (EdgeNode *) malloc(sizeof(EdgeNode));
        // 临接点序号为 sub2
        edgeNode->adjvex = arrSub2[i];
        // 将新结点*edgeNode 插入顶点 v[sub1]的边表头部
        edgeNode->next = G->adjList[arrSub1[i]].firstEdge;
        G->adjList[arrSub1[i]].firstEdge = edgeNode;

        // 生成另一个对称的新的边结点 edgeNode
        edgeNode = (EdgeNode *) malloc(sizeof(EdgeNode));
        // 邻接点序号为 sub1
        edgeNode->adjvex = arrSub1[i];
        // 将新结点 edgeNode 插入顶点 v[sub2]的边表头部
        edgeNode->next = G->adjList[arrSub2[i]].firstEdge;
        G->adjList[arrSub2[i]].firstEdge = edgeNode;
    }
}

// 打印邻接表
void PrintAMatrix(ALGraph G) {
    printf("============= 无向图G的邻接表 =============\n");
    /*
     * 上
     */
    for (int i = 0; i < G.numVertexes; i++) {
        if (i == 0) {
            printf("   .--------.");
            for (EdgeNode *edgeNode = G.adjList[i].firstEdge;
                 edgeNode != NULL; edgeNode = edgeNode->next) {
                printf("   .-------.");
            }
            printf("\n");
        }
        /*
         * 中
         */
        printf(" %d ", i);
        // 打印头结点信息
        printf("| V%d | --|-->", G.adjList[i].verxs);
        // 打印结点信息
        for (EdgeNode *edgeNode = G.adjList[i].firstEdge;
             edgeNode != NULL; edgeNode = edgeNode->next) {
            if (edgeNode->next != NULL) {
                printf("| %d | --|", edgeNode->adjvex);
                printf("-->");
            } else {
                printf("| %d | ^ |", edgeNode->adjvex);
            }
        }
        printf("\n");
        /*
         * 下
         */
        if (i != G.numVertexes - 1) {
            printf("   |----+---|");
            for (EdgeNode *edgeNode = G.adjList[i].firstEdge;
                 edgeNode != NULL; edgeNode = edgeNode->next) {
                printf("   :=======:");
            }
        } else {
            printf("   '--------'");
            for (EdgeNode *edgeNode = G.adjList[i].firstEdge;
                 edgeNode != NULL; edgeNode = edgeNode->next) {
                printf("   '-------'");
            }
        }
        printf("\n");
    }
}

// 对非连通图G做 深度优先遍历
void DFSTraverse(ALGraph G, int v) {
    // 注意:对于以下所有循环次数解析,最多有numVertexes顶点个连通图,所以循环numVertexes次就足够保证整个非连通图都被遍历到
    for (int i = 0; i < G.numVertexes; i++) {
        visited[i] = FALSE; // 访问标志数组初始化
    }
    for (int i = 0; i < G.numVertexes; i++) { // 循环调用遍历连通图的算法函数
        if (!visited[i]) {
            DFS_AL(G, v); // 对尚未访问的顶点调用DFS_AL()
        }
    }
}

// 图G为邻接表类型,从第v个顶点出发 深度优先搜索遍历 图G
void DFS_AL(ALGraph G, int v) {
    printf("V%d ", v);
    visited[v] = TRUE; // 访问第v个顶点,并置访问标志数组相应分量值为true
    EdgeNode *edgeNode = G.adjList[v].firstEdge; // edgeNode 指向 v 的边链表的第一个边结点
    while (edgeNode != NULL) { // 边结点非空

        int i = edgeNode->adjvex; // 表示 i 是 v的邻接点
        if (!visited[i]) { // 如果 i 未访问,则递归调用DFS_AL
            DFS_AL(G, i);
        }

        edgeNode = edgeNode->next; // edgeNode 指向下一个边结点
    }
}

// 对非连通图G做 广度优先遍历
void BFSTraverse(ALGraph G, int v) {
    // 注意:对于以下所有循环次数解析,最多有numVertexes顶点个连通图,所以循环numVertexes次就足够保证整个非连通图都被遍历到
    for (int i = 0; i < G.numVertexes; i++) {
        visited[i] = FALSE; // 访问标志数组初始化
    }
    for (int i = 0; i < G.numVertexes; i++) { // 循环调用遍历连通图的算法函数
        if (!visited[i]) {
            BFS_AL(G, v); // 对尚未访问的顶点调用BFS_AL()
        }
    }
}

// 图G为邻接表类型,从第v个顶点出发 广度优先搜索遍历 图G
void BFS_AL(ALGraph G, int v) {
    printf("V%d ", v);
    visited[v] = TRUE; // 访问第v个顶点,并置访问标志数组相应分量值为 true
    LinkQueue queue;
    InitQueue(&queue); // 辅助队列queue初始化,置空
    EnQueue(&queue, v); // v 进队
    int u;
    while (!IsEmpty(&queue)) {
        DeQueue(&queue, &u); // 队头元素出队并置为 u
        EdgeNode *edgeNode = G.adjList[u].firstEdge;
        while (edgeNode != NULL) {
            if ((!visited[edgeNode->adjvex])) {
                printf("V%d ", edgeNode->adjvex);
                visited[edgeNode->adjvex] = TRUE; // 访问第 edgeNode->adjvex 个顶点,并置访问标志数组相应分量值为true
                EnQueue(&queue, edgeNode->adjvex);  // edgeNode->adjvex 进队
            }
            edgeNode = edgeNode->next;
        }
    }
}

// 构造一个空队列
int InitQueue(LinkQueue *queue) {
    queue->front = queue->rear = (QueuePtr) malloc(sizeof(QNode)); // 生成新结点作为头结点,队头和队尾指针指向此结点
    if (!(queue->front)) {
        return FALSE;
    }
    queue->front->next = NULL; // 头结点指针域置空
    return TRUE;
}

// 插入元素elem为queue的新的队尾元素
int EnQueue(LinkQueue *queue, QElemType elem) {
    QueuePtr temp;
    temp = (QueuePtr) malloc(sizeof(QNode)); // 为入队元素分配结点空间,用指针temp指向
    if (!temp) {
        return FALSE;
    }
    temp->data = elem; // 将新结点数据域置为elem
    temp->next = NULL;
    queue->rear->next = temp; // 将新结点插入到队尾
    queue->rear = temp; // 修改队尾指针
    return TRUE;
}

// 删除queue的队头元素,用elem返回其值
int DeQueue(LinkQueue *queue, QElemType *elem) {
    if (IsEmpty(queue)) {
        return FALSE; // 若队列空,则返回FALSE
    }
    QueuePtr pQ;
    pQ = queue->front->next; // pQ指向队头元素
    *elem = pQ->data; // *elem保存队头元素的值
    queue->front->next = pQ->next; // 修改头结点的指针域
    if (queue->rear == pQ) {
        queue->rear = queue->front; // 最后一个元素被删,队尾指针指向头结点
    }
    free(pQ); // 释放原队头元素空间
    return TRUE;
}

// 判队空
int IsEmpty(LinkQueue *queue) {
    return queue->front == queue->rear;
}

运行结果

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小丶象

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

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

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

打赏作者

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

抵扣说明:

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

余额充值