树是一种特殊的图,是无环连通图。图分成有向图和无向图。ab无向边处理相当于a到b的有向边和b到a的有向边组成。所以简化为我们只用考虑有向图。
有向图存储方式:
- 邻接矩阵 ——> g[a][b] 存储 a——>b边的信息 空间复杂度:(n^2)
- 邻接表(常用)单链表存储
注意:深度优先搜索和宽度优先搜索每个点都只会遍历一次
思路:
1.用数组存储图,需要idx存储边的下标索引,h[i]数组存储数字i指向的第一个数字,ne[i]存储与第i条边同起点的下一条边的idx,e[i]存储第i条边连向的终点
2.还需要一个boolean型数组st[]记录当前点有没有被走过,由于每个点只会经过一次,所以并不需要进行恢复现场的操作
3.从第一点进行遍历,走到链表存储的下一个点,如果没有被走过,就递归搜索该点链表存储的数字。
详解:
1.h[]数组的详解(见下图)
2.插入操作(保存边的关系)
题目:
详解(Java)
import java.util.Scanner;
public class 树与图的深度优先遍历 {
static int N = 100010,M = N * 2;//N节点数量,M边的数量,由于是无向图,所以边的数量是点的数量的2倍
static int[] h = new int[N];//h[i] 表示 i为起点 连向的 第一个点的 下标索引idx
static int[] e = new int[M];//e[i] 表示 边i 连向的 终点
static int[] ne = new int[M];//ne[i] 表示 与第i条边 同起点的 下一条边的 下标索引idx
static int idx;//边的下标索引
static boolean[] st = new boolean[N];//存点有没有走过
static int ans = N;//记录最小的最大值
static int n;
//初始化,每个点一开始都没有连接边,值都为-1
public static void init(){
for (int i = 1; i < N; i++) {
h[i] = -1;
}
}
//链表中插入值(一般都是在第一个插入)
public static void add(int a,int b){
e[idx] = b;//表示加入边的 终点的节点
ne[idx] = h[a];//表示 与加入边的 同起点的 下一条边的终点为 原先以a为起点 连向的第一个点的下标索引
h[a] = idx++;//表示 现在的以a为起点 连向的第一个点的 下标为新加入的 点b的下标索引,再让下标索引自增方便下一个新点的加入
}
//返回以u为根的 子树中 点的数量
public static int dfs(int u){
st[u] = true;//标记当前点已经被搜过了
int sum = 1;//以u为根的子树 点的数量 要把u自身算进去
int res = 0;//每个连通块的最大值
for (int i = h[u]; i != -1 ; i=ne[i]) {//从点u连向的边中的头节点开始,直到u连向的边全部搜完
int j = e[i];//节点的下标赋值给j
if(!st[j]){//当j没有被搜过时
//dfs(j) ——> 搜索点j连向的点
int s = dfs(j);//当前子树的大小
res = Math.max(res,s);
sum+=s;
}
}
//n-sum 为删除点的 上面的 连通块的大小
res = Math.max(res,n-sum);
ans = Math.min(ans,res);
return sum;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
//初始化
init();
for (int i = 0; i < n-1; i++) {
int a = sc.nextInt();
int b = sc.nextInt();
//无向边,分别指向对方
add(a,b);
add(b,a);
}
dfs(1);
System.out.println(ans);
}
}
树与图的深度优先遍历,主要是理解遍历方式,有了前面DFS的学习,注重如何实现。