树的基本操作
小编参加第二十一次CSP认证考试时,被第三题的关于树的题卡了好久之后,下定决心对树进行系统的学习。小编经过赛后的练习,确定了自己感觉十分顺手的树的构建方法。
树的数据结构
小编比较喜欢,并且用起来十分顺手的树的数据结构如下(小编对用指针一向不是很擅长,因此选择了用vector数组来代替指针存储孩子的节点):
struct Node
{
int father; //树节点的父亲节点
int value; //树节点的值
vector<int> child; //树节点的孩子节点
}node[/*根据题目最大节点数来填写*/];
结构体Node代表着树中任意一个节点,存储着节点的信息,例如:节点的父亲节点、节点的数值以及节点的孩子节点。这些都是节点的基本信息,做题的时候,可以根据题目要求,在其中加入更多的其他信息,以满足题目的要求。
树的构建
以结构体数组为基础的数据结构相比于利用指针创建的树,虽然更耗空间,但是一部分操作却更加简单、容易理解,比如树的构建。
-
无向树的构建
假如输入的形式是:(u , v),其中u , v分别代表边上两个节点的序列号,利用如上的数据结构,只需要如下两条语句即可完成对无向树的创建。node[u].child.push_back(v); node[v].child.push_back(u);
-
有向树的构建
有向树的创建类似于无向树的创建,假如输入的形式是:(u , v) ,其中代表从节点u到节点v之间有一个树的边连接着,u是v的父节点,v是u的孩子节点。node[u].child.push_back(v);
寻找所有叶节点
叶节点相对于其他种类的树节点,具有一个特殊的性质就是其没有孩子节点,因此利用这个特殊的性质即可找到所有叶节点。任意一个节点的孩子节点的数量可以通过节点数据结构中vector数组child的元素数量得到。
for (i = 1; i <= n; i++)
{
if (node[i].child.size() == 0)
{
/* 对叶子节点的操作 */
}
}
无向树转有向树
无向树与有向树都具有树的特性,但是无向树并不包含有父子节点的关系,有一些关于树的算法题目,题目给的输入无向树的形式,但是题目经过分析,可以通过将无向树转换成有向树,以此来大幅度提升效率,同时不会对结果产生影响。
- 无向树可以通过上面的方法快速创建。
- 无向树是没有根节点的,但是要想转成有向树,则必须确定一个根节点。而从无向树中随意抽出一个节点,都可以当作有向树的根节点,用这个方法的时候,要仔细分析确定如此操作不会对题目的结果产生影响。
- 无向树的边上的两个节点是双向连接的,要想转换成有向树,可以依据已经建好的无向树,重新创建一个有向树。小编从根节点出发,利用广度优先搜索(BFS)的思想,进行有向树的创建。代码如下:
memset(vis, 0, sizeof(vis)); q.push(1); vis[1] = 1; while (!q.empty()) { int top = q.front(); q.pop(); for (j = 0; j < node_temp[top].child.size(); j++) { if (vis[node_temp[top].child[j]] == 0) { q.push(node_temp[top].child[j]); vis[node_temp[top].child[j]] = 1; node[top].child.push_back(node_temp[top].child[j]); //添加孩子节点 node[node_temp[top].child[j]].father = top; //指定孩子节点的父节点 } } }
- 上述代码中,node_temp是原本存储无向图的数据结构,而node是存储转换得到的有向图的数据结构。代码中以序号为1的节点为有向树的根节点。
寻找节点的所有父节点
在树中,除了根节点,每一个节点都是有父节点的,一些树的基本修改操作可以通过修改其所有父节点(包括父节点的父节点)来快速完成。在创建树的时候,除了根节点,其他节点的父节点都指向树中的一个节点序号,只有根节点的父节点是没有指向的,因此,将其初始化为-1,以实现对根节点的识别。代码如下(假设要遍历序号为u的节点的所有父节点):
int father = node[u].father;
while (father != -1)
{
/* 对父节点的操作 */
father = node[father].father;
}