26考研——图_图的代码实操(6)

408答疑


文章目录


五、图的代码实操

图的存储

邻接矩阵

结构定义
c 复制代码
typedef struct GraphMtx {
    int numV; // 顶点个数
    int numE; // 边的条数
    ElemType vList[MAX_VERTEX_SIZE]; // 顶点空间
    int edge[MAX_VERTEX_SIZE][MAX_VERTEX_SIZE]; // 边的矩阵
} GraphMtx;
初始化
  • 初始化图的顶点数和边数为 0,并将邻接矩阵的所有元素设置为 0。
c 复制代码
void initGraph(GraphMtx *g) {
    g->numV = 0;
    g->numE = 0;
    for (int i = 0; i < MAX_VERTEX_SIZE; ++i) {
        for (int j = 0; j < MAX_VERTEX_SIZE; ++j)
           g->edge[i][j] = 0;
    }
}
插入顶点
  • 在图中插入一个新顶点,如果顶点数超过最大值则不插入。
c 复制代码
void insertVertex(GraphMtx *g, ElemType vertex) {
    if (g->numV >= MAX_VERTEX_SIZE)
        return;

    g->vList[g->numV] = vertex;
    g->numV++;
}
获取顶点位置
  • 返回顶点在顶点列表中的位置,如果不存在则返回 -1。
c 复制代码
int getPosVertex(GraphMtx *g, ElemType vertex) {
    for (int i = 0; i < g->numV; ++i) {
        if (g->vList[i] == vertex)
            return i;
    }
    return -1; // 没有要查找的顶点
}
在顶点 v1 和 v2 之间插入边
  • 在图中插入一条从顶点 vertex1 到顶点 vertex2 的边。
c 复制代码
void insertEdge(GraphMtx *g, ElemType vertex1, ElemType vertex2) {
    int v1 = getPosVertex(g, vertex1);
    int v2 = getPosVertex(g, vertex2);

    // 插入v1->v2边
    g->edge[v1][v2] = 1;

	// 若是无向图,则插入v2->v1边
	//g->edge[v2][v1] = 1;
	
    g->numE++;
}
获取第一个邻接顶点
  • 获取给定顶点的第一个邻接顶点。
c 复制代码
int getFirstNeighbor(GraphMtx *g, ElemType vertex) {
    int v = getPosVertex(g, vertex);
    if (v == -1)
        return -1; // 没有邻接顶点

    for (int j = 0; j < g->numV; ++j) {
        if (g->edge[v][j] == 1)
            return j; // 返回邻接顶点
    }
    return -1; // 没有邻接顶点
}
获取下一个邻接顶点
  • 获取给定顶点的下一个邻接顶点。
c 复制代码
int getNextNeighbor(GraphMtx *g, ElemType vertex1, ElemType vertex2) {
    int v1 = getPosVertex(g, vertex1);
    int v2 = getPosVertex(g, vertex2);
    if (v1 == -1 || v2 == -1)
        return -1;

    for (int j = v2 + 1; j < g->numV; ++j) {
        if (g->edge[v1][j] == 1)
            return j;
    }
    return -1;
}
显示图
  • 显示图的邻接矩阵。
c 复制代码
void showGraph(GraphMtx *g) {
    printf(" ");
    for (int i = 0; i < g->numV; ++i)
        printf(" %c", g->vList[i]);
    printf("\n");

    for (int i = 0; i < g->numV; ++i) {
        printf("%c ", g->vList[i]);
        for (int j = 0; j < g->numV; ++j) {
            printf("%d ", g->edge[i][j]);
        }
        printf("\n");
    }
}

邻接表

结构定义
c 复制代码
typedef struct Edge {
    int dest; // 目标顶点的下标
    struct Edge *next; // 结点指针
} Edge;

typedef struct Vertex {
    ElemType data;   // 顶点数据
    Edge *first; // 指向边的起始指针
} Vertex;

