DS-第五章-二叉树的遍历

数据结构二叉树遍历总结⭐⭐⭐

DS-第五章-二叉树的遍历⭐⭐⭐

  • 数据结构二叉树遍历总结⭐⭐⭐
  • 二叉树的概念
  • 遍历
  • 习题分析与代码
  • 二叉树的遍历与线索二叉树
    • 先序遍历
      • 递归先序遍历
      • 非递归算法
    • 中序遍历
      • 递归中序遍历
      • 非递归算法
    • 后序遍历⭐
      • 递归算法
      • 非递归后序遍历算法⭐⭐(课后大题频出)
      • 非递归后序遍历与递归后序遍历的对比
  • 5.3.3习题分析与总结
    • 选择题
    • 综合大题

二叉树的概念

1、特点是:每个节点至多有两颗子树,分别为左子树和右子树
2、满二叉树:i<= ⌊n/2⌋,向下取整,i前面都是非叶子节点、i后面都是叶子节点
①如果n为奇数,则每个非叶子节点都有左右孩子,因为根节点只有一个,n-1个为偶数,则其余的层都是2的倍数
②如果n为偶数,则除了i= ⌊n/2⌋的非叶子节点只有左孩子外,其他非叶子都有左右孩子
③满二叉树是特殊的完全二叉树
④高度为h的满二叉树,利用等比数列求和公式可得,有2h-1个节点
扩展:
①高度为h的m叉数最多的节点数:N=(mh-1)/(m-1)
②高度为h的m叉数至少有h个节点(每个节点只有一个孩子)
③高度为h,度为m的树至少有h+m-1个节点。先构建高度为h的树,至少有h个节点,每一层一个节点,然后在任何一个节点再加m-1个节点,即可构建
非空二叉树重要公式:N0=N2+1
如何用满二叉树的性质+先序序列pre得出,后序序列post呢?
3、完全二叉树

遍历

存储方式:顺序?/链式? 定义其数据结构类型?
顺序存储:

typedef struct SeqTree{
     
	int T[MaxSize];//容量及其大
	int length;//长度
}SeqTree

链式存储:

typedef struct BTNode{
     
	int data;//数据域
	struct BTNode *lchild;//左孩子
	int weight;//权重,WPL 王道P142 T19 2014年统考真题
	struct BTNode *rchild;//右孩子
}

习题分析与代码

1、 P120 T7
在这里插入图片描述

节点个数等于度数之和+1
原理是每一个非叶子节点的分支的指向都是一个节点,而每一个节点的分支树即为该节点的度数(出度),那么把所有节点的度数加起来,就是所有的分支数,但是不要忘了根节点是没有计上的,计上的只是根节点的出度(分支数),因此节点数=节点个数等于度数之和+1
20x4+10x3+1x2+10x1+1=123
n4+n3+n2+n1+n0=123 so n0=82
正确答案:B


2、P126 T4
DS-第五章-二叉树的遍历_第1张图片

DS-第五章-二叉树的遍历_第2张图片
因为只有度为0和度为2的节点,那么为了尽可能的少,只能是除了根节点外,每层2个节点,那么高度为h,所有至少有2*h-1个节点


3、P126 T6

DS-第五章-二叉树的遍历_第3张图片

最特殊的解法:
完全二叉树也是二叉树,满足性质,2n个节点,在完全二叉树中只有1个节点度数为1,即只能是奇数,所以C是错的
A:n0=n      n2=n-1      n0=1    n0+n2+n1=n   A√
B:n0=2m   n2=2m-1   n1=1   n0+n1+n2=4m,当4m=n时满足条件  B√
D:n2=2m   n0=2m+1   n1=1  n0+n1+n2=4m+2=n,当m=(n-2)/4满足条件  D√
DS-第五章-二叉树的遍历_第4张图片
DS-第五章-二叉树的遍历_第5张图片


4、P126 T16
在这里插入图片描述

该题中124为偶数的完全二叉树,n1=1
n0=n2+1   n2=123
sum=123+124+1=248


5、P126 T11
DS-第五章-二叉树的遍历_第6张图片

第6层有8个叶子节点,那么第6层就有25-8=24非叶子节点
那么第7层就有24*2=48个叶子节点
因为是完全二叉树,所以前6层共26-1=63
63+48=111
那么如果改一下题目,把最多改成最少呢?
DS-第五章-二叉树的遍历_第7张图片
如此就能得到8个叶子节点,且节点数量最少了。
第6层8个叶子节点
前5层共25-1=31
所以共31+8=39
总结规律:
①完全二叉树如果题目中已经说明了一层有多少个叶子节点,那么另一层的叶子节点也会确定
②要得到最少的节点数,且题目中已经说明了一层有多少个叶子节点,那么那层的叶子节点就放在前面
③要得到最多的节点数,且题目中已经说明了一层有多少个叶子节点,那么那层的叶子节点就放在后面,使其下一层还有叶子节点,下一层的叶子节点树=(本层的可拥有的最多节点数-本层叶子节点树)*2

