挑战程序设计(算法和数据结构)—DFS和BFS及其一些应用

DFS

题目12.3链接Depth First Search
DFS可以使用递归和栈来解。

以下两种方法都是将邻接表转化为邻接矩阵,在图的邻接矩阵上操作。
使用递归:

#include <iostream>
#include <cstring>
using namespace std;

const int Max = 105;
int G[Max][Max], color[Max], d[Max], f[Max];
int t, n;
//color -1未遍历  0已遍历,但仍在栈中  1出栈了,已经访问完其邻接表了
void dfs_visit(int u)
{
    color[u] = 0;//已遍历
    d[u] = ++t;//开始时刻
    for(int j=0; j<n; j++)
    {
        if(!G[u][j]) continue;//不相邻
        if(color[j]==-1)//递归访问未访问过的结点
            dfs_visit(j);
    }
    color[u] = 1;//递归完后,出栈,表示其邻接表的点已访问完
    f[u] = ++t;//结束时刻
}
void dfs()
{
    memset(color, -1, sizeof(color));//初始化
    t = 0;//记录时间
    for(int i=0; i<n; i++)
    {
        if(color[i]==-1)//如果未遍历,则可以从它开始DFS
            dfs_visit(i);
    }
    for(int i=0; i<n; i++)//输出结果
    {
        cout << i+1 << " " << d[i] << " " << f[i] << endl;
    }
}
int main()
{
    int u, k, v;
    cin >> n;
    for(int i=0; i<n; i++)
    {
        cin >> u >> k;
        for(int j=0; j<k; j++)
        {
            cin >> v;
            G[u-1][v-1] = 1;
        }
    }
    dfs();
    return 0;
}

使用栈:

#include <iostream>
#include <cstring>
#include <stack>
using namespace std;

const int Max = 105;
int G[Max][Max], color[Max], d[Max], f[Max], nt[Max];//nt[i]用来记录邻接矩阵第i行访问到了哪一列
int t, n;

int next(int u)//查询u的邻接点访问到了哪一个
{
    for(int v=nt[u]; v<n; v++)
    {
        nt[u] = v+1;
        if(G[u][v]) return v;
    }
    return -1;//表示邻接点访问完了
}
void dfs_visit(int r)
{
     stack<int> S;
     S.push(r);
     color[r] = 0;
     d[r] = ++t;

     while(!S.empty())
     {
         int u = S.top();
         int v = next(u);//寻找u的邻接点
         if(v!=-1)//u还有邻接点
         {
             if(color[v]==-1)//未访问过
             {
                 color[v] = 0;
                 d[v] = ++t;
                 S.push(v);
             }
         }
         else
         {
             S.pop();
             color[u] = 1;
             f[u] = ++t;
         }
     }
}

void dfs()
{
    memset(color, -1, sizeof(color));
    memset(nt, 0, sizeof(nt));
    t = 0;
    for(int i=0; i<n; i++)
    {
        if(color[i]==-1)
            dfs_visit(i);
    }
    for(int i=0; i<n; i++)
    {
        cout << i+1 << " " << d[i] << " " << f[i] << endl;
    }
}

int main()
{
    int u, k, v;
    cin >> n;
    for(int i=0; i<n; i++)
    {
        cin >> u >> k;
        for(int j=0; j<k; j++)
        {
            cin >> v;
            G[u-1][v-1] = 1;
        }
    }
    dfs();
    return 0;
}
BFS

题目12.4链接Breadth First Search

BFS主要使用队列来解,每次将所有为遍历的邻接点入队列。
这个方法还是采用了邻接矩阵,复杂度O(N^2)

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

const int Max = 105;
int G[Max][Max], color[Max], d[Max];//d存的是距离
int n;
//color -1未遍历  0已遍历
void bfs(int u)
{
    memset(color, -1, sizeof(color));
    memset(d, -1, sizeof(d));
    queue<int> Q;
    int v;
    Q.push(u);//第一个节点
    d[u] = 0;
    color[u] = 0;
    while(!Q.empty())
    {
        u = Q.front(); Q.pop();
        for(int i=0; i<n; i++)
        {
            if(G[u][i] && color[i]==-1)//找到未遍历的邻接点
            {
                color[i] = 0;
                Q.push(i);
                d[i] = d[u]+1;
            }
        }
    }
    for(int i=0; i<n; i++)
    {
        cout << i+1 << " " << d[i] << endl;
    }
}

int main()
{
    int u, k, v;
    cin >> n;
    for(int i=0; i<n; i++)
    {
        cin >> u >> k;
        for(int j=0; j<k; j++)
        {
            cin >> v;
            G[u-1][v-1] = 1;
        }
    }
    bfs(0);
    return 0;
}
应用
DFS的应用
求图的关节点

题目15.3链接Articulation Points
求关节点使用了tarjan算法,相关讲解见tarjan算法求关节点和《挑战程序设计竞赛–数据结构与算法》p291。
变量设置如下:

