网站点击深圳网站优化培训
一、树的概念
1. 树的定义
树型结构是⼀类重要的⾮线性数据结构。
2. 树的基本术语
3. 有序树和⽆序树
4. 有根树和⽆根树
注意:
如果是⽆根树,⽗⼦关系不明确,此时我们需要把所有的情况都存下来。⽐如a和b之间有⼀条 边,我们不仅要存a有⼀个孩⼦b,也要存b有⼀个孩⼦a。
甚⾄有的时候,在有根树的题⽬⾥,也要这样存储。
二、树的存储
1. 孩⼦表⽰法
2. 实现⽅式⼀:⽤vector数组实现
#include <iostream>
#include <vector>using namespace std;const int N = 1e5 + 10;int n;
vector<int> edges[N]; // 存储树int main()
{cin >> n;for(int i = 1; i < n; i++){int a, b; cin >> a >> b;// a 和 b 之间有一条边edges[a].push_back(b);edges[b].push_back(a);}return 0;
}
3. 实现⽅式⼆:链式前向星
#include <iostream>using namespace std;const int N = 1e5 + 10;// 链式前向星
int h[N], e[N * 2], ne[N * 2], id;
int n;// 其实就是把 b 头插到 a 所在的链表后面
void add(int a, int b)
{id++;e[id] = b;ne[id] = h[a];h[a] = id;
}int main()
{cin >> n;for(int i = 1; i < n; i++){int a, b; cin >> a >> b;// a 和 b 之间有一条边add(a, b); add(b, a);}return 0;
}
关于vector数组以及链式前向星:
- 前者由于⽤到了容器vector,实际运⾏起来相⽐较于后者更耗时,因为vector是动态实现的。
- 但是在如今的算法竞赛中,⼀般不会⽆聊到卡这种常数时间。也就是vector虽然慢,但不会因此⽽超时。
三、树的遍历
1. 深度优先遍历-DFS
时间复杂度:
简单估计⼀下,在 dfs 的整个过程中,会把树中所有的边扫描量两遍。边的数量为n − 1 ,因此时间复杂度为O(N) 。
空间复杂度:
最差情况下,结点个数为 n 的树,⾼度也是 n ,也就是变成⼀个链表。此时递归的深度也是 n ,此时的空间复杂度为O(N) 。
2. 宽度优先遍历-BFS
时间复杂度:
所有结点只会⼊队⼀次,然后出队⼀次,因此时间复杂度为 O(N) 。
空间复杂度:
最坏情况下,所有的⾮根结点都在同⼀层,此时队列⾥⾯最多有 n − 1 个元素,空间复杂度为 O(N) 。
3. 练习
(1)题目描述
题⽬描述:
给定⼀棵树,该树⼀共有 n 个结点,编号分别是 1 ∼ n 。
输⼊描述:
第⼀⾏⼀个整数 n ,表⽰ n 个结点。
接下来 n − 1 ⾏,每⾏两个整数 u, v ,表⽰ u 和 v 之间有⼀条边。
测试⽤例:
111 37 33 101 54 52 111 26 1111 811 9
(2)实现代码
深度优先遍历——DFS
// // 用 vector 数组存储树// #include <iostream>
// #include <vector>// using namespace std;// const int N = 1e5 + 10;// int n;
// vector<int> edges[N]; // 存储图
// bool st[N]; // 标记哪些点已经访问过了void dfs(int u)
{cout << u << " ";st[u] = true; // 当前这个点已经访问过了// 访问所有的孩子for(auto v : edges[u]){if(!st[v]){dfs(v);}}
}// int main()
// {
// // 建树
// cin >> n;
// for(int i = 1; i < n; i++)
// {
// int a, b; cin >> a >> b;
// edges[a].push_back(b);
// edges[b].push_back(a);
// }// // 深度优先遍历
// dfs(1);// return 0;
// }// 用链式前向星存储
#include <iostream>using namespace std;const int N = 1e5 + 10;// 存图
int h[N], e[N * 2], ne[N * 2], id;
int n;bool st[N]; // 标记哪些点已经访问过了void add(int a, int b)
{id++;e[id] = b;ne[id] = h[a];h[a] = id;
}void dfs(int u)
{cout << u << " ";st[u] = true;for(int i = h[u]; i; i = ne[i]){int v = e[i]; // 孩子if(!st[v]){dfs(v);}}
}int main()
{// 建树cin >> n;for(int i = 1; i < n; i++){int a, b; cin >> a >> b;add(a, b); add(b, a);}// 深度优先遍历dfs(1);return 0;
}// 斐波那契数列
int fib(int n)
{if(n == 0 || n == 1) return n;return fib(n - 1) + fib(n - 2);
}
宽度优先遍历——BFS
// 用 vector 数组存树
// #include <iostream>
// #include <queue>
// #include <vector>// using namespace std;// const int N = 1e5 + 10;// int n;
// vector<int> edges[N]; // 存树
// bool st[N]; // 标记哪些节点已经访问过了// void bfs()
// {
// queue<int> q;
// q.push(1);
// st[1] = true;// while(q.size())
// {
// int u = q.front(); q.pop();
// cout << u << " ";// for(auto v : edges[u])
// {
// if(!st[v])
// {
// q.push(v);
// st[v] = true;
// }
// }
// }
// }// int main()
// {
// // 建树
// cin >> n;
// for(int i = 1; i < n; i++)
// {
// int a, b; cin >> a >> b;
// edges[a].push_back(b);
// edges[b].push_back(a);
// }// bfs();// return 0;
// }// 用链式前向星存储树
#include <iostream>
#include <queue>using namespace std;const int N = 1e5 + 10;int n;
int h[N], e[N * 2], ne[N * 2], id;
bool st[N];void add(int a, int b)
{id++;e[id] = b;ne[id] = h[a];h[a] = id;
}void bfs()
{queue<int> q;q.push(1);st[1] = true;while(q.size()){int u = q.front(); q.pop();cout << u << " ";for(int i = h[u]; i; i = ne[i]){int v = e[i];if(!st[v]){q.push(v);st[v] = true;}}}
}int main()
{cin >> n;for(int i = 1; i < n; i++){int a, b; cin >> a >> b;add(a, b); add(b, a);}bfs();return 0;
}