代码随想录算法训练营day45

3 阅读5分钟

图论总结: 图论学习部分可以参考邓俊辉老师的《数据结构》。邓老师的图部分适用范围更广,在拓扑排序,优先级遍历,深度遍历,广度遍历的深度相对来说讲得更深,引入了时间变量,并将各连同点之间连线进行了标注。

1.图的定义 图主要指相互联系的一组字符集,其中又可以依据相互联系划分为有向图,无向图,连通图等。存储方式分为邻接矩阵,邻接表等方式。其中邻接矩阵的空间复杂度较高,但是便于理解和快速访问,适合稠密图,邻接表空间复杂度较低,方便存储,适合稀疏图。

2.图的遍历方式 图的遍历方式主要分为两种: 深度优先搜索和广度优先搜索。

要遍历图,首先时要读入图,按照不同的存储方式进行图的读入: 邻接矩阵模式:

    vector<vector<int>> graphin(){
        int n,m,s,t;
        cout<<"输入结点数量"<<endl;
        cin>>n;//输入结点数量
        cout<<"输入边的数量"<<endl;
        cin>>m;//输入边的数量
        cout<<"输入联通的各结点"<<endl;
        vector<vector<int>> graph(n+1,vector<int>(n+1,0));//初始化邻接矩阵,申请比原结点数量大一的空间
        //输入联通的结点
        while(m--){
           cin>>s>>t;
           graph[s][t]=1;
        }         
    }

邻接表模式

   vector<list<int>> graphin(){
        int n,m,s,t;
        cout<<"输入结点数量"<<endl;
        cin>>n;//输入结点数量
        cout<<"输入边的数量"<<endl;
        cin>>m;//输入边的数量
        cout<<"输入联通的各结点"<<endl;
        vector<list<int>> graph(n+1);//初始化邻接矩阵,注意,这里一般申请比结点大一的空间
        //输入联通的结点
        while(m--){
           cin>>s>>t;
           graph[s].push_back(t);
        } 
        return graph;
    }

2.1深度优先搜索 主要通过递归栈对图进行深度遍历搜索,函数不断对满足联通关系的结点执行入栈操作,直到达到递推返回条件才弹出返回,然后重新执行入栈操作,直到栈空。原理上时采用回溯递归算法。 具体算法如下: 采用邓俊辉老师《数据结构》算法

    enum{undiscover,discover,visited}
    void dfs(int s){
      int v=s;
      int clock=0;
      do{
         if(status[v]==undiscover)
          DFS(s,clock);
      }while(s!=(v=(v++%n)))//确保不漏不重
    }
    
    void DFS(int v,int clock){
       //记录访问时间,之后时间增加
       dtime=clock++;
       //改变结点状态、
       status(v)=discover;
       //枚举所有邻居,根据邻居结点状况进行访问
       for(int u=firstnbr(v);u>-1;u=nextnbr(v)){
          switch(status(u)) {
            case undiscover:
               type(v,u)=tree;parent(u)=v;DFS(u,clock);break;
            case discover:
               type(v,u)=backward;break;
            case visited:
               type(v,u)=(dtime(v)<dtime(u))?forward:cross;break;         
          }
       }
       //访问完毕结点之后改变结点状态,做好标记
       status(v)=visited;ftime(v)=++clock;
    }

同理采用代码随想录里的算法,使用递归三部曲 1.确定递归返回参数和传参

//graph  需要访问的图
//v开始遍历的结点
//n想要方位的终点结点
   void DFS(const vector<vector<int>>graph, int v,int n)
   void DFS(const vector<vector<int>>graph, int v)

2.确定递归终止条件

   if(v==n) 
   result.push_back(path);//存储路径
   return

3.设计单层递归逻辑

   for(int i=1;i<=n;i++){
     if(graph[v][i]==1){
           path.push_back(i);
           DFS(garph,i);//递归遍历
           path.pop_back();
     }
   }

完整代码如下:

    void DFS(const vector<vector<int>> graph,int v,int n){
        if(v==n){
         result.push_back(path);
         return;
        }
        for(int i=1;i<=n;i++){
           if(graph[v][i]==1){
              path.push_back(i);
              DFS(graph,i);
              path.pop_back();
           }
        }
    }