要注意与没有说本层节点的叶子节点的只给出一共有多少个叶子节点,问一共有多个个节点的题型区分,如:T16与T11,因此一起总结.
DS-第五章-二叉树的遍历_第8张图片
由图可得,当上一层有一个度为1的节点时,完全二叉树的叶子节点树没有变化,而这就导致了相比于第一种情况而已多了一个节点
①当出现第一种情况时,除了最后一个节点外,其他节点都是度为2的节点,那么就有奇数个节点,因为根节点是要计上的
②而当出现第二种情况的时候:除了最后一层最右边的一个节点是多出来的,刚好与根节点相加=2,那么就和其他非叶子节点(度都为2)相加得到总节点数偶数

由此可得
①当节点总数为偶数时N1=1
②当节点总数为奇数时N1=0


6、P126 T20

在这里插入图片描述
首先先理解题目中树对应的二叉树无右孩子结点:意思就说,对应树中无右兄弟
法一:极端情况,叶子节点全在最后一层,那么最后一层的最后一个叶子节点无右兄弟,上面的1895个节点都只有孩子,没有兄弟,共1895+1=1896个
DS-第五章-二叉树的遍历_第9张图片
法二:通过二叉树的性质来做,116个叶子节点,N2=N0-1=116-1=115个度为2的节点,只有左孩子,或左右孩子都没有的节点,2011-115=1896个


在这里插入图片描述

非空完全二叉树,叶子节点都在同一层且非叶子节点都有两个子节点,就是满二叉树的性质
K个叶子结点那么非叶子结点就是K-1个,共2K-1


7、P127 T23

在这里插入图片描述

其实这里是有坑的,10个节点的二叉树在满足高度为5的的情况下,可能只需要16个存储单元,但是第5层的结点只能是一个放在首部,但不满足二叉树高度为5的任意性,其实那10个结点就是用来坑人的,关键信息还是高度为5,二叉树转化为顺序存储至少需要25-1=31个结点
那么为什么说10个结点是坑人的呢?,因为如果这10个节点的二叉树中第五层有一个节点再最右边的情况,即31号节点,那么如果采用最少使用的情况16个的话,将不能满足任意的高度为5的二叉树转化为顺序存储的二叉树。

二叉树的遍历与线索二叉树

先序遍历

递归先序遍历

先序遍历顺序:  根左右

void PreOrder(BiTree T){
     
	if(T){
     //非空条件
		visit(T);//访问
		PreOrder(T->lchild);//左孩子
		PreOrder(T->lchild);//右孩子
	}
}

非递归算法

void PreOrder(BiTree T){
     
	Stack S;//栈
	InitStack(S);//栈的初始化
	BiTree p;//辅助指针
	while(IsEmpty(S)||p!=NULL){
     
			if(p){
     //非空
				visit(p);//访问
				Push(S,p);//压栈
				p=p->lchild;//继续访问其左孩子,直到出现空指针
		}
			else{
     //为空,则把其双亲结点出栈
			Pop(S,p);//栈顶元素出栈并放入p指针中
			p=p->rchild;//访问右孩子
		}
	}
}

中序遍历

递归中序遍历

中序遍历顺序:  左根右

void InOrder(BiTree T){
     
	if(T){
     //非空条件
		PreOrder(T->lchild);//左孩子
		visit(T);//访问
		PreOrder(T->lchild);//右孩子
	}
}

非递归算法

void InOrder(BiTree T){
     
	Stack S;//栈
	InitStack(S);//栈的初始化
	BiTree p;//辅助指针
	while(IsEmpty(S)||p!=NULL){
     
			if(p){
     //非空
				Push(S,p);//压栈
				p=p->lchild;//继续访问其左孩子,直到出现空指针
		}
			else{
     //为空,则把其双亲结点出栈
			Pop(S,p);//栈顶元素出栈并放入p指针中
			visit(p);//访问
			p=p->rchild;//访问右孩子
		}
	}
}

后序遍历⭐

递归算法

后序遍历顺序:  左右根

void PostOrder(BiTree T){
     
	if(T){
     //非空条件
		PreOrder(T->lchild);//左孩子
		PreOrder(T->lchild);//右孩子
		visit(p);//访问
	}
}

