跳转至

FB2-判断有向图中两个顶点间是否存在路径

代码实现

DFS

C
/**
 * 判断有向图中两个顶点间是否存在路径 - DFS实现
 * 
 * 算法思想:
 * 使用深度优先搜索从起始顶点Vi开始遍历,看是否能到达目标顶点Vj
 * 
 * 核心思路:
 * 1. 从顶点i开始进行DFS遍历
 * 2. 在遍历过程中,如果遇到目标顶点j,则说明存在路径
 * 3. 使用visit数组避免重复访问,防止无限递归
 * 4. 通过指针参数res返回查找结果
 * 
 * @param G 图的邻接表表示
 * @param i 起始顶点
 * @param j 目标顶点
 * @param visit 访问标记数组
 * @param res 结果指针,用于返回是否存在路径
 */
void pathExistsDFS(Graph G, int i, int j, int visit[], bool* res) {
    // 基础情况:如果当前顶点就是目标顶点,说明找到路径
    if (i == j) {
        *res = true;
        return;  // 可以提前返回,优化性能
    }

    // 标记当前顶点i为已访问,防止重复访问形成环
    visit[i] = 1;

    // 获取当前顶点的第一条邻接边
    Node p = G.adjlist[i].firstarc;

    // 遍历当前顶点的所有邻接顶点
    for (; p != NULL; p = p->nextarc) {
        // 如果邻接顶点未被访问过
        if (visit[p->adjvex] == 0) {
            // 递归搜索从邻接顶点到目标顶点j的路径
            pathExistsDFS(G, p->adjvex, j, visit, res);

            // 如果已经找到路径,可以提前返回(可选优化)
            if (*res == true) {
                return;
            }
        }
    }
}

BFS

C
/**
 * 判断有向图中两个顶点间是否存在路径 - BFS实现
 * 
 * 算法思想:
 * 使用广度优先搜索从起始顶点Vi开始层次遍历,看是否能到达目标顶点Vj
 * 
 * 核心思路:
 * 1. 从顶点i开始进行BFS遍历
 * 2. 按层次扩展,将可达顶点逐层加入队列
 * 3. 如果在遍历过程中遇到目标顶点j,则说明存在路径
 * 4. 如果队列为空仍未找到目标顶点,则不存在路径
 * 
 * @param G 图的邻接表表示
 * @param i 起始顶点
 * @param j 目标顶点
 * @return true表示存在路径,false表示不存在路径
 */
