图的深度优先与广度优先搜索

树和图的储存

树是特殊的图

有向图 无向图(特殊的有向图)

a->b a–b(a->b,b->a)

故只需要考虑有向图

1、邻接矩阵 :g[a,b] 存储a->b的信息,如果有权重表示权重,如果没有则是bool型,表示有边or无边,但是不能存储重边,只能保留一条。空间复杂度是 n 2 n^2 n2适合存储稠密图。

2、邻接表:即n个单链表,类似于拉链法的哈希表,每个点上都有一个单链表。头节点h[i]存储第i条链的头节点,而插入的时候从头插入。

图的深度优先搜索

模板

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=100010,M=N*2;

int h[N],e[N],ne[M],idx;
//e[]存的是图中节点的编号,ne[]存的是idx
bool st[N];//记录点是否被遍历过

//深度优先搜索
void dfs(int u){//已经搜到了u这个点,这里的u是节点在图中的编号
    st[u]=true;//先标记这个点被搜过了
    
    for(int i=h[u];i!=-1;i=ne[i]){//遍历u的所有出边
        int j=e[i];//令j为i指向的节点的编号
        if(!st[j]) dfs(j);//如果j这个点没有被搜过,则一直搜下去
    }
}

void add(int a,int b){//增加一条a->b的边,a,b是节点在图中的编号
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

int main(){
    memeset(h,-1,sizeof h);//先使每个链表的头节点指向-1
}

给定一颗树,树中包含 nn 个结点(编号 1∼n1∼n)和 n−1n−1 条无向边。

请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。

重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。

输入格式

第一行包含整数 nn,表示树的结点数。

接下来 n−1n−1 行,每行包含两个整数 aa 和 bb,表示点 aa 和点 bb 之间存在一条边。

输出格式

输出一个整数 mm,表示将重心删除后,剩余各个连通块中点数的最大值。

数据范围

1≤n≤1051≤n≤105

输入样例

9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6

输出样例:

4
#include<iostream>
#include<cstring>
#include<algorithm>
//本题是无向图,所以每两个点之间要建立两条方向相反的边

using namespace std;

const int N=100010,M=N*2;

int h[N],e[M],ne[M],idx,n;
//e[]存的是图中节点的编号,ne[]存的是idx
bool st[N];//记录点是否被遍历过

int ans=N;//最后所求的答案,最小的最大值

//返回以u为根的点的数量,包括u节点
int dfs(int u){//已经搜到了u这个点,这里的u是节点在图中的编号
    st[u]=true;//先标记这个点被搜过了
    
    int res=0;//存储 删掉某个节点后,最大的连通子图的节点数
    int sum=0;//存储 以u为根的树的节点数,包括u
    
    for(int i=h[u];i!=-1;i=ne[i]){//依次遍历与u连通的子节点
        int j=e[i];//令j为i指向的节点的编号
        if(st[j]) continue;//如果该节点已经被搜索过
        
        //若未被搜索过
        int s=dfs(j);//u的一个子树的节点数
        res=max(s,res);//使res为最大连通子图的节点数
        sum+=s;//以u为节点的树的节点树=子图的节点数+1
        
    }
    
    res=max(res,n-sum);//子树中最大阶段数和除了子树外的其他连通节点数 求较大值
    ans=min(res,ans);//遍历过的假设重心中,最小的最大联通子图的 节点数
    return sum;//返回的使以u韦根的图点的数量,不是ans
}

void add(int a,int b){//增加一条a->b的边,a,b是节点在图中的编号
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

int main(){
    memset(h,-1,sizeof h);//先使每个链表的头节点指向-1
    scanf("%d",&n);
    
    for(int i=0;i<n-1;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b),add(b,a);//无向图
    }
    
    dfs(1);//从第一个节点开始深搜
    
    printf("%d\n",ans);
    
    return 0;
}
图的宽度优先搜索

求图中的最短路

给定一个 nn 个点 mm 条边的有向图,图中可能存在重边和自环。

所有边的长度都是 11,点的编号为 1∼n1∼n。

请你求出 11 号点到 nn 号点的最短距离,如果从 11 号点无法走到 nn 号点,输出 −1−1。

输入格式

第一行包含两个整数 nn 和 mm。

接下来 mm 行,每行包含两个整数 aa 和 bb,表示存在一条从 aa 走到 bb 的长度为 11 的边。

输出格式

输出一个整数,表示 11 号点到 nn 号点的最短距离。

数据范围

1≤n,m≤1051≤n,m≤105

输出样例

4 5
1 2
2 3
3 4
1 3
1 4

输出样例:

1
#include<iostream>
#include<cstring>
#include<algorithm>
#include <queue>

/*宽搜框架:
    queue <- 1号点
    while queue不空{
        t <- 队头
        拓展t所有邻点
            if(x未遍历){
                queue <- x未遍历
                d[x]=d[t]+1
            }
    }
*/

const int N=100010;
int n,m;//n个点,m条边
int h[N],e[N],ne[N],idx;
int d[N],q[N];

void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

int bfs(){
    memset(d,-1,sizeof d);
    std::queue<int> q;
    d[1]=0;
    q.push(1);
    
    while(q.size()){
        auto t=q.front();
        q.pop();
        
        for(int i=h[t];i!=-1;i=ne[i]){//依次扩展和其邻接的点
            int j=e[i];//用j存储和i邻接的点
            if(d[j]==-1){//如果这个点没有被扩展到
                d[j]=d[t]+1;
                q.push(j);
            }
        }
    }
    return d[n];
}

int main(){
    std::cin>>n>>m;
    memset(h,-1,sizeof h);
    
    for(int i=0;i<m;i++){
        int a,b;
        std::cin>>a>>b;
        add(a,b);
    }
    
    std::cout<<bfs()<<std::endl;
    
    return 0;
}
图的宽搜的应用——拓扑排序

例:有向图的拓扑序列

给定一个 nn 个点 mm 条边的有向图,点的编号是 11 到 nn,图中可能存在重边和自环。

请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 −1−1。

若一个由图中所有点构成的序列 AA 满足:对于图中的每条边 (x,y)(x,y),xx 在 AA 中都出现在 yy 之前,则称 AA 是该图的一个拓扑序列。

输入格式

第一行包含两个整数 nn 和 mm。

接下来 mm 行,每行包含两个整数 xx 和 yy,表示存在一条从点 xx 到点 yy 的有向边 (x,y)(x,y)。

输出格式

共一行,如果存在拓扑序列,则输出任意一个合法的拓扑序列即可。

否则输出 −1−1。

数据范围

1≤n,m≤1051≤n,m≤105

输入样例:

3 3
1 2
2 3
1 3

输出样例:

1 2 3
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;

//拓扑序列->所有边都是从前指向后
//有向无环图(拓扑图),一定至少存在一个入度为0de点,一定存在一个拓扑序列
/*由于所有入度为0的点:可以排在当前最前面的位置
    queue <- 所有入度为0的点
    while queue不空{
        t <- 队头
        枚举t的所有出边 t -> j
            删掉t -> j,即使d[j]--   //t出队,即已经放在了后续点的前面,故指向后面点的边可以删去
            if d[j]==0{
                queue <- j
            }
    }
*/

const int N=100010;

int n,m;
int h[N],e[N],ne[N],idx;
int d[N];//记录入度
int ans[N],cnt;//存储排好序的答案
std::queue<int> q;

void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void topsort(){
    
    for(int i=1;i<=n;i++){
        if(d[i]==0)//找出入度为0的节点
            q.push(i);
    }
    
    while(q.size()){//队列不空
        auto t=q.front();
        q.pop();
        
        ans[cnt++]=t;
        
        for(int i=h[t];i!=-1;i=ne[i]){
            int j=e[i];
            if(--d[j]==0)//符合条件的才会被push进队列里
                q.push(j);
        }
    }//最后队列已经清空
}

int main(){
    cin>>n>>m;
    memset(h,-1,sizeof h);
    
    for(int i=0;i<m;i++){
        int a,b;
        cin>>a>>b;
        add(a,b);
        
        d[b]++;//入度
    }
    
    topsort();
    
    if(cnt==n){
        for(int i=0;i<n;i++)
            printf("%d ",ans[i]);
    }
    else puts("-1");
    cout<<endl;
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值