二叉树的遍历:traverse
(1)深度优先遍历 :(递归与非递归)
(1.1)先序 (先根):preOrder(WithRecusion/WithoutRecusion)
(1.2)中序 (中根):inOrder(WithRecusion/WithoutRecusion)
(1.3)后序 (后根):postOrder(WithRecusion/WithoutRecusion)
(注意:非递归的深度优先遍历中:栈中的字段有top,以及节点指针数组BTNode* array[Maxsize] )
----
(2)广度优先遍历 :
(2.1)层次遍历 :leverOrder
(注意:层次遍历中队列中的字段有 front ,rear 以及 节点指针数组 BTNode* array[Maxsize] )
1.如果在队列中加入一个字段 int levelArray[Maxsize]可以记录下每个节点的层次。
2.如果在队列中的的数组的每个元素为typedef struct Element { BTNode * pnode ,int parentIndex};(parentIndex表示当前节点pnode的父节点所在的下标)
然后队列为{ front ,rear, Element array[Maxsize] },则可以根据由某个节点快速的查找到父节点,对于查找路径方便。
-----
(3)祖先节点与 到根节点的路径:
---------------------------------
(一)先序:先访根节点,再访问左子树,再访问右子树。
(1)递归:
void PreOrder(BTNode* b){
if(b!=NULL){
visit(b->data);
PreOrder(b->lchild);
PreOrder(b->rchild);
}
}
(2)非递归:
思想:先根,然后左子树,然后右子树。
所以:先将根节点进栈,在栈非空时循环:出栈p,访问p,将其右孩子节点进栈,再左孩子节点接栈。
void preOrder(BTNode *b){
BTNode* stack[Maxsize];
BTNode * p;
int top=-1;
if(b!=NULL){
top++;
stack[top]=b;//根节点进栈
while(top>-1){
//栈非空
p=stack[top];
top--;//出栈
visit(p->data);//访问
if(p->rchild!=NULL){
//右孩子非空,则进栈
top++;
stack[top]=p->rchild;
}
//必须先判断右子树,然后再判断左子树,右孩子先进栈。
if(p->lchild!=NULL){
//左孩子非空,则进栈
top++;
stack[top]=p->lchild;
}
}
}
}
注意:非递归的先序遍历中,必须是先是右孩子进栈,然后孩子进栈,顺序不可以颠倒,否则就是根右左。
-----------
(二)中序:先左子树,然后跟节点,然后右子树。
(1)递归:
void inOrder(BTNode * b){
if(b!=NULL){
inOrder(b->lchild);
visit(b->data);
inOrder(b->rchild);
}
}
(2)非递归:
思想:
1)先扫描根节点的所有左节点并将它们一一压栈,
2)然后出栈一个节点p,当前新出栈的节点p必然没有左孩子或者左孩子以及被访问过,然后访问节点p,
3)然后扫描节点p的右孩子,然后将p的右孩子的所有左孩子一一压栈,与1)重复,直至栈空。
void inOrder(BTNode * b){
BTNode* stack[Maxsize];
BTNode* p;
int top=-1;
if(b!=NULL){
p=b;
while(p!=NULL || top>-1){
while(p){
//根节点p的左孩子不断进栈
top++;
stack[top]=p;
p=p->lchild;
}
if(top>-1){
//栈非空时
p=stack[top];
top--;//出栈
visit(p->data);//访问
p=p->rchild;//p的右子树作为新的p,让p的右子树的左孩子逐个进栈
}
}
}
}
-----------
(三)后续:先左子树,然后右子树,然后根节点。
(1)递归:
void postOrder(BTNode *b){
if(b){
postOrder(b->lchild);
postOrder(b->rchild);
visit(b->data);
}
}
(2)非递归:
思想:
1)先扫描一个根节点的所有的左孩子并一一进栈
2)出栈一个节点p,然后扫描节点p的右孩子并进栈,在扫描p的右孩子的所有左孩子并一一进栈。即将p的右孩子作为1)中的跟节点,重复1)
3)当一个节点的左右孩子均访问过,才访问该节点。直至栈空。
难点:如何判断节点p的右孩子已经访问过了,用q保存右子树中刚刚访问过的节点(初值为null),若p->rchild=q,说明p节点的左右子树都已经访问过了,然后就可以访问p了。如果不成立,说明p的右子树没有访问完。
void postOrder(BTNode *b){
BTNode* stack[Maxsize];
BTNode* p=b,*pre;
int flag,top=-1;
if(b){
do{
while(p){//节点p的所有左孩子进栈
top++;
stack[top]=p;
p=p->lchild;
}
pre=NULL;//pre指向栈顶节点前一个已经访问过的节点。
flag=1;
while(top!=-1 && flag==1){
p=stack[top];//取栈顶,但不出栈
if(p->rchild==pre){ //栈顶元素的右孩子是前一个访问的节点,那么就可以访问栈顶的节点了。
top--;//出栈
visit(p->data);//访问
pre=p;//更新已经访问过的 节点。
}
else{ //否则,栈顶节点不可以访问 ,还需要将栈顶节点的右子树的左孩子再给压入栈中;
//否则将p节点的右孩子的所有左孩子进栈。
p=p->rchild;
flag=0;//跳出当前循环
}
}
}while(top!=-1);
}
}
---------------
(四)层次遍历非递归:levelOrder
(1)思想:
1)先根节点进入队列;
2)队列非空时,出队列一个节点,进行访问;
出来节点的左孩子非空,则进入队列;
出来节点的右孩子非空,则进入队列;
(2)代码实现:
#include "queue.h"
void levelOrder(BTNode * T){
BTNode * p;
Queue Q;
initQueue(Q);
enQueue(Q,T);
while(!isEmpty(Q)){
deQueue(Q,p);
visit(p->data);
if(p->lchild) enQueue(Q,p->lchild);
if(p->rchild) enQueue(Q,p->rchild);
}
}
或者:
void levelOrder(BTNode* b){
BTNode* queue[Maxsize];
int front=rear=-1;
BTNode* pnode;
rear++;
queue[rear]=b;//进队
while(rear!=front){//队列非空
front=(front+1)%Maxsize;
pnode=queue[front];//出队
visit(pnode->data);//访问
if(pnode->lchild) {
rear++;
queue[rear]=pnode->lchild;//左孩子进队
}
if(pnode->rchild) {
rear++;
queue[rear]=pnode->rchild;//右孩子进队
}
}
}
---------
(五)层次遍历(递归)
0
(5.1)方法一:
这个算法先求出根结点的高度,depth=高度+1。在函数PrintNodeAtLevel中,当且仅当level==1时才进行打印。举个例子:
1
2 3
4 5 6 7
这棵树的根的高度是2,depth=3。然后,在LevelTraverse函数中,level从1开始,这会打印出1;之后level=2,进入PrintNodeAtLevel(T->leftChild, level - 1)函数和PrintNodeAtLevel(T->rightChild, level - 1),level又等于1,就打印出2,3。以此类推,整棵树就按层打印出来了。
// 用递归方法求树的高度
int Depth(BiTree T)
{
if (NULL == T)
return 0;
int leftDepth = Depth(T->leftChild);
int rightDepth = Depth(T->rightChild);
return 1+ max(leftDepth, rightDepth);
}
void PrintNodeAtLevel(BiTree T,int level)
{
// 空树或层级不合理
if (NULL == T || level < 1 )
return;
if (1 == level)
{
cout << T->data << " ";
return;
}
// 左子树的 level - 1 级
PrintNodeAtLevel(T->leftChild, level - 1);
// 右子树的 level - 1 级
PrintNodeAtLevel(T->rightChild, level - 1);
}
void LevelTraverse(BiTree T)
{
if (NULL == T)
return;
int depth = Depth(T);
int i;
for (i = 1; i <= depth; i++)
{
PrintNodeAtLevel(T, i);
cout << endl;
}
}
(5.2)方法二:
分析:
(1)递归分析
要想使用递归,必须有两个条件:
1)函数参数类型相同;
2)递归必须有出口;
在二叉树中找到上面的两个条件,与前中后三种遍历一样:
函数参数为节点,递归出口是当左右孩子为空的时候.传入根节点,然后依次递归访问左右孩子,直至为空.
重点那么问题来了,递归遍历的数据保存到那?如何做到保存后是层序的顺序?
1
2 3
4 5 6 7
第一层
根节点标记为1元素是层序输出的第一个值;
第二层
标记为2的元素是层序输出的第二个值,同时他是标记为1节点的左孩子
标记为3的元素是层序输出的第三个元素,同时他是标记为1的节点的右孩子.
第三层
标记为4的元素是输出的第四个元素,他是标记为2节点的左孩子;
标记为5的元素是输出的第五个元素,他是标记为2节点的右孩子...
(2)很容易找到一个规律:
1)每一个节点左孩子在层序中输出的位置是该节点在层序输出位置的二倍
2)每一个节点右孩子在层序中输出的位置是该节点在层序输出位置的二倍加一
3)根节点在层序输出的位置为1;
int tree2str(bitree *b,int *a,int i )
{
if (NULL == b) {
return;
}
if(b->left!=NULL)
{
tree2str(b->left,a,2*i);
}
if(b->right!=NULL)
{
tree2str(b->right,a,2*i+1);
}
*(a+i)=b->data;
}
int main()
{
int arr[N] = {-1};
tree2str(b, arr, 1);
for (int i = 1; i < N && -1 != arr[i]; i++) {
print("%d", arr[i]);
}
}
----------
(六)补充:路径与祖先节点。
1.某个节点的祖先节点 即 从该节点到根节点的路径;
2. 非递归的后序遍历中,栈中栈顶节点的下面的节点为栈顶节点的祖先节点,,即 将栈 中各个元素出栈可以得到栈顶节点到根结点的路径。