跳转至

FB1-求无向连通图中距离顶点v最远的结点

[[#计算无向连通图的直径(任意两点间最短路径的最大值)]]

代码实现

求无向连通图中距离顶点v最远的结点

C
/**
 * 求无向连通图中距离顶点v最远的结点
 * 
 * 算法思想:
 * 利用BFS的层次遍历特性,最后被访问的节点就是距离起始节点最远的节点
 * 
 * 理论依据:
 * 1. BFS按层次遍历,先访问距离为1的节点,再访问距离为2的节点...
 * 2. 最后一个出队的节点一定是距离起始节点最远的节点
 * 3. 这是因为BFS保证了按最短路径距离的顺序访问节点
 * 
 * 应用场景:
 * - 网络中最远的服务器
 * - 社交网络中关系最远的人
 * - 树的直径计算(需要两次BFS)
 */

int findFarthestNode(Graph G, int v) {
    // 访问标记数组,防止重复访问节点
    int visit[maxsize] = {0};

    // 循环队列,用于BFS层次遍历
    int que[maxsize];
    int front = 0, rear = 0;

    // 标记起始节点v为已访问
    visit[v] = 1;
    // 将起始节点入队
    que[++rear] = v;

    // 用于记录最后访问的节点
    int lastVisited = v;

    // BFS遍历所有可达节点
    while (front != rear) {
        // 出队操作:获取队首节点
        v = que[++front];
        // 更新最后访问的节点
        lastVisited = v;

        // 遍历当前节点的所有邻接节点
        Node p = G.adjlist[v].firstarc;
        for (; p != NULL; p = p->nextarc) {
            // 如果邻接节点未被访问
            if (visit[p->adjvex] == 0) {
                // 标记为已访问
                visit[p->adjvex] = 1;
                // 将邻接节点入队
                que[++rear] = p->adjvex;
            }
        }
    }

    // 返回最后访问的节点,即距离起始节点最远的节点
    return lastVisited;
}

计算无向连通图的直径(任意两点间最短路径的最大值)

计算两点间最短距离(使用BFS)

C
/**
 * 计算无向连通图的直径(任意两点间最短路径的最大值)
 * 
 * 算法思想:
 * 1. 从任意节点开始,使用BFS找到距离它最远的节点u
 * 2. 从节点u开始,再次使用BFS找到距离u最远的节点v
 * 3. u到v的距离就是图的直径
 * 
 * 理论依据:
 * 这是基于树的直径算法,对于连通图同样适用
 */
int calculateGraphDiameter(Graph G) {
    // 第一次BFS:从节点0开始找到最远节点
    int farthestNode1 = findFarthestNode(G, 0);

    // 第二次BFS:从第一次找到的最远节点开始,找到真正的最远节点
    int farthestNode2 = findFarthestNode(G, farthestNode1);

    // 第三次BFS:计算两个最远节点间的距离
    return getDistance(G, farthestNode1, farthestNode2);
}

/**
 * 函数:getDistance
 * 功能:使用广度优先搜索(BFS)算法计算无向/有向无权图中两个顶点之间的最短路径长度(边数)
 * 参数:
 *   G     : 图的邻接表表示
 *   start : 起始顶点编号
 *   end   : 目标顶点编号
 * 返回值:
 *   若存在路径,返回从 start 到 end 的最短距离(边数)
 *   若不可达,返回 -1(虽然题目假设图连通,但仍保留健壮性处理)
 *
 * 注意:本算法适用于无权图。若为有权图,应使用 Dijkstra 或 Bellman-Ford 等算法。
 */
int getDistance(Graph G, int start, int end) {
    // 特殊情况:起点和终点相同,距离为0
    if (start == end) return 0;

    // 访问标记数组,visit[i] == 1 表示顶点 i 已被访问
    int visit[maxsize] = {0};

    // 循环队列,用于实现 BFS 的层次遍历
    int que[maxsize];
    int front = 0, rear = 0;  // 队首和队尾指针(初始为空)

    // 距离数组,distance[i] 表示从 start 到顶点 i 的最短距离
    int distance[maxsize] = {0};

    // 标记起始节点为已访问,并入队
    visit[start] = 1;
    que[++rear] = start;  // 入队操作:先移动 rear 再赋值(1-indexed 风格)

    // 当队列非空时持续进行 BFS
    while (front != rear) {
        // 出队一个顶点
        int current = que[++front];  // 先移动 front 再取值

        // 如果当前顶点就是目标顶点,直接返回其距离
        if (current == end) {
            return distance[current];
        }

        // 遍历当前顶点的所有邻接点(通过邻接表)
        Node p = G.adjlist[current].firstarc;
        while (p != NULL) {
            int neighbor = p->adjvex;  // 获取邻接顶点编号

            // 若该邻接点未被访问,则进行处理
            if (visit[neighbor] == 0) {
                visit[neighbor] = 1;  // 标记为已访问(避免重复入队)

                // 距离 = 当前节点距离 + 1(无权图的边权为1)
                distance[neighbor] = distance[current] + 1;

                // 将邻接点入队,等待后续扩展
                que[++rear] = neighbor;
            }
            p = p->nextarc;  // 移动到下一个邻接边
        }
    }

    // 若 BFS 结束仍未找到目标节点,说明不可达
    return -1;
}

复杂度分析

时间复杂度:O(V + E)

  • V: 顶点数,E: 边数
  • 每个顶点最多入队和出队一次:O(V)
  • 每条边最多被检查一次:O(E)
  • 总时间复杂度:O(V + E)

空间复杂度:O(V)

  • visit[]数组:O(V)
  • que[]队列:O(V)
  • distance[]数组(在距离计算中使用):O(V)
  • 总空间复杂度:O(V)

图解说明

Text Only
示例图结构:
    1
   / \
  0   3
   \ / \
    2   4
       /
      5

从节点0开始找最远节点的BFS过程:

第1层(距离0):[0]
访问顺序:0
队列状态:[0] → []

第2层(距离1):[1, 2]  
访问顺序:0 → 1 → 2
队列状态:[1,2] → [2] → []

第3层(距离2):[3]
访问顺序:0 → 1 → 2 → 3
队列状态:[3] → []

第4层(距离3):[4]
访问顺序:0 → 1 → 2 → 3 → 4
队列状态:[4] → []

第5层(距离4):[5]
访问顺序:0 → 1 → 2 → 3 → 4 → 5
队列状态:[5] → []

最后访问的节点是5,它就是距离节点0最远的节点
距离为4(路径:0→1→3→4→5 或 0→2→3→4→5)

图的直径计算过程:
1. 从节点0开始BFS → 最远节点是5
2. 从节点5开始BFS → 最远节点可能是0或1或2
3. 计算5到最远节点的距离 → 图的直径

相关知识点延伸

1. 树的直径算法

C
1
2
3
4
5
6
7
8
9
// 树的直径:两次BFS算法
int treeDiameter(Graph tree) {
    // 第一次BFS找最远点
    int farthest1 = findFarthestNode(tree, 0);
    // 第二次BFS找真正的最远点
    int farthest2 = findFarthestNode(tree, farthest1);
    // 计算距离
    return getDistance(tree, farthest1, farthest2);
}

2. 多源BFS优化

C
// 同时从多个节点开始的BFS
void multiSourceBFS(Graph G, int sources[], int sourceCount) {
    int visit[maxsize] = {0};
    int que[maxsize];
    int front = 0, rear = 0;

    // 所有源节点同时入队
    for (int i = 0; i < sourceCount; i++) {
        visit[sources[i]] = 1;
        que[++rear] = sources[i];
    }

    // 同时进行BFS扩展
    while (front != rear) {
        int current = que[++front];
        printf("访问节点: %d\n", current);

        Node p = G.adjlist[current].firstarc;
        for (; p != NULL; p = p->nextarc) {
            if (visit[p->adjvex] == 0) {
                visit[p->adjvex] = 1;
                que[++rear] = p->adjvex;
            }
        }
    }
}

3. 应用场景扩展

C
// 网络中的关键节点识别
int findCriticalNode(Graph G) {
    int maxDistance = -1;
    int criticalNode = 0;

    // 对每个节点计算其到最远节点的距离
    for (int i = 0; i < G.numver; i++) {
        int farthest = findFarthestNode(G, i);
        int distance = getDistance(G, i, farthest);

        if (distance > maxDistance) {
            maxDistance = distance;
            criticalNode = i;
        }
    }

    return criticalNode;
}

4. 最小生成树中的应用

在最小生成树中,直径的计算对于网络设计非常重要: - 通信网络:最小化最大延迟 - 交通网络:最小化最远站点间的旅行时间 - 分布式系统:最小化节点间最大通信跳数

5. 算法正确性证明要点

  1. BFS的层次性:保证按距离递增顺序访问节点
  2. 连通性保证:无向连通图确保所有节点可达
  3. 最优性:BFS保证找到的是最短路径距离
  4. 终止性:有限图确保算法在有限步内终止

这个算法是图论中基础而重要的应用,体现了BFS在求解最短路径相关问题中的强大能力。