搜索与图论(一)树的遍历/深度/广度/拓扑排序

文章目录

搜索与图论

树与图的深度优先遍历

举个栗子
树的重心
思路

邻接表存储

cpp 复制代码
for(int i=1;i<=n;i++)  
{
    cout<<i<<":";
    for(int j=h[i];j!=-1;j=ne[j])
    {
        cout<<"->"<<e[j]; 
    }
    cout<<endl;
}
return 0;

输出树结构:

1:->4->7->2

2:->5->8->1

3:->9->4

4:->6->3->1

5:->2

6:->4

7:->1

8:->2

9:->3


结论

在本题的邻接表存储结构中,有两个容易混淆的地方,一个是节点的编号,一个是节点的下标。

节点的编号是指上图所画的树中节点的值,范围是从1~n。在本题中,每次输入的a和b就是节点的编号,编号用e[i]数组存储。

节点的下标指节点在数组中的位置索引,数组之间的关系就是通过下标来建立连接,下标用idx来记录。idx范围从0开始,如果idx==-1表示空。

e[i]的值是编号,是下标为i节点的编号。
ne[i]的值是下标,是下标为i的节点的next节点的下标。
h[i]存储的是下标,是编号为i的节点的next节点的下标,比如编号为1的节点的下一个节点是4,那么我输出e[h[1]]就是4

代码如下
cpp 复制代码
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1e5 + 10; //数据范围是10的5次方
const int M = 2 * N; //以有向图的格式存储无向图,所以每个节点至多对应2n-2条边

int h[N]; //邻接表存储树,有n个节点,所以需要n个队列头节点
int e[M]; //存储元素
int ne[M]; //存储列表的next值
int idx; //单链表指针
int n; //题目所给的输入,n个节点
int ans = N; //表示重心的所有的子树中,最大的子树的结点数目

bool st[N]; //记录节点是否被访问过,访问过则标记为true

//a所对应的单链表中插入b  a作为根 
void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

//返回以u为根的子树中节点的个数,包括u节点
int dfs(int u) {
    int res = 0; //存储 删掉某个节点之后,最大的连通子图节点数
    st[u] = true; //标记访问过u节点
    int sum = 1; //存储 以u为根的树 的节点数, 包括u,如图中的4号节点

    //访问u的每个子节点
    for (int i = h[u]; i != -1; i = ne[i]) {
        int j = e[i];
        //因为每个节点的编号都是不一样的,所以 用编号为下标 来标记是否被访问过
        if (!st[j]) {
            int s = dfs(j);  // u节点的单棵子树节点数 如图中的size值
            res = max(res, s); // 记录最大联通子图的节点数
            sum += s; //以j为根的树 的节点数
        }
    }

    //n-sum 如图中的n-size值,不包括根节点4;
    res = max(res, n - sum); // 选择u节点为重心,最大的 连通子图节点数
    ans = min(res, ans); //遍历过的假设重心中,最小的最大联通子图的 节点数
    return sum;
}

int main() {
    memset(h, -1, sizeof h); //初始化h数组 -1表示尾节点
    cin >> n; //表示树的结点数

    // 题目接下来会输入,n-1行数据,
    // 树中是不存在环的,对于有n个节点的树,必定是n-1条边
    for (int i = 0; i < n - 1; i++) {
        int a, b;
        cin >> a >> b;
        add(a, b), add(b, a); //无向图
    }

    dfs(1); //可以任意选定一个节点开始 u<=n

    cout << ans << endl;

    return 0;
}

树与图的广度优先遍历

