二叉树的遍历是指通过一定的顺序访问二叉树的所有结点。遍历方法一般有四种:先序遍历、中序遍历、后序遍历和层次遍历,其中,前三种一般使用深度优先搜索(DFS)实现,而层次遍历一般使用广度优先搜索(BFS)实现
把一颗二叉树分为三个部分:根节点、左子树、右子树,且对左子树和右子树可以同样进行这样的划分,这样对树的遍历就可以分为对这三个部分的遍历,这三种方法中,左子树一定要先于右子树遍历,且所谓的“先中后”都是指根节点root在遍历中的位置,因此先序遍历的访问顺序是根节点-左子树-右子树;中序遍历的访问顺序是左子树-根节点-右子树;后序遍历的访问顺序是左子树-右子树-根节点。
先序遍历
1、先序遍历的实现
对先序遍历来说,总是先访问根节点root,再访问左子树和右子树,因此先序遍历的顺序是根节点-左子树-右子树
为了实现递归的先序遍历,需要得到两样东西:递归式和递归边界。其中递归式已经可以由先序遍历直接得到,即先访问根节点(可以做任何事情),再递归访问左子树,最后递归访问右子树,递归边界就是子树为空
先序遍历的代码如下:
void preorder(node *root){
if(root==NULL){
//到达空树,递归边界
return;
}
//访问根节点root,例如将其数据域输出
printf("%d\n",root->data);
//访问左子树
preorder(root->lchild);
//访问右子树
preorder(root->rchild);
}
2、先序遍历序列的性质
由于先序遍历先访问根节点,因此对一颗二叉树的先序遍历序列,序列的第一个一定是一个根节点。例如再上面的例子中,对整颗二叉树,A是先序遍历的第一个,因此A是根节点,而对A的左子树的三个结点,它们的先序序列是BDE,由于B是第一个,因此B是这颗树的根节点。
中序遍历
1、中序遍历的实现
中序遍历的实现思路和先序遍历相同,只不过把对根节点的访问放到左子树和右子树中间,代码如下:
void inorder(node *root){
if(root==NULL){
//到达空树,递归边界
return;
}
//访问左子树
inorder(root->lchild);
//访问根节点root,例如将其数据域输出
printf("%d\n",root->data);
//访问右子树
inorder(root->rchild);
}
2、中序遍历的性质
由于中序遍历总是把根节点放在左子树和右子树中间,因此只要知道根节点,就可以通过根节点在中序遍历中的位置区分出左子树和右子树。
后序遍历
1、后序遍历的实现
与上述两种方法思路相同,只是把根节点放在最后访问
代码如下:
void postorder(node *root){
if(root==NULL){
//到达空树,递归边界
return;
}
//访问左子树
postorder(root->lchild);
//访问右子树
postorder(root->rchild);
//访问根节点root,例如将其数据域输出
printf("%d\n",root->data);
}
2、 后序遍历的性质
后序遍历总是把根节点放在最后访问,这和先序遍历恰好相反。因此对后序遍历来说,序列的最后一个肯定是根结点
总的来说,无论是先序遍历序列还是后序遍历序列,都必须知道中序遍历序列才能唯一的确定一棵树,这是因为通过先序遍历和后序遍历都只能得到根节点,而只有中序遍历才能把根节点的左右子树分开,从而递归生成一颗二叉树。当然,这个做法需要保证在所有元素都不相同时才能使用。
层次遍历
层次遍历是指按层次的顺序从根节点向下逐层进行遍历,且对同一层的结点从左到右遍历
层次遍历相当于对二叉树从根节点开始的广度优先搜索,其基本思路如下:
- 将根节点root加入队列q
- 取出队首结点,访问它
- 如果该结点有左孩子,将左孩子入队
- 如果该结点有右孩子,将右孩子入队
- 返回2,知道队列为空
按照上述描述写出代码如下:
//层次遍历
void LayerOrder(node *root){
queue<node *>q;//注意队列里是存地址
q.push(root);//将根节点地址入队
while(!q.empty()){
node *now=q.front();//取出队首元素
q.pop();
printf("%d",now->data);//访问队首元素
if(now->lchild!=NULL) q.push(now->lchild);//左子树非空
if(now->rchild!=NULL) q.push(now->rchild);//右子树非空
}
}
可以发现,这里使用的队列中的元素是node*型而不是node型。这是因为队列中保存的知识原元素的一个副本,因此如果队列中直接存放node 型,当需要修改队首元素时,就会无法对原元素进行修改(即只修改了队列中的副本),故让队列中存放node型变量的地址,也就是node*型变量。这样就可以通过访问地址去修改原元素,就不会有问题了。
另外还需指出,很多题目当中要求计算出每个结点所处的层次,这时就需要在二叉树结点的定义中添加一个记录层次layer的变量
struct node{
int data;//数据域
int layer;//层次
node *lchild;//指向左子树的根结点的指针
node *rchild;//指向右子树的根结点的指针
};
需要在根节点入队前就先令根节点的layer为1来表示根节点是第一层(也可以令根节点的层号为0,由题意而定),之后再now-lchild和noe-rchild入队前,把它们的层号都记为当前结点的层号加1
代码如下:
//层次遍历
void LayerOrder(node *root){
queue<node *>q;//注意队列里是存地址
root->layer=1;//根节点的层号为1
q.push(root);//将根节点地址入队
while(!q.empty()){
node *now=q.front();//取出队首元素
q.pop();
printf("%d",now->data);//访问队首元素
if(now->lchild!=NULL){
now->lchild->layer=now->layer+1;
q.push(now->lchild);//左子树非空
}
if(now->rchild!=NULL){
now->rchild->layer=now->layer+1;//当前层号加1
q.push(now->rchild);//右子树非空
}
}
}
来源:https://blog.csdn.net/amf12345/article/details/99412981