1、基本概念
tree是节点的集合。这个集合可以是空集;若不是空集,则有一个根节点和零个或多个非空子树,每个子树都被根节点使用一条有向边所连接。
由树的定义,我们可以知道,树是由节点和有向边组成的,根据树的结构,我们有以下衍生的定义:
- 整棵树最上层的节点称为根节点(root);
- 连接两个节点的称为有向边(edge);
- 有向边的头称为父节点(parent),尾称为子节点(child);
- 没有子节点的节点称为叶节点(leaf);
- 若子节点的最大个数为n,则该树称为n叉树,即最多有两个子节点的树称为二叉树;
- 拥有相同父节点的子节点之间互为兄弟节点(sibling);
- 根节点至任何节点有唯一的路径(path),路径所经过的边的个数称为路径的长度(length);
- 根节点至任一节点的路径长度,称为该节点的深度(depth),则根节点的深度为0;
- 某一节点到其最深的子节点的路径长度称为该节点的高度(height),则树的高度为根节点的高度;
- 若节点a和节点b之间存在一条路径,则a称为b的祖代(ancestor),b称为a的子代(descendant);
- 某一节点和其子节点的个数之和称为该节点的大小(size)。
2、实现结构
树最直接的实现结构就是有节点数据和指向子节点的链组成,但是子节点的个数是不确定的,如果直接链接,将造成空间的巨大浪费。目前典型的结构定义如下:
struct TreeNode
{
TreeNode()
{
this->data = 0;
this->firstChild = NULL;
this->nextSibling = NULL;
}
TreeNode(int data)
{
this->data = data;
this->firstChild = NULL;
this->nextSibling = NULL;
}
int data;
TreeNode* firstChild;
TreeNode* nextSibling;
};
这里通过firstChild访问到子节点链表的表头,通过nextSibling是子节点形成链表。这样通过有限的结构,就可满足树无限的扩展。
3、节点遍历
要访问到树中的每个节点,根据不同的顺序得到的结果是不一样的,目前有一下几种遍历方法:
- 广度优先遍历。这种遍历方法,按照层次去遍历,依次遍历根节点,子节点及其兄弟节点,然后是下一层的子节点和兄弟节点,以下是实现算法:
void breadthFirstTravel(TreeNode* root)
{
cout<<"bft:";
queue<TreeNode*> q;
q.push(root);
while (!q.empty())
{
TreeNode* node = q.front();
q.pop();
while (node != NULL)
{
cout <<node->data<<" ";
if (node->firstChild)
{
q.push(node->firstChild);
}
node = node->nextSibling;
}
}
cout<<endl;
}
- 深度优先遍历。这种遍历方法,是沿着树的深度进行遍历,当节点v的的所有子节点都已经访问的后,将回溯到v的父节点继续搜索,不断重复以上过程直到所有节点都被访问到。这里节点的访问顺序不同,得到的结果也是不一致的,目前有三种访问顺序:
- 前序遍历。这种顺序,先访问根节点,然后访问其他子树,算法如下:
void depthFirstTravel_DLR(TreeNode* root)
{
cout<<"dft_DLR:";
stack<TreeNode* > s;
s.push(root);
while (!s.empty())
{
TreeNode* node = s.top();
s.pop();
cout<<node->data<<" ";
//记录右兄弟
if (node->nextSibling)
{
s.push(node->nextSibling);
}
//记录第一个子节点
if (node->firstChild)
{
s.push(node->firstChild);
}
}
cout<<endl;
}
这里是先访问根节点,然后是第一个子树,最后是该子树的兄弟子树。由于需要回溯节点,我们需要借助stack。
2. 中序遍历。这种顺序是先访问第一个子树,然后访问根节点,最后子树的兄弟子树。算法如下:
void depthFirstTravel_LDR(TreeNode* root)
{
//先遍历第一个子女
TreeNode* child = root->firstChild;
if (child)
{
depthFirstTravel_LDR(child);
}
//访问自己
cout<<root->data<<" ";
//遍历其他子女
if (child)
{
TreeNode* sibling = child->nextSibling;
while (sibling)
{
depthFirstTravel_LDR(sibling);
sibling = sibling->nextSibling;
}
}
}
这里直接使用了递归的实现,看起来更加简洁,但递归也是用stack实现的,实际本质是一样的。
3. 后序遍历。这种顺序先遍历所有的子树,然后再访问根节点。算法如下:
void depthFirstTravel_LRD(TreeNode* root)
{
//先遍历所有子女
TreeNode* child = root->firstChild;
if (child)
{
depthFirstTravel_LRD(child);
TreeNode* sibling = child->nextSibling;
while (sibling)
{
depthFirstTravel_LRD(sibling);
sibling = sibling->nextSibling;
}
}
//访问自己
cout<<root->data<<" ";
}
以上是三种遍历顺序,下面是测试代码:
void treeTest()
{
TreeNode *a = new TreeNode(1);
TreeNode *b = new TreeNode(2);
TreeNode *c = new TreeNode(3);
TreeNode *d = new TreeNode(4);
TreeNode *e = new TreeNode(5);
TreeNode *f = new TreeNode(6);
TreeNode *g = new TreeNode(7);
TreeNode *h = new TreeNode(8);
TreeNode *i = new TreeNode(9);
a->firstChild = b;
b->firstChild = f;
b->nextSibling = c;
c->firstChild = h;
c->nextSibling = d;
d->nextSibling = e;
f->nextSibling = g;
h->nextSibling = i;
breadthFirstTravel(a); //bft:1 2 3 4 5 6 7 8 9
depthFirstTravel_DLR(a); //dft_DLR:1 2 6 7 3 8 9 4 5
cout<<"dft_LDR:";
depthFirstTravel_LDR(a); //dft_LDR:6 2 7 1 8 3 9 4 5
cout<<endl;
cout<<"dft_LRD:";
depthFirstTravel_LRD(a); //dft_LRD:6 7 2 8 9 3 4 5 1
cout<<endl;
}