非递归后序遍历算法⭐⭐(课后大题频出)

typedef struct Stack{
     
	BiTree t;//二叉树指针
	int tag;//标记,tag=0访问了左子树,tag=1访问右子树
}
int top;//栈顶指针
void PostOrder(BiTree T){
     
	Stack S[MaxSize];//栈,MaxSize足够大的容量
	top=0;
	while(T||top>0){
     //结点存在或者栈非空
		while(T){
     //一直循环,直至访问到左孩子为空
			S[++top].t=T;//入栈
			S[top].tag=0;//访问左结点
			T=T->lchild;//访问左结点
		}
		while(S[top].tag==1&&top>0){
     
			visit(S[top].t);//访问
			top--;//该结点已经访问过了就出栈!
		}
		if(top>0){
     //上一个while循环结束后top依然>0
		//说明还有结点的右孩子未被访问
		//只有当左右孩子都访问完了,才能访问其双亲结点
			S[top].tag=1;
			T=S[top].t->rchild;//遍历其右孩子
		}
	}
}

非递归后序遍历与递归后序遍历的对比

#include<stdio.h>
#include<stdlib.h> 
#define MaxSize 10000 
typedef struct BTnode{
     
	int  data;
	struct BTnode *lchild;//左孩子
	struct BTnode *rchild;//右孩子 
}BTnode,*BiTree;
typedef struct Stack{
     
	BiTree t;//二叉树指针
	int tag;//标记,tag=0访问了左子树,tag=1访问右子树
}Stack;
int top;//栈顶指针
void visit(BiTree t){
     
	printf("%d ",t->data);
}
void PostOrder1(BiTree T){
     
	if(T){
     
		PostOrder1(T->lchild);
		PostOrder1(T->rchild);
		visit(T);
	}
}
void PostOrder(BiTree T){
     
	Stack S[MaxSize];//栈,MaxSize足够大的容量
	top=0;
	while(T||top>0){
     //结点存在或者栈非空
	
		while(T){
     //一直循环,直至访问到左孩子为空
			S[++top].t=T;//入栈
			S[top].tag=0;//访问左结点
			T=T->lchild;//访问左结点
		}

		while(S[top].tag==1&&top>0){
     
			visit(S[top].t);//访问
			top--;//该结点已经访问过了就出栈!
		}
		if(top>0){
     //上一个while循环结束后top依然>0
		//说明还有结点的右孩子未被访问
		//只有当左右孩子都访问完了,才能访问其双亲结点
			S[top].tag=1;
			T=S[top].t->rchild;//遍历其右孩子
		}
	}

}
int main(){
      
	BiTree T7=(BiTree)malloc(sizeof(BTnode));
	T7->data=7;
	BiTree T3=(BiTree)malloc(sizeof(BTnode));
	T3->data=3;
	T7->lchild=T3;
	BiTree T6=(BiTree)malloc(sizeof(BTnode));
	T6->data=6;
	T7->rchild=T6;
	
	BiTree T1=(BiTree)malloc(sizeof(BTnode));
	T1->data=1;
	T3->lchild=T1;
	T1->lchild=NULL;
	T1->rchild=NULL;
	
	BiTree T2=(BiTree)malloc(sizeof(BTnode));
	T2->data=2;
	T3->rchild=T2;
	T2->lchild=NULL;
	T2->rchild=NULL;
	
	BiTree T4=(BiTree)malloc(sizeof(BTnode));
	T4->data=4;
	T6->lchild=T4;
	T4->lchild=NULL;
	T4->rchild=NULL;
	
	BiTree T5=(BiTree)malloc(sizeof(BTnode));
	T5->data=5;
	T6->rchild=T5;
	T5->lchild=NULL;
	T5->rchild=NULL;
	printf("递归后序遍历算法:");
	PostOrder1(T7);
	printf("\n"); 
	printf("非递归后序遍历算法:");
	PostOrder(T7);
} 

5.3.3习题分析与总结

选择题

1、P138 T1
DS-第五章-二叉树的遍历_第10张图片

DS-第五章-二叉树的遍历_第11张图片
该图中:
中序遍历为:2 1
先序遍历为:1 2
A、B首先排除
DS-第五章-二叉树的遍历_第12张图片

图1

DS-第五章-二叉树的遍历_第13张图片

图2
由这两个图可看出,图2 的

先序序列为:1 2 3 4
中序为:2 1 4 3 所以D不满足D错误

若一个叶子结点为中序遍历的最后一个结点,说明这个结点在图的最右方,且没有左孩子和右孩子,那么先序遍历时到达图的最右方肯定当到这个没左右孩子的结点为最后一个结点,因此两者都在同一个结点停下