typedef struct GraphLnk {
    int numV; // 顶点数
    int numE; // 边的条数
    Vertex nodeTable[MAX_VERTEX_SIZE];
} GraphLnk;
初始化图
  • 初始化图的顶点数和边数为 0,并将邻接表的所有元素设置为 NULL。
c 复制代码
void initGraph(GraphLnk *g) {
    g->numV = 0;
    g->numE = 0;
    for (int i = 0; i < MAX_VERTEX_SIZE; ++i)
        g->nodeTable[i].first = NULL;
}
插入顶点
  • 在图中插入一个新顶点,如果顶点数超过最大值则不插入。
c 复制代码
void insertVertex(GraphLnk *g, ElemType vertex) {
    if (g->numV >= MAX_VERTEX_SIZE)
        return;

    g->nodeTable[g->numV].data = vertex;
    g->numV++;
}
获取顶点位置
  • 返回顶点在顶点列表中的位置,如果不存在则返回 -1。
c 复制代码
int getPosVertex(GraphLnk *g, ElemType vertex) {
    for (int i = 0; i < g->numV; ++i) {
        if (g->nodeTable[i].data == vertex)
            return i;
    }
    return -1; // 没有要查找的顶点
}
在顶点 v1 和 v2 之间插入边
  • 在图中插入一条从顶点 vertex1 到顶点 vertex2 的边。
c 复制代码
void insertEdge(GraphLnk *g, ElemType vertex1, ElemType vertex2) {
    int v1 = getPosVertex(g, vertex1);
    int v2 = getPosVertex(g, vertex2);

    // v1->v2
    Edge *e = (Edge*)malloc(sizeof(Edge));
    e->dest = v2;
    e->next = g->nodeTable[v1].first;
    g->nodeTable[v1].first = e;

    // 	若是无向图,则插入v2->v1边
    //e = (Edge*)malloc(sizeof(Edge));
    //e->dest = v1;
    //e->next = g->nodeTable[v2].first;
    //g->nodeTable[v2].first = e;
    
    g->numE++;
}
获取第一个邻接顶点
  • 获取给定顶点的第一个邻接顶点。
c 复制代码
int getFirstNeighbor(GraphLnk *g, ElemType vertex) {
    int v = getPosVertex(g, vertex);
    if (v == -1)
        return -1; // 没有邻接顶点

    if (g->nodeTable[v].first != NULL)
        return g->nodeTable[v].first->dest;
    return -1; // 没有邻接顶点
}
获取下一个邻接顶点
  • 获取给定顶点的下一个邻接顶点。
c 复制代码
int getNextNeighbor(GraphLnk *g, ElemType vertex1, ElemType vertex2) {
    int v1 = getPosVertex(g, vertex1);
    int v2 = getPosVertex(g, vertex2);
    if (v1 == -1 || v2 == -1)
        return -1;

    Edge *p = g->nodeTable[v1].first;
    while (p->dest != v2)
        p = p->next;
    p = p->next;
    if (p != NULL)
        return p->dest;
    return -1;
}
显示图
  • 显示图的邻接表。
c 复制代码
void showGraph(GraphLnk *g) {
    for (int i = 0; i < g->numV; ++i) {
        printf("%d %c : ", i, g->nodeTable[i].data);
        Edge *p = g->nodeTable[i].first;
        while (p != NULL) {
            printf("%d->", p->dest);
            p = p->next;
        }
        printf("Nil.\n");
    }
}

图的遍历

深度优先遍历

算法思想

深度优先遍历(DFS)是一种用于遍历或搜索图或树的算法。该算法使用栈(显式或递归)来实现非递归约的遍历过程。算法从图中的某一顶点开始,沿着路径深入访问尽可能远的顶点,直到不能再深入为止,然后回溯并访问其他路径。