举个例子
图中点的层次
样例展示
代码
cpp 复制代码
#include <iostream>
#include <queue>
#include <queue>
#include <cstring>
using namespace std;
const int N = 100010;
int idx, e[N], h[N], ne[N], d[N];
int n, m;
void add(int a, int b) {
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

int bfs() {
    memset(d, -1, sizeof d);
    queue<int> q;
    q.push(1);
    d[1] = 0;
    
    while(q.size()) {
        int t = q.front();
        q.pop();
        
        for(int i = h[t]; i != -1; i = ne[i]) {
            int j = e[i];
            if(d[j] == -1) {
                d[j] = d[t] + 1;
                q.push(j);
            }
        }
    }
    return d[n];
}

int main() {
    cin >> n >> m;
    memset(h, -1, sizeof h);    //初始化必须有h[] = -1
    int a, b;
    for(int i = 0; i < m; i++) {
        scanf("%d%d", &a, &b);
        add(a, b);
    }
    cout << bfs() << endl;
    return 0;
}

拓扑排序

啥是拓扑排序?
  • 一个有向图,如果图中有入度为 0 的点,就把这个点删掉,同时也删掉这个点所连的边。

  • 一直进行上面出处理,如果所有点都能被删掉,则这个图可以进行拓扑排序。

这时整个图被删除干净,所有能进行拓扑排序。


解题思路

首先记录各个点的入度

然后将入度为 0 的点放入队列

将队列里的点依次出队列,然后找出所有出队列这个点发出的边,删除边,同事边的另一侧的点的入度 -1。

如果所有点都进过队列,则可以拓扑排序,输出所有顶点。否则输出-1,代表不可以进行拓扑排序。


举个栗子

有向图中的拓扑排序

题目
代码如下
cpp 复制代码
**
首先记录各个点的入度
然后将入度为 0 的点放入队列

将队列里的点依次出队列,然后找出所有出队列这个点发出的边,删除边,同事边的另一侧的点的入度 -1。

如果所有点都进过队列,则可以拓扑排序,输出所有顶点。否则输出-1,代表不可以进行拓扑排序。
**
#include<iostream>
#include<cstring>
using namespace std;
const int N = 100010;
int e[N],ne[N],idx; //邻接表存储图
int h[N];       //头结点
int q[N],hh = 0,tt = -1;    //队列保存入度为0的点
int n,m;    //存储图的点数和边数
int d[N];   //保存各个点的入度

void add(int a,int b)   //将b加在a的后边
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

void topsort()
{
    //遍历一遍顶点的入度
    for(int i = 1;i <= n;i++)
    {
        if(d[i] == 0) 
        q[++tt] = i; //如果入度为0,可以进队列
    }
    while(hh <= tt) //循环处理队列中点
    {
        int a = q[hh++];
        //循环删除a发出的边
        for(int i = h[a];i != -1;i = ne[i])
        {
            int b = e[i];
            //相关点入度-1
            d[b]--;
            if(d[b] == 0)
            //若b的入度减为0,则可以输出,进队列
            q[++tt] = b;
        }
       
    }
    //如果队列中点的个数等于图中点的个数,则可以进行拓扑排序
    //输出队列
    if(tt == n - 1)
    {
        for(int i = 0;i < n;i++)
        {
            printf("%d ",q[i]);
        }
    }
    else cout << -1;    //输出-1,代表错误
}

int main()
{
    cin >> n >> m;  //保存点的个数和边的个数
    memset(h,-1,sizeof h);  //初始化邻接矩阵
    while(m--)
    {   //依次读入边
        int a,b;
        cin >> a >> b;
        add(a,b);   //添加到邻接矩阵
        d[b]++;     //顶点b的入读+1
        
    }
    topsort();//进行拓扑排序
    return 0;
}
相关推荐
xiaoshiguang33 小时前
LeetCode:222.完全二叉树节点的数量
算法·leetcode
爱吃西瓜的小菜鸡3 小时前
【C语言】判断回文
c语言·学习·算法
别NULL3 小时前
机试题——疯长的草
数据结构·c++·算法
TT哇3 小时前
*【每日一题 提高题】[蓝桥杯 2022 国 A] 选素数
java·算法·蓝桥杯
yuanbenshidiaos4 小时前
C++----------函数的调用机制
java·c++·算法
唐叔在学习4 小时前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
ALISHENGYA5 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
数据结构·算法
chengooooooo5 小时前
代码随想录训练营第二十七天| 贪心理论基础 455.分发饼干 376. 摆动序列 53. 最大子序和
算法·leetcode·职场和发展
jackiendsc5 小时前
Java的垃圾回收机制介绍、工作原理、算法及分析调优
java·开发语言·算法
游是水里的游6 小时前
【算法day20】回溯:子集与全排列问题
算法