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王道考研书

相关推荐
生成论实验室3 分钟前
《源·觉·知·行·事·物:生成论视域下的统一认知语法》导论:在破碎的世界寻找统一语法
人工智能·科技·算法·架构·创业创新
承渊政道4 分钟前
【动态规划算法】(两个数组的DP问题深度剖析与求解方法)
数据结构·c++·学习·算法·leetcode·动态规划·哈希算法
杨连江8 分钟前
原子级平面限域协同晶核诱导定向生长单层鳞片石墨的研究
算法
MATLAB代码顾问14 分钟前
混合粒子群-模拟退火算法(HPSO-SA)求解作业车间调度问题——附MATLAB代码
算法·matlab·模拟退火算法
Felven18 分钟前
C. Prefix Min and Suffix Max
算法
加农炮手Jinx18 分钟前
LeetCode 26. Remove Duplicates from Sorted Array 题解
算法·leetcode·力扣
加农炮手Jinx19 分钟前
LeetCode 88. Merge Sorted Array 题解
算法·leetcode·力扣
Hhy_110719 分钟前
【从零开始学习数据结构 ④】:栈 ——后进先出的艺术
c语言·数据结构·学习·visual studio
格林威19 分钟前
线阵工业相机:如何计算线阵相机的行频(Line Rate)?公式+实例
开发语言·人工智能·数码相机·算法·计算机视觉·工业相机·线阵相机
2501_9271682921 分钟前
手机号测吉凶:尾数722手机号吉凶
笔记