2、P139 T9

在这里插入图片描述

先序:根左右
后序:左右根
要序列相反有两种情况:
①每个结点只有右孩子
②每个结点只有左孩子
则每个结点度为1则会出现只有一个叶结点
所以正确答案为C


3、P140 T21

在这里插入图片描述

先序:根左右
中序:左根右
要想序列相同:
①每个结点只有右孩子->只有右子树
正确答案为B
但可能有人会选C,但是C只能保证度为1 的话,可能只有左孩子,可能只有右孩子,不能保证只有左孩子,这个是必要不充分条件


4、P140 T26

在这里插入图片描述

在先序序列中若左右子树非空,那么最后面的最后一个结点必定右空域,因为最后一个叶子结点只有一个前驱,而后继已经没有了只能NULL
DS-第五章-二叉树的遍历_第14张图片

而左子树为空的情况
DS-第五章-二叉树的遍历_第15张图片


5、P140 T28

线索二叉树考选择题,只要遍历一遍就直到如何的连接这些虚线了

DS-第五章-二叉树的遍历_第16张图片

DS-第五章-二叉树的遍历_第17张图片
举一反三
DS-第五章-二叉树的遍历_第18张图片
DS-第五章-二叉树的遍历_第19张图片
总结:
画线索二叉树首先先写出对应的遍历顺序(用于理清思路和验证线条)
根据遍历的过程,当左子树或者右子树为空时就要线索化。从输出的一个结点开始。


6、P141 T35

在这里插入图片描述

先序:根左右
后序:左右根
①每个结点只有右孩子
②每个结点只有左孩子

归纳总结为:
①要么度为1
②高度等于其结点数
答案选B

综合大题

1、P141 T3

在这里插入图片描述

法一:改变中序遍历+读栈不出栈法

void PostOrder(BiTree T){
     
	Stack S;//定义栈用于保存二叉树的结点
	InitStack(S);//栈初始化
	BiTree p=NULL;//用于判断是否是最近出过栈的结点
	while(T||!IsEmpty(S)){
     //结点非空/或者栈非空
		if(T){
     //结点非空
			Push(T);//结点入栈
			T=T->lchild;//一直向左走,直到为空为止
		}
		else{
     //最左端已经到底
			GetTop(S,T);//读栈顶元素
			if(T->rchild&&T->rchild!=p){
     
			//结点的右子树存在且未访问过
				T=T->rchild;//向右孩子走
			}
			else{
     //左子树已经走完,右子树又不存在或者已经访问过
				//即访问该节点
				Pop(S,T);//栈顶元素出栈
				visit(T);//访问该结点
				p=T;//更新当前最近访问过的结点,防止其双亲结点再判断到右孩子非空导致死循环
				T=NULL;//清除T指针内容,因为该指针的的左右子树包括其结点已经访问过出栈了
			}
		}
	}
}

法二:自定义栈的数据结构,含有tag标记,tag=0访问了左孩子,tag=1了有孩子,只有当左右孩子都访问了即tag=1了且栈中还有元素则访问该元素。

typdef struct Stack{
     
	BiTree t;//二叉树指针
	int tag;//tag=0 访问左孩子;tag=1 访问右孩子
}Stack;
int top;//栈顶指针
void PostOrder(BiTree T){
     
	Stack S[MaxSize];//MaxSize为足够大容量
	top=0;//栈顶指针

	while(T||top>0){
     //结点非空或栈非空
		while(T){
     //非空则一直向左
			S[++top]=T;//入栈
			S[top].tag=0;//访问左结点孩子
			T=T->lchild;//向左孩子走
		}
		while(S[top].tag==1&&top>0){
     //其孩子结点都已经访问,且栈中还有孩子
			visit(S[top].t);//访问该结点
			top--;//出栈
		}
		if(top>0){
     //经过上面的while循环后top还没有到达0,栈中还有元素
		//说明还有结点的右孩子还没被访问
			S[top].tag=1;//该节点记录走过
			T=S[top].t->rchild;//走向其右孩子继续访问
		}
	}
}

法三:先序 (左右顺序相反+栈输出)

