递归数据结构的定义
▼▼▼
采用递归方式定义的数据结构称为递归数据结构。在递归数据结构定义中包含的递归运算称为基本递归运算。
对于二叉树,采用递归数据结构的定义如下:
RD = (D,Op)
其中,D是所有二叉树(含空二叉树)的集合Op= {op1,op2}:
op1(b)=b ->lchild b为含一个或一个以上节点的二叉树op2(b)=b->rchild b为含一个或一个以上节点的二叉树
显然,这两个基本的递归数据结构的运算符都是一元运算符,且具有封闭性。
二叉树的遍历算法设计
▼▼▼
二叉树的遍历是指按照一定次序访问树中所有结点,并且每个结点均被访问一次,且仅被访问一次。
由二叉树的递归定义可得:遍历一棵二叉树便要决定对根结点N、左子树L和右子树R的访问顺序。按照先遍历左子树再遍历右子树的原则,常见的遍历次序有先序(NLR)、中序(LNR)、后序(LRN)。
常见的4中递归遍历方法如下:
①先序遍历
②中序遍历
③后序遍历
④层序遍历
1先序遍历先序遍历二叉树的过程如下:
➤访问根结点;➤先序遍历左子树;➤先序遍历右子树。➤先序遍历的递归算法如下:void PreOrder(BiTree T){ if(T != NULL) { visit(T); //访问根结点 PreOrder(T->lchild); //先序递归访问左子树 PreOrder(T->rchild); //先序递归访问右子树 }}//提示:先序遍历序列中第一个结点是根结点
➤先序遍历的非递归算法如下:
void PreOrder(BiTree T){ InitStack(S); p = T; while(p || !StackEmpty(S)) { if(p) { visit(p->data); push(S,p); p = p->lchild; } else { pop(S,p); p = p->rchild; } } }
提示:先序遍历序列中第一个结点是根结点
2中序遍历中序遍历二叉树的过程如下:
➤中序遍历左子树;
➤访问根结点;
➤中序遍历右子树。
➤中序遍历的递归算法如下:
void InOrder(BiTree T){ if(T != NULL) { InOrder(T->lchild); //中序递归访问左子树 visit(T); //访问根结点 InOrder(T->rchild); //中序递访问右子树 }}
➤中序遍历的非递归算法如下:
void InOrder(BiTree T){ InitStack(S); p = T; while(p || !StackEmpty(S)) { if(p) { push(S,p); p = p->lchild; } else { pop(S,p); visit(p->data); p = p->rchild; } } }
提示:中序遍历序列中,若已知根结点,则其前为左子树的中序遍历序列,其后为右子树的中序遍历序列。
3后序遍历后序遍历二叉树的过程如下:
➤后序遍历左子树;➤后序遍历右子树;➤访问根结点。➤后序遍历的递归算法如下:void PostOrder(BiTree T){ if(T != NULL) { PostOrder(T->lchild); //中序递归访问左子树 PostOrder(T->rchild); //中序递访问右子树 visit(T); //访问根结点 }}
➤后序遍历的非递归算法如下:
void PostOrder(BiTree T){ InitStack(S); p = T; while(p || !StackEmpty(S)) { if(p) { push(S,p); p = p->lchild; } else { pop(S,p); if(p->rchild && (p->rchild).tag == 0) { push(S,p); p = p->rchild; } else { visit(p->data); p.tag = 1; p = NULL; } } } }
说明:在二叉树的存储结构中每个结点增加一个是否访问的tag域(标记),其初始值为0
4层次遍历层次遍历的思路和先序遍历、中序遍历、后序遍历的方式都不一样,它是按照层级的方式来进行遍历的。
先遍历第一层的所有结点,再遍历第二层的所有结点,依次类推进行遍历。要进行层次遍历,需要借助一个队列。层次遍历的算法过程可以概括为:➤先将二叉树根结点入队,➤然后出队,访问该出队结点,➤若它有左子树,则将左子树根结点入队,➤若它有右子树,则将右子树根结点入队,➤然后出队,访问出队结点......➤如此反复,直到队列为空。
void LevelOrder(BiTree T){ InitQueue(Q); //初始化辅助队列 BiTree p; //p是遍历指针 EnQueue(Q,T); //将根结点入队 while(!isEmpty(Q)) //队列不为空时,循环 { DeQueue(Q,p); //对头元素出队 visit(p); //访问当前p所指向结点 if(p->lchild != NULL) EnQueue(Q,p->lchild); //若左子树不为空,则左子树入队 if(p->rchild != NULL) EnQueue(Q,p->rchild); //若右子树不为空,则右子树入队 } }
提示:层序遍历序列的第一个结点是根结点
例题1 采用先序遍历递归算法查找值为x的节点。找到后返回其指针,否则返回NULL。
➤查找节点FindNode(*b,x)
➤解:其递归模型如下:
BTNode *FindNode(BTNode *b,ElemType x){ BTNode *p; if(b == NULL) return NULL; else if(b->data == x) return b; else { p = FindNode(b->lchild,x); if(p!=NULL) return p; else return FindNode(b->rchild,x); }}
例题2 假设二叉树采用二叉链存储结构,设计一个算法输出值为x的结点的所有祖先。
➤解:根据二叉树的中祖先的定义可知:若结点p的左孩子或右孩子是结点q,则结点p是结点q的祖先;若结点p的左孩子或右孩子是q结点的祖先,则结点p也是结点q的祖先。➤设f(b,x)表示结点b是否为值是x的结点的祖先。若结点b是值为x的结点的祖先,f(b,x)返回true;否则返回false。当f(b,x)为true时,输出结点b的值。
求值为x的结点的所有祖先的递归模型如下:bool ancestor(BTNode *b, ElemType x){ if(b == NULL) return false; else if(b->lchild !=NULL && b->lchild->data == x || b->rchild !=NULL && B->rchild->data == x) { printf("%c",b->data); return true; } else if(ancestor(b->lchild,x) || ancestor(b->rchild,x)) { printf("%c",b->data); return true; } else return false; }
例题3 假设二叉树采用二叉链存储结构存储,设计一个算法,求先序遍历序列中第k(1≦k≦二叉树中结点个数)个结点的值。➤解:用一个全局变量n(初值为1)保存先序遍历时访问节点的序号。当二叉树b为空时返回特殊字符‘ ’ (‘ '为空格字符),当k==n时表示找到了满足条件的节点,返回b->data;当k≠n时,在左子树中查找,若找到了返回该值,否则在右子树中查找,并返回其结果。➤对应的递归模型如下:
//递归算法代码如下:int n = 1; //全局变量 ElemType PreNode(BTNode *b, int k) //先序遍历第k个结点值的递归算法 { ElemType ch; if(b == NULL) return ' '; if(n == k) return b->data; ch = PreNode(b->lchild,k); //遍历左子树 if(ch != ' ') return ch; //在左子树中找到返回 ch = PreNode(b->rchild,k); //遍历右子树 return ch; //遍历右子树的结构 } //非递归算法如下:ElemType PreNode(BTNode *b, int k) //先序遍历第k个结点值的非递归算法 { BTNode *St[MaxSize], *p; int top = -1; n = 0; if(b != NULL) { top++; //根结点进栈 St[top] = b; while(top > -1) //栈不为空时循环 { p = St[top]; //退栈并访问该结点 top--; n++; if(n == k) return p->data; if(p->lchild != NULL) //左孩子结点进栈 { top++; St[top] = p->lchild; } if(p->rchild != NULL) //右孩子结点进栈 { top++; St[top] = p->rchild; } } } return ' ';}
真题回顾
习
题1假设二叉树采用二叉链存储结构存储,设计一个算法,求中序遍历序列中第k(1≦k≦二叉树中结点个数)个结点的值。
➤①请写出中序遍历的递归算法:➤②请写出中序遍历的非递归算法:提示:本题对应的解题方法和例题3类似,只是把先序遍历换成中序遍历,套用中序递归遍历的模型。
习题2 设二叉树T采用二叉链表存储方式,根结点用T指针指示,设计一个算法,求指针p所指结点的双亲结点。提示:先把例题2学会,本题的解题思路和方法可以按照例题2的思路
习题3假设二叉树采用二叉链存储结构,设计一个算法把树b的左右子树进行交换。要求算法的空间复杂度为O(1).
➤趣闻:据说这是一道谷歌的算法面试题目
往期推荐
?链表递归题型大解密,这类题还学不会算我输!
?带你搞定考研二叉树算法(上)