1.数据结构导论
1.1.基础定义
树是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因
为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
在这种层次结构中有一个结点具有特殊的地位,这个结点称为该树的根结点,或称为树根。
这是一颗树
术语 | 描述 |
---|---|
根结点 | 每棵树都有一个根结点 50为这棵树的根节点 |
子节点 | 一个节点的后继节点被称为子节点 50的子节点为27,75 |
父节点 | 若一个节点含有子节点,则这个节点称为其子节点的父节点 50是27,75的父节点 |
兄弟节点 | 具有相同父节点的不同节点 61和85是兄弟节点 |
节点的度 | 一个节点含有的子节点的个数称为该节点的度 75的度为2,61的度为1 |
叶节点 | 度为0的节点称为叶节点 67、81都是叶子节点 |
树的深度 | 树中节点的最大层次 最深的节点为67,深度为4 |
节点的祖先 | 从根到该节点所经分支上的所有节点 50,75,61,71都是67的祖先 |
子孙 | 所有子节点以及子节点的子节点以及… 所有节点都是50的子孙 |
子树 | 以某节点为根的树 以75为根的子树包括7个节点 |
边 | 父子节点直接存在一条连边 50和27之间有条边 |
1.2.树的特性
一棵树中任意两个结点有且仅有唯一的一条路径连通。
一棵树如果有n个节点, 那么它一定有n-1条边。
在一棵树中添加一条边将会构成一个回路。
一棵有 n个节点的无向树,所有节点的度数和为2× (𝑛 − 1) 。
树形结构以树和二叉树最为常用,直观来看,树是以分支关系定义的层次结构。树形结构
中元素之间有着明显的层次关系,每一个元素可以和下层的多个元素相关, 但只能和上层
中一个元素相关。
1.3.树与链表
链表是一棵特殊的树。我们可以将链表的头节点看作树的根。树的每个节点可以有多个
next ,我们称之为子节点。所以链表是一棵每个节点都只有一个子节点的树。
链表 | 树 |
---|---|
头节点 | 根结点 |
只有一个后继节点(next) | 有一个或多个子节点(child) |
双向链表包括前驱结点(pre) | 除了根结点外,每个节点都有唯一的父节点(parent) |
一对一关系 | 一对多关系 |
1.4.树的基本操作-树的存储与创建
在创建一棵树的时候, 使用什么方法去存储呢?
可以采用与链表类似的方法。但由于子节点的数量不确定, 因此我们想到用vector来存储
树的子节点。
在一般树中,子节点的数量没有限制,所以常用的存储方法是使用vector数组G, G[0]保
存编号为0的顶点连接到的所有顶点, 由于n个结点的树只有n-1条边, vector数组实际占
用的空间为O(n) 。
每读到一条边(u,v) ,如果不知道谁是父亲谁是儿子,我们可以先在G[u]中添加一个v ,再
在G[v]中添加一个u 。
如果知道父子关系,可以只在父节点中添加儿子,而不用将边保存两份。
vector<int> G[100005]; //每个vector用来记录所有子节点的编号
int n;
int main()
{
scanf("%d", &n);
for(int i = 1; i < n; i++){
int u, v;
scanf("%d %d",&u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
return 0;
}
1.5.删除树的点和边
删除节点u时需要删除该节点的所有边。
Del(当前节点u){
for(u有连边的节点v){
从G[v]中删除u;
}
清空G[u];
}
删除边(u,v) 。
Del(边(u,v)){
从G[v]中删除u;
从G[u]中删除v;
}
1.6.树的遍历
遍历树的方式有很多,最常用的方式是DFS 。
DFS(当前节点u){
for(u的所有子节点v){
DFS(v);
}
}
如果是在不知父子的情况下,保存了双向边,那么需要做如下处理。
DFS(当前节点u, 父节点p){
for(u有连边的节点v){
if(v不是p)
DFS(v, u);
}
}
遍历方式对比 | |
---|---|
数组 | 从下标0开始遍历 |
链表 | 从头节点开始遍历 |
树 | 从根节点开始递归遍历 |
1.7.例题:叶子节点的数量
给出一棵n个节点的树,节点编号为1-n(根节点编号为1),求这棵树叶子结点的数量。
例如:
1─2─4─5
└─3
其中3和5是叶子节点,输出2。
1.7.1.输入
第一行:1个数n(1 < n <= 1000),表示树的节点数量。
后面n-1行:每行2个数x y,表示节点x是节点y的父节点(1 <= x, y <= n)。
1.7.2.输出
输出1个数,表示这棵树有多少个叶子节点。
1.7.3.输入样例
5
1 2
1 3
2 4
4 5
1.7.4.输出样例
2
1.7.5.分析
可以用一个bool数组t[i]来存储每一个节点,t[i]表示第i个节点是否有子节点,如果没有,i就是叶子节点。但要注意输入 n-1次,判断t[i]是否为false时要循环n次。t数组要开在外边,否则在main函数里要memset初始化一下。
1.7.6.代码
#include <bits/stdc++.h>
using namespace std;
bool t[1010];
int main() {
int n, ans = 0;
cin >> n;
for(int i = 1; i <= n - 1; i++) {
int a, b;
cin >> a >> b;
t[a] = true;
}
for(int i = 1; i <= n; i++) if(!t[i]) ans++;
cout << ans << endl;
return 0;
}