void PostOrder2(BiTree T){
     //法三:先序 (左右顺序相反+栈输出) 
	if(!T)
		return;
	BiTree S[MaxSize];//足够大容量用于遍历
	BiTree S1[MaxSize];//用于输出的栈
	int top=-1,top1=-1;//栈顶指针
	while(T||top>-1){
     
		while(T){
     
			S[++top]=T;//入栈
			S1[++top1]=T;//入栈
			T=T->rchild;//走向右孩子,顺序反向,先走右边
		}
		if(top>-1){
     //栈中还有结点
			T=S[top--];//取栈顶元素
			T=T->lchild;//走向左孩子
		}
	}
	//printf("%d %d\n",top1,top);
	while(top1!=-1){
     //输出
		visit(S1[top1--]);//访问
	}
}

2、P141 T5

在这里插入图片描述

此题为非递归算法,先引入递归算法求二叉树高度

int high(BiTree T,int h){
     //二叉树指针和记录当前的高度
	int lh,rh,max;//左右子树高度
	if(T){
     
		lh=high(T->lchild,h+1);//递归左子树
		rh=high(T->rchild,h+1);//递归右子树
		return ((lh>rh)?lh:rh)+1;
	}
}

非递归算法算二叉树高度
①层序遍历

int high(BiTree T){
     
	if(!T)//空树返回
		return -1;
	int front=-1,rear=-1;//队头队尾指针
	int last=0;//本层最后一个结点的指针
	int level=0;//高度
	BiTree Q[MaxSize];//队列
	Q[++rear]=T;//根节点入队
	BiTree p;//辅助指针用于保存出队结点
	while(front<rear){
     //队头指针小于队尾指针,证明还有元素
		p=Q[++front];//队头结点出队
		if(p->lchild)//左孩子存在
			Q[++rear]=p->lchild;//左孩子进队
		if(p->rchild)//右孩子存在
			Q[++rear]=p->rchild;//右孩子进队
		if(last==front){
     //队头指针已经走到了本层的最后一个结点的为止
			last=rear;//更新下一层的结点尾指针
			level++;//高度自增
		}	
	}
	return level;//返回高度
}

在这里插入图片描述

若我将这一题改成是求二叉树的宽度要怎么搞?
方法很简单,在上一题的非递归层序遍历上加上记录每一层的最大结点总数即可

int width(BiTree T){
     //求宽度
	if(!T)//空树返回
		return -1;
	int front=-1,rear=-1;//队头队尾指针
	int last=0;//本层最后一个结点的指针
	int count=0;//记录每一层的结点数
	int max=0;//保存最大宽度
	BiTree Q[MaxSize];//队列
	Q[++rear]=T;//根节点入队
	BiTree p;//辅助指针用于保存出队结点
	while(front<rear){
     //队头指针小于队尾指针,证明还有元素
		p=Q[++front];//队头结点出队
		count++;//本层节点数自增
		if(p->lchild)//左孩子存在
			Q[++rear]=p->lchild;//左孩子进队
		if(p->rchild)//右孩子存在
			Q[++rear]=p->rchild;//右孩子进队
		if(last==front){
     //队头指针已经走到了本层的最后一个结点的为止
			last=rear;//更新下一层的结点尾指针
			max=(max>count)?max:count;
			count=0;//每一层结束时,计数清0
		}	
	}
	return max;//返回最大宽度
}

3、P141 T6

在这里插入图片描述

DS-第五章-二叉树的遍历_第20张图片

//采用递归算法建立二叉树
BiTree create(ElemType *xian,ElemType *zhong,int len){
     
	int i;//用于遍历中序序列找到左右子树的指针
	if(len==0)
		return NULL;//空树
	BiTree temp=(BiTree)malloc(sizeof(BTnode));//创建指针
	temp->data=*xian;//取值赋值
	for(int i=0;i<len;i++){
     
		if(*(zhong+i)==*xian)//如果遇到两者的值相等则break,记录此位置用于递归
			break}
	temp->lchild=create(xian+1,zhong,i);//递归左子树
	temp->rchild=create(xian+i+1,zhong+i+1,len-i-1);//递归右子树
	return temp;
}

DS-第五章-二叉树的遍历_第21张图片


4、P141 T7

在这里插入图片描述

通过队列来判断二叉树是否为完全二叉树

bool IsComTree(BiTree T){
     //判断是否为完全二叉树
	if(!T)
		return false;//空树
	BiTree Q[MaxSize];//足够大的容量
	int front=-1,rear=-1;//队头队尾指针
	Q[++rear]=T;//根结点入队
	BiTree p;//辅助指针用于保存出队的结点
	while(front<rear){
     //队头指针小于队尾指针
		p=Q[++front];//队头结点出队
		if(p){
     //非NULL结点
			//把左右孩子都入队
			Q[++front]=p->lchild;
			Q[++front]=p->rchild;
		}
		else{
     //如果遇到叶子结点,那么后面就不能再有结点了,否则不是完全二叉树
			while(front<rear){
     //循环退队
				p=Q[rear--];//出队
				if(p)//居然还有非空结点
					return false;//不是完全二叉树
			}
		}
	}
	return true;//是完全二叉树
}

