二叉树的遍历
二叉树遍历:
按一定规律对二叉树中的每个结点进行访问且仅访问一次。
二叉树的基本结构由根结点、左子树和右子树组成。
用L、D、R分别表示遍历左子树、访问根结点、遍历右子树,
那么对二叉树的遍历顺序就可以有:
- 访问根,遍历左子树,遍历右子树 (记做DLR)。
- 访问根,遍历右子树,遍历左子树 (记做DRL)。
- 遍历左子树,访问根,遍历右子树 (记做LDR)。
- 遍历左子树,遍历右子树,访问根 (记做LRD)。
- 遍历右子树,访问根,遍历左子树 (记做RDL)。
- 遍历右子树,遍历左子树,访问根 (记做RLD)。
在以上六种方式中,按先左后右的顺序:就只剩下DLR 、LDR 和LRD三种。
根据对根的访问先后顺序:
- DLR为先序遍历或先根遍历;
- LDR为中序遍历(对称遍历);
- LRD为后序遍历。
注意:先序、中序、后序遍历是递归定义的,即在其子树中亦按上述规律进行遍历。
先序遍历(DLR)操作过程:
若二叉树为空,则为空操作,否则依次执行如下操作:
(1)访问根结点;
(2)按先序遍历左子树;
(3)按先序遍历右子树。
中序遍历(LDR)操作过程:
若二叉树为空,则为空操作,否则依次执行如下操作:
(1)按中序遍历左子树;
(2)访问根结点;
(3)按中序遍历右子树。
后序遍历(LRD)操作过程:
若二叉树为空,则为空操作,否则依次执行如下操作:
(1)按后序遍历左子树;
(2)按后序遍历右子树;
(3)访问根结点。
如下二叉树,其先序、中序、后序遍历的序列为:
- 先序遍历: A、B、D、F、G、C、E、H 。
- 中序遍历: B、F、D、G、A、C、E、H 。
- 后序遍历: F、G、D、B、H、E、C、A 。
表达式求值:(a + b * c) – d / e
- 先序(前缀):
-+a*bc/de
- 中序(中缀):
a+b*c-d/e
- 后序(后缀):
abc*+de/-
中缀:表达式通常形式,只是没有括号;
前缀:波兰表达式,波兰逻辑学家J.Lukasiewicz提出;
后缀:逆波兰表达式,计算机易于求值。
先序遍历算法:
void PreOrder(BiTree root)
{ if (root!=NULL)
{
Visit(root ->data); /*访问根结点*/
PreOrder(root ->LChild); /*先序遍历左子树*/
PreOrder(root ->RChild); /*先序遍历右子树*/
}
}
中序遍历算法:
void InOrder(BiTree root)
{ if (root!=NULL)
{
InOrder(root ->LChild); /*中序遍历左子树*/
Visit(root ->data); /*访问根结点*/
InOrder(root ->RChild); /*中序遍历右子树*/
}
}
后序遍历算法:
void PostOrder(BiTree root)
{ if(root!=NULL)
{
PostOrder(root ->LChild); /*后序遍历左子树*/
PostOrder(root ->RChild); /*后序遍历右子树*/
Visit(root ->data); /*访问根结点*/
}
}
中序遍历二叉树的递归过程:
递归算法的时间复杂度:
- n个结点,每个都作子树被访问;
- 入栈、出栈和访问各一次;
- 时间复杂度为O(n).
遍历算法应用
1. 输出二叉树中的结点
给出的是先序的算法,其他两种只需将访问操作变为输出操作即可。
void PreOrder(BiTree root)
/*先序遍历二叉树,root为指向二叉树根结点的指针*/
{
if (root!=NULL)
{
printf("%c ", root->data); /*输出结点*/
PreOrder(root->LChild); /*先序遍历左子树*/
PreOrder(root->RChild); /*先序遍历右子树*/
}
}
2. 输出二叉树中的叶子结点
void PreOrder(BiTree root)
/*先序遍历二叉树, root为指向二叉树根结点的指针*/
{
if (root!=NULL)
{
if (root->LChild==NULL && root->RChild==NULL)
printf("%c ", root->data); /*输出叶子结点*/
PreOrder(root->LChild); /*先序遍历左子树*/
PreOrder(root->RChild); /*先序遍历右子树*/
}
}
3. 统计叶子结点数目
方法1(后序遍历):
/* LeafCount保存叶子结点数目的全局变量,调用之前初始化值为0 */
void leaf(BiTree root)
{
if(root!=NULL)
{
leaf(root->LChild);
leaf(root->RChild);
if (root->LChild==NULL && root->RChild==NULL)
LeafCount++;
}
}
方法2(后序遍历):
采用分治算法,如果是空树,返回0;如果只有一个结点,返回1;否则为左右子树的叶子结点数之和。
int leaf_b(BiTree root)
{
int LeafCount;
if(root==NULL)
LeafCount=0;
else
if((root->Lchild==NULL) && (root->Rchild==NULL))
LeafCount=1;
else
LeafCount=leaf_b(root->LChild)+leaf_b(root->RChild);
return LeafCount;
}
4. 建立二叉链表方式存储的二叉树
给定二叉树遍历序列,创建二叉树(链表)。
这里说的遍历序列是一种“扩展遍历序列”:用小圆点显示表示空子树。
如,先序遍历序列A B . D F. .G . .C . E . H . .
【算法思想】
读入当前根结点的数据,如果是“.”则将当前树根置为空,否则申请一个新结点,存入当前根结点的数据,分别用当前根结点的左子域和右子域进行递归调用,创建左、右子树。
void CreateBiTree(BiTree *bt)
{
char ch;
ch = getchar();
if(ch == '.') *bt = NULL;
else
{
*bt = (BiTree)malloc(sizeof(BiTNode)); //生成一个新结点
(*bt)->data=ch;
CreateBiTree(&((*bt)->LChild)); //生成左子树
CreateBiTree(&((*bt)->RChild)); //生成右子树
}
}
5. 求二叉树的高度
高度其实就是左右子树高度最大值+1。
- 后序:先求hl, hr, 再
return hl>hr?hl+1:hr+1
; - 先序:从根累计高度(h=1),子树h+1 。
后序算法:
int PostTreeDepth(BiTree bt) /* 后序遍历求二叉树的高度递归算法 */
{
int hl, hr, max;
if(bt != NULL)
{ hl = PostTreeDepth(bt->LChild); /* 求左子树的深度 */
hr = PostTreeDepth(bt->RChild); /* 求右子树的深度 */
max = hl>hr?hl:hr; /* 得到左、右子树深度较大者*/
return(max+1); /* 返回树的深度 */
}
else return(0); /* 如果是空树,则返回0 */
}
先序算法:
void PreTreeDepth(BiTree bt, int h)
/* h为bt指向结点所在层次,初值为1*/
/*depth为当前求得的最大层次,为全局变量,初值为0 */
{
if(bt != NULL)
{
if(h > depth) depth = h; /*层次值h大于depth,更新*/
PreTreeDepth(bt->Lchild, h+1); /* 遍历左子树 */
PreTreeDepth(bt->Rchild, h+1); /* 遍历右子树 */
}
}
6. 按树状打印二叉树
示意:
【算法思想】
二叉树的横向打印应是竖向显示的90度旋转,恰好为逆中序顺序(RDL)。
每次递归进层时层深度加1。
void PrintTree(BiTree bt, int nLayer) //nLayer:结点层次
{
if(bt == NULL) return;
PrintTree(bt->RChild, nLayer+1);
for(int i=0;i<nLayer;i++)
printf(" ");//深度
printf("%c\n", bt->data);//按逆中序输出结点,用层深决定的左、右位置
PrintTree(bt->LChild,nLayer+1);
}