树与图的存储与遍历

110 阅读4分钟

树与图的存储

  • 数是一种特殊的图(无环连通图)
  • 对于无向图中的边ab,存储两条有向边a->b, b->a
  • 因此我们可以只考虑有向图的存储

邻接矩阵存储

  • 二维数组g[a][b]来存储边a->b
    • 如果有权重,里面就存权重,否则就存布尔值代表有没有这条边
    • 无法存储重边,有重边只能保留一条(最短路的话就存权重最小的)
    • 空间复杂度n^2^,比较浪费空间,比较适合存储稠密图

邻接表存储

Snipaste_2023-03-13_19-24-24.png

  • C++
// 对于每个点k,开一个单链表,存储k所有可以走到的点。h[k]存储这个单链表的头结点
int h[N], e[N], ne[N], idx;

// 添加一条边a->b
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

// 初始化
idx = 0;
memset(h, -1, sizeof h);
  • Java
public static final N = 100010;
public static final M = N * 2;
public static int[] h = new int[N];
public static int[] e = new int[M];
public static int[] ne = new int[M];
public static int idx;

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

//初始化
idx = 0;
Arrays.fill(h, -1);

树与图的遍历

  • 时间复杂度 O(n+m),n表示点数,m表示边数

深度优先遍历

  • 树与图一般只需要搜索一次,所以不需要回溯

  • C++

int dfs(int u)
{
    st[u] = true; // st[u] 表示点u已经被遍历过

    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j]) dfs(j);
    }
}
  • Java
//布尔数组st表示当前点是否被搜过
public static boolean[] st = new boolean[N];

public static int dfs(int u) {
    st[u] = true;  //先将当前节点设为true,表示已经被搜过
    
    for (int i = h[u]; i != -1; i = ne[i]) {
        int j = e[i];
        if (st[j]) {
            dfs(j);
        }
    }
}

宽度优先遍历

  • C++
queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);

while (q.size())
{
    int t = q.front();
    q.pop();

    for (int i = h[t]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j])
        {
            st[j] = true; // 表示点j已经被遍历过
            q.push(j);
        }
    }
}
  • Java
public static boolean[] st = new boolean[N];

public static void bfs() {
    ArrayDeque<Integer> q = new ArrayDeque<>();

	st[1] = true;  //表示1号点已经被遍历过
	q.offer(1);    //将1号点插入队尾

	while (q.size() != 0) {
    	int t = q.poll();  //弹出队头
    
    	for (int i = h[t]; i != -1; i = ne[i]) {
        	int j = e[i];
        	if (!st[j]) {
            	st[j] = true;  //表示j号点已经被遍历过
            	q.offer(j);    //将j号点插入队尾
        	}
    }
}
}

练习

01 树的重心

  • 题目

Snipaste_2023-03-13_20-23-26.png

  • 题解

Snipaste_2023-03-13_20-19-24.png

import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 100010;
    public static final int M = N * 2;
    public static int[] h = new int[N];
    public static int[] e = new int[M];
    public static int[] ne = new int[M];
    public static boolean[] st = new boolean[N];
    public static int n, idx;
    public static int ans = N;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        n = Integer.parseInt(br.readLine());
        //初始化
        idx = 0;
        Arrays.fill(h, -1);
        //n-1条数据
        int m = n - 1;
        while (m-- > 0) {
            String[] str1 = br.readLine().split(" ");
            int a = Integer.parseInt(str1[0]);
            int b = Integer.parseInt(str1[1]);
            //无向图
            add(a, b);
            add(b, a);
        }

        dfs(1);
        pw.println(ans);
        br.close();
        pw.close();
    }

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

    //dfs返回以当前节点为根的子树的节点个数
    public static int dfs(int u) {
        //先将当前节点设为true,表示已经被搜过
        st[u] = true;
        //sum是dfs的返回值 表示以当前节点为根的子树的节点个数,先算上自己
        int sum = 1;
        //res表示将这个节点删除后,剩余各个连通块中节点数量的最大值
        int res = 0;

        for (int i = h[u]; i != -1; i = ne[i]) {
            int j = e[i];
            //如果当前节点的子节点没有被搜过继续往下搜
            if (!st[j]) {
                //返回的s是以 当前节点的子节点 为根节点 的 子树 的节点个数
                int s = dfs(j);
                //其中s也是将当前节点删除后,形成的一个连通块的节点个数,做一下比较,取最大值
                res = Math.max(res, s);
                //把s加上
                sum += s;
            }
        }
        //这时候就已经把以 当前节点的所有子节点 为根节点 的子树都遍历了一遍
        //sum也就算出来了

        //还要和当前节点向上的连通块的节点个数进行比较,取最大值
        res = Math.max(res, n - sum);

        //取最大值的最小值
        ans = Math.min(ans, res);
        return sum;
    }
}

01 图中点的层次

  • 题目

Snipaste_2023-03-14_16-04-19.png

  • 题解
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 100010;
    public static int[] h = new int[N];
    public static int[] e = new int[N];
    public static int[] ne = new int[N];
    //存从1号点到i号点的距离
    public static int[] d = new int[N];
    public static int idx, n, m;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        String[] str1 = br.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        //初始化图
        idx = 0;
        Arrays.fill(h, -1);
        
        while (m-- > 0) {
            String[] str2 = br.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);
            int b = Integer.parseInt(str2[1]);
            add(a, b);
        }

        pw.println(bfs());
        br.close();
        pw.close();
    }

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

    public static int bfs() {
        //初始化d数组
        Arrays.fill(d, -1);
        d[1] = 0;
        ArrayDeque<Integer> q = new ArrayDeque<>();
        q.offer(1);

        while (q.size() != 0) {
            int t = q.poll();
            for (int i = h[t]; i != -1; i = ne[i]) {
                int j = e[i];
                //只有当当前节点没有到过时才继续走
                if (d[j] == -1) {
                    d[j] = d[t] + 1;
                    q.offer(j);
                }
            }
        }
        return d[n];
    }
}