5、P141 T10

在这里插入图片描述

采用非递归先序遍历算法

int PreOrderFind(BiTree T,int k){
     
	int top=-1;//栈顶指针
	BiTree Stack[MaxSize];//MaxSize容量足够大
	int i=0;//用于与k比较
	while(top>-1||T){
     //当栈中有结点或者当前访问的结点非空
		if(T){
     
			i++;
			if(i==k)//到达第k个元素
				return T->data;//返回其结点的值
			Stack[++top]=T;//入栈
			T=T->lchild;//一直向左走
		}
		else{
     //当遇到空结点时,返回其双亲结点,向右走
			T=Stack[top--];//栈顶结点出栈
			T=T->rchild;//向右走
		}
	}
	return -1;//没有找到第k个值 
}

采用递归写法

int flag=0;//标记是否找到第k个节点
int i=0;//编号
void PreOrderFind1(BiTree T,int k){
     //先序序列递归算法找第k个节点的值
	if(T){
     //非空条件
		i++;//自增第i个节点
		if(i==k){
     
			visit(T);
			flag=1;
			return; 
		}
		if(flag!=1){
     
			PreOrderFind1(T->lchild,k);//左孩子
			PreOrderFind1(T->rchild,k);//右孩子
		}
		else
			return;
	}
}

在这里插入图片描述

采用递归算法实现

int flag=0;
void PostOrder(BiTree T,int x){
     
	if(!T)//空树
		return;
	if(T->data==x){
     //找到x值的节点
		flag=1;//标记
		return;//返回
	}
	if(flag==0){
     
		PostOrder(T->lchild,x);//递归左子树
	}
	if(flag==0){
     
		PostOrder(T->lchild,x);//递归左子树
		}
	if(flag!=0){
     //找到了x了
		visit(T);
	}
}

采用非递归后序遍历算法找--------自定义栈算法—标记

typedef Stack{
     
	BiTree t;//指针域
	int tag;//tag=0 访问了左结点,tag=1,访问了右节点
}Stack;
void PostOrderFindX1(BiTree T,int x){
     
	//找x的所有祖先---自定义栈算法---标记
	if(!T)//空树
		return;
	Stack S[MaxSize];//栈,足够大容量
	int top=-1;//栈顶
	while(top>-1||T){
     //树非空或者栈中有节点
		while(T){
     //非空一直向左走
			S[++top].t=T;//入栈
			S[top].tag=0;//右节点已经访问
			T=T->lchild;//向左走
		}
		if(S[top].t->data==x){
     
				for(int i=top-1;i>=0;i--)
					visit(S[i].t);
				break;
		}	
		//退出while,意味着遇到了空节点
		while(S[top].tag==1&&top>-1){
     
			top--;//已经访问过就退栈
		}
		if(top>-1){
     //经过上层的while循环,top还>-1
		//说明还有节点的右孩子未被访问
			S[top].tag=1;//标记该节点
			T=S[top].t->rchild;//访问右孩子
		}
	}
}

采用非递归后序遍历算法找----先序(顺序相反)+栈输出(有问题)

void PostOrderFindAn(BiTree T,int x){
     
	int top=-1,top1=-1;//双栈写法,一栈用于遍历,一栈用于输出
	BiTree S[MaxSize];//遍历栈
	BiTree S1[MaxSize];//输出栈
	while(top>-1||T){
     //栈中还有节点,或者树非空
		while(T){
     
			S[++top]=T;//入栈
			S1[++top1]=T;//入栈
			T=T->rchild;//先向右走
	//因为后序遍历右节点比左结点慢访问,子树根节点也比右节点慢访问
		//次序为 左-》右-》根
		//而根据栈的特性,先入栈的就后访问
		//因此,子树根节点入栈后,先走右孩子让右孩子入栈
		}
		if(S1[top1]->data==x){
     //找到x的位置
			for(int i=top-1;i>=0;i--)
				visit(S1[i]);
			exit(1);
		}
		if(top>-1){
     //栈中还有节点
			T=S[top--];//出栈取栈顶结点
			T->lchild;//访问左孩子
		}
	}
}

在这里插入图片描述