bool pathExistsBFS(Graph G, int i, int j) {
    // 特殊情况:起始顶点就是目标顶点
    if (i == j) {
        return true;
    }

    // 访问标记数组,初始化为0(未访问)
    int visit[maxsize] = {0};

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

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

    // 当队列不为空时继续搜索
    while (front != rear) {
        // 出队操作:获取队首顶点
        i = que[front++];

        // 检查当前顶点是否为目标顶点
        if (i == j) {
            return true;  // 找到目标顶点,存在路径
        }

        // 获取当前顶点的第一条邻接边
        Node p = G.adjlist[i].firstarc;

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

    // 队列为空仍未找到目标顶点,说明不存在路径
    return false;
}
C
/**
 * 更简洁的DFS实现(直接返回结果,不使用指针参数)
 * 
 * @param G 图的邻接表表示
 * @param i 起始顶点
 * @param j 目标顶点
 * @param visit 访问标记数组
 * @return true表示存在路径,false表示不存在路径
 */
bool pathExistsDFS_Simple(Graph G, int i, int j, int visit[]) {
    // 基础情况:如果当前顶点就是目标顶点,说明找到路径
    if (i == j) {
        return true;
    }

    // 标记当前顶点为已访问
    visit[i] = 1;

    // 遍历当前顶点的所有邻接顶点
    Node p = G.adjlist[i].firstarc;
    for (; p != NULL; p = p->nextarc) {
        // 如果邻接顶点未被访问,递归搜索
        if (visit[p->adjvex] == 0) {
            if (pathExistsDFS_Simple(G, p->adjvex, j, visit)) {
                return true;  // 找到路径,直接返回
            }
        }
    }

    // 未找到路径
    return false;
}
C
/**
 * 双向BFS实现(优化版本)
 * 
 * 算法思想:
 * 同时从起点和终点开始进行BFS,当两个搜索相遇时说明存在路径
 * 可以在某些情况下减少搜索空间
 * 
 * @param G 图的邻接表表示
 * @param i 起始顶点
 * @param j 目标顶点
 * @return true表示存在路径,false表示不存在路径
 */
bool pathExistsBidirectionalBFS(Graph G, int i, int j) {
    if (i == j) return true;

    int visit_start[maxsize] = {0};  // 从起点开始的访问标记
    int visit_end[maxsize] = {0};    // 从终点开始的访问标记

    int que_start[maxsize], que_end[maxsize];
    int front_start = 0, rear_start = 0;
    int front_end = 0, rear_end = 0;

    // 起点BFS初始化
    que_start[rear_start++] = i;
    visit_start[i] = 1;

    // 终点BFS初始化
    que_end[rear_end++] = j;
    visit_end[j] = 1;

    // 交替进行双向BFS
    while (front_start != rear_start && front_end != rear_end) {
        // 从起点开始的BFS一步
        int current_start = que_start[front_start++];
        Node p_start = G.adjlist[current_start].firstarc;
        for (; p_start != NULL; p_start = p_start->nextarc) {
            int adj_vertex = p_start->adjvex;
            if (visit_start[adj_vertex] == 0) {
                // 如果该顶点已被终点BFS访问过,说明路径存在
                if (visit_end[adj_vertex] == 1) {
                    return true;
                }
                visit_start[adj_vertex] = 1;
                que_start[rear_start++] = adj_vertex;
            }
        }

        // 从终点开始的BFS一步
        int current_end = que_end[front_end++];
        Node p_end = G.adjlist[current_end].firstarc;
        for (; p_end != NULL; p_end = p_end->nextarc) {
            int adj_vertex = p_end->adjvex;
            if (visit_end[adj_vertex] == 0) {
                // 如果该顶点已被起点BFS访问过,说明路径存在
                if (visit_start[adj_vertex] == 1) {
                    return true;
                }
                visit_end[adj_vertex] = 1;
                que_end[rear_end++] = adj_vertex;
            }
        }
    }

    return false;
}

复杂度分析

DFS方法复杂度

  • 时间复杂度:O(V + E)
  • 最坏情况下需要访问所有顶点和边
  • V: 顶点数,E: 边数
  • 空间复杂度:O(V)
  • visit数组:O(V)
  • 递归调用栈:最坏情况O(V)

BFS方法复杂度

  • 时间复杂度:O(V + E)
  • 同样需要访问所有可达的顶点和边
  • 空间复杂度:O(V)
  • visit数组:O(V)
  • 队列空间:最坏情况O(V)

图解说明

Text Only
示例有向图结构(邻接表表示):
0 -> 1 -> 2
1 -> 3
2 -> 3 -> 4
3 -> 4
4 -> 

判断从顶点0到顶点4是否存在路径:

DFS执行过程:
调用pathExistsDFS(0, 4):
├── 访问0,visit[0]=1
├── 遍历0的邻接点:1, 2
├── 调用pathExistsDFS(1, 4)
│   ├── 访问1,visit[1]=1
│   ├── 遍历1的邻接点:3
│   ├── 调用pathExistsDFS(3, 4)
│   │   ├── 访问3,visit[3]=1
│   │   ├── 遍历3的邻接点:4
│   │   ├── 调用pathExistsDFS(4, 4)
│   │   │   ├── i==j,设置res=true
│   │   │   └── 返回
│   │   └── 返回
│   └── 返回
└── 返回

BFS执行过程:
队列:[0] → 访问0
队列:[1,2] → 访问1
队列:[2,3] → 访问2
队列:[3,3,4] → 访问3
队列:[3,4,4] → 访问3
队列:[4,4] → 访问4 → 找到目标,返回true

搜索路径图:
0 ──→ 1 ──→ 3 ──→ 4
│              ↗
└────→ 2 ──────┘

相关知识点延伸

1. 两种方法的比较

特性 DFS BFS
搜索方式 纵深探索 层次扩展
内存使用 递归栈 显式队列
路径特性 可能找到较长路径 找到最短路径
实现复杂度 递归简洁 迭代直观

2. 最短路径查找

C
// 使用BFS查找最短路径长度
int shortestPathLength(Graph G, int i, int j) {
    if (i == j) return 0;

    int visit[maxsize] = {0};
    int distance[maxsize] = {0};  // 记录到起始点的距离
    int que[maxsize];
    int front = 0, rear = 0;

    que[rear++] = i;
    visit[i] = 1;

    while (front != rear) {
        int current = que[front++];

        if (current == j) {
            return distance[current];  // 返回最短距离
        }

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

    return -1;  // 不可达
}

3. 实际应用场景

C
// 社交网络中的好友关系判断
bool areConnected(UserGraph G, int user1, int user2) {
    return pathExistsBFS(G, user1, user2);
}

// 网络路由可达性检查
bool isReachable(NetworkGraph G, int source, int destination) {
    return pathExistsDFS(G, source, destination, visit_array, &result);
}

// 依赖关系检查(任务调度)
bool hasDependency(TaskGraph G, int task1, int task2) {
    return pathExistsBFS(G, task1, task2);
}

4. 算法优化技巧

C
// 带剪枝的DFS优化
bool pathExistsDFS_Optimized(Graph G, int i, int j, int visit[], int max_depth, int current_depth) {
    // 深度限制剪枝
    if (current_depth > max_depth) {
        return false;
    }

    if (i == j) return true;

    visit[i] = 1;

    Node p = G.adjlist[i].firstarc;
    for (; p != NULL; p = p->nextarc) {
        if (visit[p->adjvex] == 0) {
            if (pathExistsDFS_Optimized(G, p->adjvex, j, visit, max_depth, current_depth + 1)) {
                return true;
            }
        }
    }

    visit[i] = 0;  // 回溯时重置访问标记(如果需要多次调用)
    return false;
}

这些算法在实际的图论问题中应用广泛,是解决路径相关问题的基础工具。