算法步骤
  1. 访问起始顶点:选择图中的某一顶点作为起始点,并标记为已访问。
  2. 访问邻接顶点:访问该顶点的所有未被访问的邻接顶点,对每个邻接顶点递归地调用DFS。
  3. 回溯:当当前路径无法继续深入时,回溯到最近的已访问顶点,检查是否有其他未访问的邻接顶点。
  4. 重复过程:重复上述过程,直到图中所有顶点都被访问。
算法特点
  • DFS 相当于二叉树中的前序遍历。
  • 使用临时空间来标记结点是否被访问过。
  • 适用于图的连通性检测、拓扑排序等场景。
邻接矩阵
c 复制代码
void DFS(GraphMtx *g, ElemType vertex) {
    printf("%c->", vertex);
    int v = getPosVertex(g, vertex);
    visit[v] = 1; // 标记顶点

    int w = getFirstNeighbor(g, vertex);
    while (w != -1) {
        if (!visit[w]) {
            DFS(g, g->vList[w]);
        }
        w = getNextNeighbor(g, g->vList[v], g->vList[w]); //(g, v1, v2)
    }
}
邻接表
c 复制代码
void DFS(GraphLnk *g, ElemType vertex) {
    printf("%c->", vertex);
    int v = getPosVertex(g, vertex);
    visit[v] = 1; // 标记顶点

    int w = getFirstNeighbor(g, vertex);
    while (w != -1) {
        if (!visit[w]) {
            DFS(g, g->nodeTable[w].data);
        }
        w = getNextNeighbor(g, g->nodeTable[v].data, g->nodeTable[w].data); //(g, v1, v2)
    }
}

广度优先遍历

算法思想
  1. 初始化

    • 标记起始顶点为已访问。
    • 将起始顶点入队。
  2. 遍历队列:当队列不为空时,执行以下操作。

    • 从队列中取出一个顶点。
    • 访问该顶点的所有未访问过的邻接顶点。
    • 将这些邻接顶点标记为已访问,并加入队列。
  3. 重复步骤2,直到队列为空,即所有可达顶点都被访问。

邻接矩阵
c 复制代码
void BFS(GraphMtx *g, ElemType vertex) {
    printf("%c->", vertex);
    int v = getPosVertex(g, vertex);
    visit[v] = 1;

    int Q[MAX_VERTEX_SIZE];
    int front = 0, rear = 0;
    // 入队
    Q[rear++] = v;

    while (front != rear) { // 队列不空
        v = Q[front++]; // 出队并保存顶点
        int w = getFirstNeighbor(g, g->vList[v]);
        while (w != -1) {
            if (!visit[w]) {
                printf("%c->", g->vList[w]);
                visit[w] = 1;
                Q[rear++] = w;
            }
            w = getNextNeighbor(g, g->vList[v], g->vList[w]);
        }
    }
}
邻接表
c 复制代码
void BFS(GraphLnk *g, ElemType vertex) {
    printf("%c->", vertex);
    int v = getPosVertex(g, vertex);
    visit[v] = 1;

    int Q[MAX_VERTEX_SIZE];
    int front = 0, rear = 0;
    // 入队
    Q[rear++] = v;

    while (front != rear) { // 队列不空
        v = Q[front++]; // 出队并保存顶点
        int w = getFirstNeighbor(g, g->nodeTable[v].data);
        while (w != -1) {
            if (!visit[w]) {
                printf("%c->", g->nodeTable[w].data);
                visit[w] = 1;
                Q[rear++] = w;
            }
            w = getNextNeighbor(g, g->nodeTable[v].data, g->nodeTable[w].data);
        }
    }
}

图的应用

BFS算法求解单源最短路径问题

  1. 初始化

    • 设置所有顶点的最短路径长度为无穷大,起始顶点的距离为0。
    • 标记起始顶点为已访问。
    • 将起始顶点加入队列。
  2. BFS遍历:当队列非空时,执行以下操作。

    • 从队列中取出一个顶点。
    • 遍历该顶点的所有未访问邻接顶点。
    • 更新邻接顶点的最短路径长度。
    • 标记邻接顶点为已访问。
    • 将邻接顶点加入队列。
  3. 完成遍历

    • 重复步骤2,直到队列为空。
    • 此时,数组d中存储的即为从起始顶点到所有可达顶点的最短路径长度。