typedef Stack{
     
	BiTree t;//指针域
	int tag;//tag=0 访问了左结点,tag=1,访问了右节点
}Stack;
//ROOT根节点  p q任意两个节点
//ROOT根节点  p q任意两个节点
BiTree ANCESTOR(BiTree ROOT,BiTree p,BiTree q){
     //后序非递归算公共祖先
	Stack S[MaxSize];//栈,足够大容量
	Stack S1[MaxSize];//栈,足够大容量,用于保存对比指针
	int top=-1;//栈顶指针
	int top1=-1;//对比栈的栈顶
	BiTree T=ROOT;//辅助指针
	while(T||top>-1){
     
		while(T){
     //如果节点存在
			S[++top].t=T;//入栈
			S[top].tag=0;//记录访问左子树
			T=T->lchild;//向左走
		}
		while(top>-1&&S[top].tag==1){
     //左右子树都已经访问过了
			if(p==S[top].t){
     //找到p的位置
				for(int i=0;i<=top;i++)
					S1[i].t=S[i].t;//转移对应的数据到另一栈中,用于比较
				top1=top;//赋值栈顶
			}
			if(q==S[top].t){
     //找到q的位置,则开始比较
				for(int i = top;i>=0;i--){
     //从top向后找,因为要找到最近的公共节点
					for(int j=top1;j>=0;j--){
     
						if(S[i].t==S1[j].t)//有公共节点,则返回
							return S[i].t;//返回指针
					}
				}
			}
			top--;//出栈
		}
		if(top>-1){
     //经过上一面的while循环,当top还>-1时,说明还有节点的右孩子未被访问
			S[top].tag=1;//标记访问右孩子
			T=S[top].t->rchild;//取出栈顶元素
		}
	}
	return NULL;
}

有一个疑问:这个if(pS[top].t)和这个if(qS[top].t)放在while里面和外面有什么区别呢?

BiTree ANCESTOR1(BiTree ROOT,BiTree p,BiTree q){
     //后序非递归算公共祖先
	Stack S[MaxSize];//栈,足够大容量
	Stack S1[MaxSize];//栈,足够大容量,用于保存对比指针
	int top=-1;//栈顶指针
	int top1=-1;//对比栈的栈顶
	BiTree T=ROOT;//辅助指针
	while(T||top>-1){
     
		if(p==S[top].t){
     //找到p的位置
				for(int i=0;i<=top;i++)
					S1[i].t=S[i].t;//转移对应的数据到另一栈中,用于比较
				top1=top;//赋值栈顶
			}
			if(q==S[top].t){
     //找到q的位置,则开始比较
				for(int i = top;i>=0;i--){
     
					for(int j=top1;j>=0;j--){
     
						if(S[i].t==S1[j].t)//有公共节点,则返回
							return S[i].t;//返回指针
					}
				}
			}
		while(T){
     //如果节点存在
			S[++top].t=T;//入栈
			S[top].tag=0;//记录访问左子树
			T=T->lchild;//向左走
		}
		
		while(top>-1&&S[top].tag==1){
     //左右子树都已经访问过了
			
			top--;//出栈
		}
		if(top>-1){
     //经过上一面的while循环,当top还>-1时,说明还有节点的右孩子未被访问
			S[top].tag=1;//标记访问右孩子
			T=S[top].t->rchild;//取出栈顶元素
		}
	}
	return NULL;
} 

两者的运行结果如下
经过多次不同数据的测试都是相同的
那么这两者有什么区别呢?DS-第五章-二叉树的遍历_第22张图片


P142 T17

在这里插入图片描述

法一:(这是我采用的方法)采用层序遍历的方法比较两颗树,只有当两颗树每次遍历的结果都是要么都是空节点,要么都有左孩子或者都有右孩子,或者左右都有才会继续执行,否则返回false

bool LevelOrder(BiTree T1,BiTree T2){
     //层序遍历,识别两棵树是否相似
//当棵树都非空,或者两颗树都为空,时就继续执行,否则返回false,不相似
	if(!((T1&&T2)||(T1==NULL&&T2==NULL)))
		return false;
	if(T1==NULL&&T2==NULL)//两者都为空直接返回true 相似
		return true;
	BiTree Q1[MaxSize];//树1的队列
	int front1=-1,rear1=-1;//树1的队头队尾指针
	BiTree Q2[MaxSize];//树2的队列
	int front2=-1,rear2=-1;//树2的队头队尾指针
	BiTree p1,p2;//辅助指针,保存队列出队的结点
	Q1[++rear1]=T1;//树1的根结点入队
	Q2[++rear2]=T2;//树2的根结点入队
	while(front1<rear&&front2<rear2){
     
		p1=Q1[++front1];//树1队列队头出队
		p2=Q2[++front2];//树1队列队头出队
		if(p1->lchild&&p2->lchild){
     //左孩子都有
			//则左孩子都入队
			Q1[++rear1]=p1->lchild;//树1的左孩子入队
			Q2[++rear2]=p2->lchild;//树2的左孩子入队
		}
		//两者都为空,不做任何处理,默认为相似
		else if(T1->lchild||T2->lchild)//只要两者有一个有左孩子,就是不相似			
			return false;
			
		if(p1->rchild&&p2->rchild){
     //左孩子都有
			//则右孩子都入队
			Q1[++rear1]=p1->rchild;//树1的右孩子入队
			Q2[++rear2]=p2->rchild;//树2的右孩子入队
		}
		//两者都为空,不做任何处理,默认为相似
		else if(T1->rchild||T2->rchild)//只要两者有一个有右孩子,就是不相似			
			return false;
			
	}
	return true;//最后都处理完了还没返回flase 就是true了
}