98.所有可达路径解答

#include <iostream>
#include <vector>
using namespace std;
vector<vector<int>> result; // 收集符合条件的路径
vector<int> path; // 1节点到终点的路径

void dfs (const vector<vector<int>>& graph, int x, int n) {
    // 当前遍历的节点x 到达节点n 
    if (x == n) { // 找到符合条件的一条路径
        result.push_back(path);
        return;
    }
    for (int i = 1; i <= n; i++) { // 遍历节点x链接的所有节点
        if (graph[x][i] == 1) { // 找到 x链接的节点
            path.push_back(i); // 遍历到的节点加入到路径中来
            dfs(graph, i, n); // 进入下一层递归
            path.pop_back(); // 回溯,撤销本节点
        }
    }
}

int main() {
    int n, m, s, t;
    cin >> n >> m;

    // 节点编号从1到n,所以申请 n+1 这么大的数组
    vector<vector<int>> graph(n + 1, vector<int>(n + 1, 0));

    while (m--) {
        cin >> s >> t;
        // 使用邻接矩阵 表示无线图,1 表示 s 与 t 是相连的
        graph[s][t] = 1;
    }

    path.push_back(1); // 无论什么路径已经是从0节点出发
    dfs(graph, 1, n); // 开始遍历

    // 输出结果
    if (result.size() == 0) cout << -1 << endl;
    for (const vector<int> &pa : result) {
        for (int i = 0; i < pa.size() - 1; i++) {
            cout << pa[i] << " ";
        }
        cout << pa[pa.size() - 1]  << endl;
    }
}

2.2 广度优先搜素 主要通过队列这种数据结构,FIFO模式,对满足该结点联通条件的其他结点不断进行入队操作,直到无联通结点时,联通结点出队,继续执行,直到队空。原理上采用的时迭代递推算法。


void Graph::bfs ( int s ) { 
    reset(); int clock = 0; int v = s;
    do {
        if ( UNDISCOVERED == status ( v ) ){
        BFS ( v, clock );
        } while ( s != ( v = ( ++v % n ) ) ); 
    }
 template void Graph::BFS ( int v, int& clock ) { 
 Queue Q; 
 status ( v ) = DISCOVERED; Q.enqueue ( v );
 while ( !Q.empty() ) {
 int v = Q.dequeue(); dTime ( v ) = ++clock; 
 for ( int u = firstNbr ( v ); -1 < u; u = nextNbr ( v, u ) ){
  if ( UNDISCOVERED == status ( u ) ) { 
     status ( u ) = DISCOVERED; Q.enqueue ( u );
     type ( v, u ) = TREE; parent ( u ) = v; 
     } else { 
      type ( v, u ) = CROSS; 
      status ( v ) = VISITED; 
         }
 }

代码随想录中的设计:


#include <iostream>
#include <vector>
#include <queue>
using namespace std;

int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向
void bfs(const vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y) {
    queue<pair<int, int>> que;
    que.push({x, y});
    visited[x][y] = true; // 只要加入队列,立刻标记
    while(!que.empty()) {
        pair<int ,int> cur = que.front(); que.pop();
        int curx = cur.first;
        int cury = cur.second;
        for (int i = 0; i < 4; i++) {
            int nextx = curx + dir[i][0];
            int nexty = cury + dir[i][1];
            if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue;  // 越界了,直接跳过
            if (!visited[nextx][nexty] && grid[nextx][nexty] == 1) {
                que.push({nextx, nexty});
                visited[nextx][nexty] = true; // 只要加入队列立刻标记
            }
        }
    }
}

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> grid[i][j];
        }
    }

    vector<vector<bool>> visited(n, vector<bool>(m, false));

    int result = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (!visited[i][j] && grid[i][j] == 1) {
                result++; // 遇到没访问过的陆地,+1
                bfs(grid, visited, i, j); // 将与其链接的陆地都标记上 true
            }
        }
    }


    cout << result << endl;
}