变量含义
prenum[u]以图的任意一点为起点进行DFS,将各顶点u的访问(发现)顺序记录在prenum[u]中,即u在DFS过程中的深度
parent[u]通过DFS生成一棵树,将其中结点u的父节点记录在parent[u]中
lowest[u]对于各顶点u,计算通过u能够到达的最低深度数并记入lowest[u]

(1)如果点u是dfs生成树的根,那么u至少有2个子女。理由:因为一删除u,它的子女所在的子树就断开了。
即若根u有两个及以上的子节点,则根u为关节点。

(2)如果点u不是dfs生成树的根,那么u的一个子女w,从w或w的子孙出发或回边到达u的祖先。如果能够到达,那么点u就不是关节点了。
即若prenum[u]<=lowest[w],则u为关节点。

对于prenum和parent均可以在DFS过程直接求出,因此只需考虑lowest的求解

lowest[u]=min{

  prenum[u] 

  min(low[w]|w为点u的子孙)  

  min(prenum[x]|x和u构成回边)

};

回边:原图中除了dfs树中的边外,还有的边叫回边;如:1->2,1->3,2->3中,dfs时1->2->3,这里dfs树中的边为1->2,2->3,那么1->3就是回边)

代码如下:

#include <iostream>
#include <set>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;

const int MAX = 100005;
int V, E, s, t, color[MAX], timer;
int prenum[MAX], parent[MAX], lowest[MAX];
vector<int> G[MAX];

//color 为0表示为访问,为1表示已访问
void dfs(int current, int prev)
{
    prenum[current] = lowest[current] = timer;//初始赋值prenum[current] = lowest[current]
    timer++;
    color[current] = 1;
    int next;

    for(int i=0; i<G[current].size(); i++)//遍历其所有的邻接点
    {
        next = G[current][i];//邻接点
        if(!color[next])//没有访问过,则可以建立生成树
        {
            parent[next] = current;//记录父节点
            dfs(next, current);//递归DFS
            lowest[current] = min(lowest[current], lowest[next]);
        }
        else if(next!=prev)//筛去其子节点是父节点的情况
        {
            lowest[current] = min(lowest[current], prenum[next]);
        }
    }
}
void art_points()
{
    memset(color, 0, sizeof(color));
    timer = 1;
    dfs(0, -1);
    int p, ans = 0;
    set<int> S;//存储结果,利用set的自动排序功能
    for(int i=1; i<V; i++)
    {
        p = parent[i];
        if(p==0) ans++;//记录根节点的子节点个数
        else if(prenum[p]<=lowest[i]) S.insert(p);
    }
    if(ans>1) S.insert(0);
    set<int>::iterator it;
    for(it=S.begin(); it!=S.end(); it++)
    {
        printf("%d\n", *it);
    }
}
int main()
{
    scanf("%d %d", &V, &E);
    for(int i=0; i<E; i++)
    {
        scanf("%d %d", &s, &t);
        G[s].push_back(t);
        G[t].push_back(s);
    }
    art_points();
    return 0;
}
BFS的应用
树的直径

题目15.4链接Diameter of a Tree
这道题采用了BFS的邻接表实现法,并定义了带权无向图的结构体。主要思路为
1-任选一节点s,求到s的最远结点x。
2-求到x最远的结点y。
3-报告结点x与结点y的距离,即树的直径。
因为是树,所以任意两点之间只有一条路径,也即其最远路径,可以使用BFS来记录所有点到点s的路径长度。

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

struct Edge
{
    int t, w;
    //Edge(){}
    Edge(int t=-1, int w=-1): t(t), w(w){}
};
const int MAX = 100005;
const int INF = (1<<29);
int color[MAX], d[MAX];//d存的是距离
vector<Edge> G[MAX];
int n;
//color -1未遍历  0已遍历
void bfs(int u)
{
    memset(color, -1, sizeof(color));
    memset(d, -1, sizeof(d));
    queue<int> Q;
    int v;
    Q.push(u);//第一个节点
    d[u] = 0;
    color[u] = 0;
    while(!Q.empty())
    {
        u = Q.front(); Q.pop();
        for(int i=0; i<G[u].size(); i++)
        {
            int v = G[u][i].t;
            if(color[v]==-1)//找到未遍历的邻接点
            {
                color[v] = 0;
                Q.push(v);
                d[v] = d[u]+G[u][i].w;
            }
        }
    }
}

void solve()
{
    bfs(0);//任意一点
    int Max = -1;
    int x, y;
    for(int i=0; i<n; i++)
    {
        if(d[i]>=0 && Max<d[i])
        {
            Max = d[i];
            x = i;
        }
    }
    bfs(x);
    Max = -1;
    for(int i=0; i<n; i++)
    {
        if(d[i]>=0 && Max<d[i])
        {
            Max = d[i];
            y = i;
        }
    }
    cout << d[y] << endl;
}
int main()
{
    int u, k, v;
    cin >> n;
    if(n==1)
    {
        cout << 0 << endl;
        return 0;
    }
    for(int i=0; i<n-1; i++)
    {
        cin >> u >> k >> v;
        G[u].push_back(Edge(k, v));
        G[k].push_back(Edge(u, v));
    }
    solve();

    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值