c 复制代码
void BFS_MIN_Distance(GraphMtx G, int u) {
    // d[i]表示从u到i结点的最短路径长度
    int d[MAX_VERTEX_SIZE]; 
    int visited[MAX_VERTEX_SIZE]; // 访问标记数组
    Queue Q; // 定义队列Q

    for (int i = 0; i < G.numV; ++i) {
        d[i] = ∞; // 初始化路径长度为无穷大
        visited[i] = FALSE; // 初始化所有顶点为未访问
    }
    d[u] = 0; // 起始顶点到自身的距离为0
    visited[u] = TRUE; // 标记起始顶点为已访问
    EnQueue(Q, u); // 将起始顶点入队

    while (!IsEmpty(Q)) { // BFS算法主过程
        int v;
        DeQueue(Q, v); // 队头元素v出队
        for (int w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w)) {
            if (!visited[w]) { // w为v的尚未访问的邻接顶点
                visited[w] = TRUE; // 标记为已访问
                d[w] = d[v] + G.edge[v][w]; // 路径长度加v到w的边的权重
                EnQueue(Q, w); // 顶点w入队
            }
        }
    }
}

拓扑排序

  • 使用栈实现图的拓扑排序,输出顶点的拓扑序列。
c 复制代码
void topSort(GraphMtx *g) {
    // 统计每一个顶点的入度
    for (int j = 0; j < g->numV; ++j) {
        for (int i = 0; i < g->numV; ++i) {
            if (g->edge[i][j] == 1)
                inDegree[j]++;
        }
    }

    // 定义一个栈
    ElemType stack[MAX_VERTEX_SIZE] = { 0 };
    int top = 0;

    // 把入度为0的顶点入栈
    for (int i = 0; i < g->numV; ++i) {
        if (inDegree[i] == 0) // 入度为0
            stack[top++] = i;
    }

    for (int i = 0; i < g->numV; ++i) { // 排序输出每一个顶点
        if (top == 0) {
            printf("图有回路.\n");
            return;
        }

        int v = stack[--top];
        printf("%c->", g->vList[v]);

        int w = getFirstNeighbor(g, g->vList[v]);
        while (w != -1) {
            if (--inDegree[w] == 0)
                stack[top++] = w;
            w = getNextNeighbor(g, g->vList[v], g->vList[w]);
        }
    }
}

六、参考资料

鲍鱼科技课件

b站免费王道课后题讲解:

网课全程班:

26王道考研书

相关推荐
雨出20 分钟前
算法学习第十七天:LRU缓存与布隆过滤器
学习·算法·缓存
V---scwantop---信35 分钟前
复古卡通纹理噪点印刷打印照片效果PS特效样机 Texturizer Original Design Effect
笔记
郭涤生1 小时前
第二章:影响优化的计算机行为_《C++性能优化指南》notes
开发语言·c++·笔记·性能优化
oioihoii1 小时前
深入解析 C++20 中的 std::bind_front:高效函数绑定与参数前置
java·算法·c++20
程序员yt1 小时前
双非一本Java方向,学完感觉Java技术含量不高,考研换方向如何选择?
java·开发语言·考研
MobiCetus1 小时前
如何一键安装所有Python项目的依赖!
开发语言·jvm·c++·人工智能·python·算法·机器学习
思麟呀1 小时前
String类的模拟实现
开发语言·c++·算法
Dante7982 小时前
判断质数及其优化方法
开发语言·c++·算法
ylfhpy2 小时前
Java面试黄金宝典19
java·开发语言·数据结构·算法·面试·面经
吴梓穆2 小时前
UE4学习笔记 FPS游戏制作32 主菜单,暂停游戏,显示鼠标指针
笔记·学习·ue4