CHAPTER_9 提高篇(3)——数据结构(2)
这节我们讲解二叉树的遍历。二叉树的遍历是指通过一定顺序访问二叉树的所有结点。遍历方法有四种:先序遍历、中序遍历、后序遍历、层序遍历。下面分别介绍这四种遍历。
9.2.1 先序遍历
先序遍历采用递归的方式进行,首先要将树分为三个部分:根节点、左子树、右子树。对于先序遍历来说,总是先访问根节点,然后访问左子树,最后访问右子树。访问左子树和右子树的过程是一个递归的过程,例如访问左子树时,同样要访问它的根节点,然后访问它的左子树,最后访问它的右子树。访问右子树的过程也同样。
对于上图这个例子,先序遍历的过程如下:1 2 4 6 7 3 5 。
为了实现递归的先序遍历,我们需要两样东西:递归式和递归边界。其中递归式已经由定义得到,即按照根节点、左子树、右子树的顺序访问。那么递归的边界是什么呢?从上个例子可以发现,递归边界是二叉树和为一个空树。即访问子树时,发现子树为空,那么可以停止子树的访问而继续后面的访问。
由此可以给出递归的先序遍历代码:
void preorder(node* root) {
if(root==NULL) //到达递归边界,回退
return;
cout<<root->data<<endl; //访问根节点
preorder(root->lchild); //访问左子树
preorder(root->rchild); //访问右子树
}
9.2.2 中序遍历
中序遍历采用递归的方式进行,它总是先访问左子树,然后访问根节点,最后访问右子树。同样地,访问左子树和右子树的过程是递归进行的。例如访问左子树时,先访问它的左子树,再访问它的根节点,最后访问它的右子树。递归的过程中,若子树为空,则停止子树的访问进行后续的遍历。
对于上图例子,中序遍历的结果为:4 6 7 2 1 5 3 。
中序遍历的递归实现思路与先序遍历相同,只是左子树和根节点的访问顺序交换了,递归代码如下:
void preorder(node* root) {
if(root==NULL) //到达递归边界,回退
return;
preorder(root->lchild); //访问左子树
cout<<root->data<<endl; //访问根节点
preorder(root->rchild); //访问右子树
}
9.2.3 后序遍历
后序遍历也采用递归的方式进行,它总是先访问左子树,然后访问右子树,最后访问根节点。同样地,访问左子树和右子树的过程是递归进行的。例如访问左子树时,先访问它的左子树,再访问它的右子树,最后访问它的根节点。递归的过程中,若子树为空,则停止子树的访问进行后续的遍历。
同样是上面那张图,后续遍历的结果为:7 6 4 2 5 3 1 。
后序遍历的递归实现和上面两种是大致相同的,唯一的区别只是访问顺序不同,代码如下:
void preorder(node* root) {
if(root==NULL) //到达递归边界,回退
return;
preorder(root->lchild); //访问左子树
preorder(root->rchild); //访问右子树
cout<<root->data<<endl; //访问根节点
}
9.2.4 层序遍历
可以看到,二叉树的先序、中序、后续的实现思路是相同的。其实这种思想我们在DFS中已经学习,一层一层的递归实际上就是栈的实现。而层序遍历则和BFS有着相同的思想,应用的数据结构为队列。
层序遍历是指按层次的顺序从根节点向下逐层进行遍历,且对同一层进行从左到右的顺序。层序遍历从逻辑上非常直观,例如下面这个例子,层序遍历的结果为:1 2 3 4 5 6 。
这个过程和BFS很像,因为BFS进行搜索总是以广度为第一关键词,对应到二叉树中广度就体现在层次上。因此层次遍历就是二叉树从根节点开始的广度优先搜索,其思路就是BFS的思路:
(1)将根节点入队;
(2)访问队首结点,然后队首出队;
(3)如果该结点有左孩子,则左孩子入队;
(4)如果该节点有右孩子,则右孩子入队;
(5)返回第二步循环,直到队列为空时退出循环。
代码如下:
void Layerorder(node* root) {
queue<node*> q;
q.push(root);
while(!q.empty()) {
node *now=q.front(); //访问该节点
q.pop();
if(now->lchild) {
q.push(now->lchild)
}
if(now->rchild) {
q.push(now->rchild)
}
}
}