运行结果如下:
DS-第五章-二叉树的遍历_第23张图片

法二:递归算法

int Similar(BiTree T1,BiTree T2){
     
	int LeftS,RightS;//左右子树是否相似
	if(T1==NULL&&T2==NULL)//两树皆空
		return 1;
	else if(T1==NULL||T2==NULL)//两树有一树为空
		return 0;
	else{
     //两树都存在
		LeftS=Similar(T1->lchild,T2->lchild);//递归左子树
		RightS=Similar(T1->rchild,T2->rchild);//递归左子树
		return LeftS&&RightS;//左右都为相似,才是相似
	}	
}

比较结果(两种不同的数据)
DS-第五章-二叉树的遍历_第24张图片
DS-第五章-二叉树的遍历_第25张图片


P142 T19

统考真题,一题多解

DS-第五章-二叉树的遍历_第26张图片

我所采用的是层序遍历
思想:通过层序遍历,用一个变量sum记录总的WPL,记录当前的深度/高度,每次都判断该结点是否是叶子结点,如果是叶子结点则把其权值和深度相乘加入sum中,如果不是则继续层序遍历。

int LevelOrderWPL(BiTree T){
     
	if(!T)
		return 0;//空树
	int sum=0;//记录WPL总和
	int front=-1,rear=-1;//队头队尾指针
	int last=0;//本层的最后一个结点的指针,而根节点在0号索引,因此last初始为0
	int level=0;//深度
	BiTree Q[MaxSize];//队列
	Q[++rear]=T;//根节点入队
	BiTree p;//辅助指针用于保存出队结点
	while(front<rear){
     //队头指针小于队尾指针,还有结点
		p=Q[++front];//队头结点出队
		if(p->lchild)//有左孩子
			Q[++rear]=p->lchild;//左孩子入队
		if(p->rchild)//有右孩子
			Q[++rear]=p->rchild;//右孩子入队
		if(p->lchild==NULL&&p->rchild==NULL)
			sum+=(level)*p->weight;//累加
		if(front==last){
     //到达本层的最后一个结点
			last=rear;//更新为下一层的尾指针
			level++;//深度自增
		}
	}
	return sum;//返回WPL
}

法二------先序遍历

int WPL=0;//全局遍历WPL变量
int PreOrderWPL(BiTree T,int h){
     //当前的深度
	if(T->lchild==NULL&&T->rchild==NULL)//叶子结点
		WPL+=T->weight*h;//累加
	if(T->lchild)
		PreOrderWPL(T->lchild,h+1);//递归左孩子
	if(T->rchild)
		PreOrderWPL(T->rchild,h+1);//递归右孩子
	
	return WPL;//返回变量
}

P143 T20

DS-第五章-二叉树的遍历_第27张图片

法1:我写的时候的方法是:采用中序遍历递归算法+全局遍历用于判断是否要加括号,如果第一次遇到操作符,不加括号,若不是叶子结点且不是第一次遇到操作符,访问左孩子前加"(",访问右孩子后加")",若是叶子结点则输出操作数

void InOrder(OpTree T){
     
	if(!T)
		return;//空则返回
	if(T->lchild==NULL&&T->rchild==NULL)//叶子结点
		printf("%c",T->data);
	else{
     //非叶子结点
		count++;
		if(count!=1)//不是第一次遇到操作符
			printf("(");
		if(T->lchild)
			InOrder(T->lchild);//递归左子树
		printf("%c",T->data);
		if(T->rchild)
			InOrder(T->rchild);//递归右子树
		if(count!=1)
			printf(")");
		count--;
	}
}

运行结果如下DS-第五章-二叉树的遍历_第28张图片

冲鸭!!!兄弟们,加油!

你可能感兴趣的:(408,数据结构,数据结构,算法)