本文包含以下内容
-
1 ,二叉树三种建立
- 前序递归
- 表达式非递归
- 孩子兄弟表达式非递归
-
2,遍历
- 三种遍历的递归与非递归实现(前中后序)
- 层次遍历(普通二叉树,孩子兄弟链表)
-
3,查询(递归设计分析)
- 指定节点
- 双亲
- 最近祖先
-
4,树高度
- 整棵树高度
- 指定点高度
建立二叉树的方法有递归–前缀表达式;非递归–括号+逗号
在此基础上可完成孩子兄弟链表的非递归构建
数据结构
typedef struct BTNode{
char data;
struct BTNode *lchild,*rchild;
}BTNode,*BTree;
建二叉树
前缀表达式(递归)
//递归建树:仅前序成功?测试数据:ABD##EG###C#F##
//后序:##D##G#EB###FCA ??中序??
void CreateBTreePreOrder(BTree &T)
{
char ch;
cin>>ch;
if(ch == '#')T=NULL;
else{
T = (BTNode*)malloc(sizeof(BTNode));
T->data = ch;
CreateBTreePreOrder(T->lchild);
CreateBTreePreOrder(T->rchild);
}
}
括号+逗号非递归
文件二叉树建立数据.txt内容
A(B(D,E(G,)),C(,F))
//二叉树的非递归建立;输入:括号+逗号. eg. A(B(D,E(G,)),C(,F)) 在这里栽跟头!!!!!
//利用一个模拟栈保存上一层节点指针,一个标志位代表左右孩子,输入有四种情况
//1,'(':上一节点指针入栈;标志位表示左孩子
//2,')':出栈
//3,',':标志位更新为右孩子
//4,除了1,2,3,中的符号:申请新节点pcur,获取栈顶指针ppre,根据标志位flag连接为ppre的左/右孩子
void CreateBTree_Stack(BTree &T)
{
fstream inFile("二叉树建立数据.txt",ios::in);
if(!inFile)cout<<"文件打开失败!"<<endl;
BTNode* s[N];//指针数组
int top=-1,flag=1;//模拟栈,top位置有所不同;左右子树标记:1->左;2->右
char ch;
BTNode *ppre=NULL,*pcur=NULL;//父节点,当前节点
while(true)
{
inFile>>ch;
if(!inFile)break;cout<<ch;
//若二叉树输入满足规范,则必须指定节点的左右
switch(ch)
{
case '(':s[++top] = pcur;flag = 1;break;//保存左括号前一个节点的信息;标志位为1,表示下一个字符作为左孩子
case ')':top--;//模拟出栈
case ',':flag = 2;break;//右孩子
default ://默认,为字符
pcur = (BTNode*)malloc(sizeof(BTNode));
pcur->data = ch;
pcur->lchild = pcur->rchild = NULL;
if(T == NULL)T = pcur;//根特殊处理
else{//非空
ppre = s[top];//cout<<"flag:"<<flag<<" ppre:"<<ppre->data<<" pcur:"<<pcur->data<<endl;
if(flag == 1)ppre->lchild = pcur;
else ppre->rchild = pcur;
}
}
}
inFile.close();//好习惯
}
孩子兄弟法(树->二叉树)
与上述非递归方法极其相似,仅有三个区别,在代码中已注释
文件孩子兄弟.txt内容
A(B,C,D(E,F),G)
//建立孩子兄弟链表:输入:A(B,C,D(E,F),G)
//与标准二叉树输入差别在于后者一个括号里可以多于2个孩子
//其下一个节点均依赖于前一个节点,所以实时更新;当右括号来临,退栈
void CreateCSTree_Stack(BTree &T)
{
fstream inFile("孩子兄弟.txt",ios::in);
if(!inFile)cout<<"文件打开失败!"<<endl;
int flag,top=-1;
char ch;
BTNode* s[N];
BTNode *ppre=NULL,*pcur=NULL;
while(true)
{
inFile>>ch;
if(!inFile)break;cout<<ch;
switch(ch){
case '(':s[++top] = pcur;flag=1;break;
case ')':ppre = s[top--];break;//与建立二叉树的区别1
case ',':flag = 2;break;
default :
pcur = (BTNode*)malloc(sizeof(BTNode));
pcur->data = ch;
pcur->lchild = pcur->rchild = NULL;
if(T == NULL)T = pcur;
else{//cout<<"flag:"<<flag<<" ppre:"<<ppre->data<<" pcur:"<<pcur->data<<endl;
// ppre = s[top];区别二
if(flag == 1)ppre->lchild = pcur;
else ppre->rchild = pcur;
}ppre = pcur;//区别三
}
}
}
遍历二叉树
递归与非递归版的前中后序遍历
递归较为简单,三种只是输出语句调换位置
递归前中后遍历
void PreOrderTraverse(BTree T)//前
{
if(T != NULL){
cout<<T->data<<" ";
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
}
void InOrderTraverse(BTree T)//中
{
if(T != NULL){
InOrderTraverse(T->lchild);
cout<<T->data<<" ";
InOrderTraverse(T->rchild);
}
}
void PostOrderTraverse(BTree T)//后
{
if(T != NULL){
PostOrderTraverse(T->lchild);
PostOrderTraverse(T->rchild);
cout<<T->data<<" ";
}
}
非递归遍历
前序
//前序遍历栈实现:与递归思路类似,不过用模拟栈实现
//先输出当前节点,若T的右孩子非空,入栈;
//若T左孩子非空,T更新为其左孩子;若T->lchild为空&&栈空,结束;否则出栈
void PreOrderTraverse_Stack(BTree T)
{
if(T != NULL)
{
BTNode *pcur=NULL;
int top = -1;
BTNode* s[N];
while(true){
cout<<T->data<<" ";
if(T->rchild != NULL)s[++top] = T->rchild;//栈存储非空右孩子 (递归)
if(T->lchild != NULL)T = T->lchild;// 更新为左孩子
else if(top == -1)break;//左孩子空&&栈空=》结束
else T = s[top--];//栈非空,出栈(回溯)
}
}
}
中序
- 注意模拟栈的使用,较为便利
- 将左子树全部入栈,直到空为止,弹出栈顶,若栈顶右孩子存在,更新为右孩子;否则T赋空,便于循环判断
void InOrderTraverse_Stack(BTree T)
{
if(T != NULL)
{
int top = -1;
BTNode* s[N];
do{
while(T != NULL)
{
s[++top] = T;
T = T->lchild;
}
T = s[top--];
cout<<T->data<<" ";
if(T->rchild != NULL){
T = T->rchild;
}
else T=NULL;
}while(top!=-1 || T!=NULL);//栈空&&当前点右子树为空 退出
}
}
层次遍历
普通二叉树
不要求计算层数,一个队列即可,否则需用两个队列
//层次遍历:能够计算层数
//两个队列来回倒
void LevelTraverse(BTree T)
{
if(T == NULL)return;
BTNode* p;
int depth = 0;
Queue Q1,Q2;
InitQueue(Q1);
InitQueue(Q2);
EnQueue(Q1,T);
//两个队列来回倒
while(!IsEmpty(Q1)){
while(!IsEmpty(Q1)){//将Q1出到空为止
p = GetTop(Q1);
DeQueue(Q1);
cout<<p->data<<" ";//出队访问,图必须入队前访问
if(p->lchild != NULL)EnQueue(Q2,p->lchild);//同时将出队元素非空左右节点入Q2,和广度优先遍历相似
if(p->rchild != NULL)EnQueue(Q2,p->rchild);
}
depth++;
cout<<endl;
bool bflag = false;
while(!IsEmpty(Q2)){//与Q1同理
bflag = true;
p = GetTop(Q2);
DeQueue(Q2);
cout<<p->data<<" ";//出队访问,图必须入队前访问
if(p->lchild != NULL)EnQueue(Q1,p->lchild);
if(p->rchild != NULL)EnQueue(Q1,p->rchild);
}
if(bflag){
cout<<endl;
depth++;
}
}
cout<<"层次遍历深度:"<<depth<<endl;
}
孩子兄弟链表
孩子兄弟表示法的层遍历,输出同一代的节点,同时输出总代数
与二叉树的层次遍历类似,两个队列来回倒,不过每次出队后先向左走一步
//孩子兄弟表示法的层遍历,输出同一代的节点,同时输出总代数
//与二叉树的层次遍历类似,两个队列来回倒,不过每次出队后先向左走一步
void LevelCSTree(BTree T)
{
if(T == NULL)return;
int depth=0,flag=1;
Queue Q1,Q2;
InitQueue(Q1);
InitQueue(Q2);
EnQueue(Q1,T);
// cout<<T->data<<endl;
BTNode* p;
while(!IsEmpty(Q1)){
while(!IsEmpty(Q1)){
p = GetTop(Q1);
DeQueue(Q1);
cout<<p->data<<" ";
p = p->lchild;
while(p != NULL){
EnQueue(Q2,p);
p = p->rchild;
}
}cout<<endl;
depth++;
bool bflag = false;
while(!IsEmpty(Q2)){
bflag = true;
p = GetTop(Q2);
DeQueue(Q2);
cout<<p->data<<" ";
p = p->lchild;
while(p != NULL){
EnQueue(Q1,p);
p = p->rchild;
}
}
if(bflag){
cout<<endl;
depth++;
}
}
cout<<"孩子兄弟深度:"<<depth<<endl;
}
测试结果
以下内容2019/1/10更新
递归查询节点,双亲,最近祖先设计分析
先实现最简单的功能,在二叉树中查找指定点。实现此功能后,类似的思路可扩展为查找双亲。实现以上两个基础功能后,可以实现大多数的其他功能,如查找两点的最近祖先。
递归写起来很简洁,但是设计出来或许要花费一番功夫,在没有理清思路前千万别写程序,否则就是无尽循环bug…
设计时关键在于查找分析状态,像是画数电的状态机图,开始查找时宁多毋少,之后再归并化简
查询指定点
实现功能:递归查找一个节点,若存在,返回节点指针;若不存在,返回空
递归设计:
- 1,树为空,查找必定失败,直接返回空
- 2,树根为查找值,查找成功,立刻返回树根指针
- 3,若情况1,2皆不满足,先继续在左子树中查找,回到情况1,2。
- 3.1 若查找成功,直接返回节点指针;
- 3.2 若查找失败,继续在其右子树中查找,回到情况1,2,3
BTNode* Search(BTree T,char e)
{
if(T == NULL) return NULL;//情况1
if(T->data == e)return T;//情况2
else{ //情况3
BTNode* p = Search(T->lchild,e);//继续在左子树查找
if(p != NULL)return p;//在左子树查找成功立刻返回
else Search(T->rchild,e);//继续右子树查找
}
}
查询双亲
实现功能:递归查询指定节点的双亲,存在,返回节点指针;不存在,返回空
递归设计(状态分析是核心,从最简单的开始分析,因为递归就是不断将问题分解为子问题,然后用相同办法处理)
- 1,树为空,必定查找失败,直接返回空
- 2,树仅由根构成,双亲不存在,返回空
- 3,情况1,2均不满足,但在左或右孩子中发现查找值,查找成功,立刻返回当前指针
- 4,情况1,2,3均不满足,且左右孩子均为空,查找失败,返回空
- 5,以上情况均不成立,先进入左子树继续查找(回到情况1,2,3,4,5)
- 5.1 若查找成功,立刻返回当前指针
- 5.2 若查找失败,进入其右子树继续查找(回到情况1,2,3,4,5)
tips:每个状态的判断顺序应该极其注意,由简入繁,考虑他们之间的递进,包含关系,范围小的需在前。
有些情况在实际编码时可以合并,如情况4,它最终会由情况1,2处理。不过分析时全都得考虑到,否则,后果是很可怕的。
写递归思路必须清晰,否则一写就错 。
BTNode* SearchParent(BTree T,char e)
{
if(T == NULL)return NULL;//情况1
if(T->data == e)return NULL;//情况2
if(T->lchild != NULL && T->lchild->data == e ||
T->rchild != NULL && T->rchild->data == e)return T;//情况3 ,需要先判空,否则出现空指针报错
// if(T->lchild == NULL && T->rchild == NULL)return NULL;//情况4
else{//情况5
BTNode* p = SearchParent(T->lchild,e);//先在左子树查找
if(p != NULL)return p;//在左子树中查找成功
else SearchParent(T->rchild,e);//继续在右子树查找
}
}
查询最近祖先
由于有了定位函数,查询双亲函数,查找最近祖先利用二者即可,所以是非递归写法,但是也间接使用了递归法
思路:
先找出两个点a,b中离根近的点,假设是a
- 1,先判断该点是否为b的祖先
- 1.1 若是,a即为二者的最近祖先
- 1.2 若不是,将a更新为a的父节点,继续步骤1
其实非空树中任意两点至少都有一个祖先,根
//寻找两个点的最近祖先 :利用定位函数+双亲定位函数即可(非递归)
BTNode* SearchAncestor(BTree T,int a,int b)
{
//判断树中是否存在这两点
BTNode *p1,*p2,*p;
p1 = Search(T,a);
p2 = Search(T,b);
if(p1 == NULL || p2 == NULL)return NULL;//不存在返回空
if(Depth_TNode(T,a) > Depth_TNode(T,b)){//优化:保证p1为离根近的点
p = p1;
p1 = p2;
p2 = p;
}
//从离根近的点开始判断,以其为根的子树是否包含p2,若包含,则找到最紧祖先;
//若不包含,求出p的父节点p'再次判断。当p'为根节点时直接返回T
p = p1;
while(p != T){
if(Search(p,p2->data) == NULL)p = SearchParent(T,p->data);//更新为p的双亲
else break;
}
return p;
}
树高度求解
整棵树高度
递归设计
- 1,空树,高度为零
- 2,非空,先求其左右子树的高度,取其中较大者,加1即是整棵树的高度
//递归求高度
int Depth(BTree T)
{
if(T == NULL)return 0;//情况1
int lh = Depth(T->lchild);//求左子树高度
int rh = Depth(T->rchild);//求右子树高度
//取大结果+1后返回
if(lh > rh)return lh+1;
else return rh+1;
}
求指定点的高度
最初是想求出整棵树高度H,再求出以指定节点为根的子树T1的高度h,然后二者相减,然后发现T1不一定就是同一层中最高的子树。
实现功能:求指定结点所在高度。
设计思路:利用查找函数及查找双亲函数,从当前点向上查询双亲,直到根
//求指定结点所在高度。利用查找函数及查找双亲函数,从当前点向上查询双亲,直到根
int Depth_TNode(BTree T,char e)
{
BTNode* p = Search(T,e);
if(p == NULL)return -1;
int depth = 1;
while(p->data != T->data){
depth++;//cout<<depth<<endl;cout<<"T->data:"<<T->data<<" p->data:"<<p->data<<endl;
p = SearchParent(T,p->data);//当前点的父节点
}
return depth;
}
输出形式(带括号,逗号)
实现功能:还原表达式:括号和逗号,便于存储于文件中(二叉树,孩子兄弟皆可)
递归设计(不可以写成条件分支,if…else…,这样无法回溯:
- 1,T非空,即为当前树根,直接输出
- 2,T的左子树非空,说明孩子存在,先输出左括号,再对其左子树继续还原
- 3,T的右子树非空,说明兄弟存在,先输出逗号,在对其右子树继续还原
- 4,T的左右子树均为空,表示当前层次结束,输出有括号即可,接着回溯
步骤2,3,4顺序不可换,一换输出必乱,因为表达式中孩子优先级高于兄弟,一个节点同时拥有孩子和兄弟时,孩子离其最近
void DisplayByBracketNotation(BTree T)
{
if(T != NULL){
cout<<T->data;
if(T->lchild != NULL){
cout<<'(';
DisplayByBracketNotation(T->lchild);
}
if(T->rchild != NULL){
cout<<',';
DisplayByBracketNotation(T->rchild);
}
if(T->rchild == NULL && T->lchild == NULL){
cout<<')';
}
}
}
测试结果
测试文件
二叉树建立数据.txt
A(B(D(O(P,Q),),E(G,M(N,K(,S)))),C(,F(H(,J),I)))
孩子兄弟.txt
A(B,C,D(E,F),G,H(I,J(K,L,M),N))
孩子兄弟二叉链表
二叉树测试
完整代码
#include<iostream>
using namespace std;
#include<stdlib.h>
#include<fstream>
#define N 100 //二叉树最大高度<=N
typedef struct BTNode{
char data;
struct BTNode *lchild,*rchild;
}BTNode,*BTree;
//队列节点
typedef struct QNode{
BTNode* data;
struct QNode* next;
}QNode;
typedef struct{
QNode* front;
QNode* rear;
}Queue;
//队列必要的操作:初始化->入队->判空->获取队头->出队(五个操作具有依赖关系)
//初始化队列
void InitQueue(Queue &Q)
{
Q.front = Q.rear = (QNode*)malloc(sizeof(QNode));
Q.front->next = NULL;
}
//入队
void EnQueue(Queue &Q,BTNode* T)
{
QNode* p = (QNode*)malloc(sizeof(QNode));
p->data = T;
p->next = NULL;
Q.rear->next = p;
Q.rear = p;
}
//判空:非空->false;空->true;
bool IsEmpty(Queue Q)
{
if(Q.front == Q.rear)return true;
else return false;
}
//获取队头
BTNode* GetTop(Queue Q)
{
return Q.front->next->data;
}
//出队
void DeQueue(Queue &Q)
{
QNode* p;
if(IsEmpty(Q))return;
p = Q.front->next;
Q.front->next = p->next;
if(Q.front->next == NULL)Q.rear = Q.front;//若删除节点后队列为空,尾指针归位
free(p);p = NULL;
}
//二叉树的非递归建立;输入:括号+逗号. eg. A(B(D,E(G,)),C(,F)) 在这里栽跟头!!!!!
//利用一个模拟栈保存上一层节点指针,一个标志位代表左右孩子,输入有四种情况
//1,'(':上一节点指针入栈;标志位表示左孩子
//2,')':出栈
//3,',':标志位更新为右孩子
//4,除了1,2,3,中的符号:申请新节点pcur,获取栈顶指针ppre,根据标志位flag连接为ppre的左/右孩子
void CreateBTree_Stack(BTree &T)
{
fstream inFile("二叉树建立数据.txt",ios::in);
if(!inFile)cout<<"文件打开失败!"<<endl;
BTNode* s[N];//指针数组
int top=-1,flag=1;//模拟栈,top位置有所不同;左右子树标记:1->左;2->右
char ch;
BTNode *ppre=NULL,*pcur=NULL;//父节点,当前节点
while(true)
{
inFile>>ch;
if(!inFile)break;cout<<ch;
//若二叉树输入满足规范,则必须指定节点的左右
switch(ch)
{
case '(':s[++top] = pcur;flag = 1;break;//保存左括号前一个节点的信息;标志位为1,表示下一个字符作为左孩子
case ')':top--;//模拟出栈
case ',':flag = 2;break;//右孩子
default ://默认,为字符
pcur = (BTNode*)malloc(sizeof(BTNode));
pcur->data = ch;
pcur->lchild = pcur->rchild = NULL;
if(T == NULL)T = pcur;//根特殊处理
else{//非空
ppre = s[top];//cout<<"flag:"<<flag<<" ppre:"<<ppre->data<<" pcur:"<<pcur->data<<endl;
if(flag == 1)ppre->lchild = pcur;
else ppre->rchild = pcur;
}
}
}
inFile.close();//好习惯
}
//fstream inFile("中序建树.txt",ios::in);递归建树没法用文件读取数据,除非每次都重定位读指针
//if(!inFile)cout<<"文件打开失败!"<<endl;
//递归建树:仅前序成功?ABD##EG###C#F##
//后序:##D##G#EB###FCA??中序??
void CreateBTreePreOrder(BTree &T)
{
char ch;
cin>>ch;//cout<<ch<<endl;
// if(!inFile)cout<<ch<<endl;
if(ch == '#')T=NULL;
else{
T = (BTNode*)malloc(sizeof(BTNode));
T->data = ch;
CreateBTreePreOrder(T->lchild);
CreateBTreePreOrder(T->rchild);
}
}
//建立孩子兄弟链表:输入:A(B,C,D(E,F),G)
//与标准二叉树输入差别在于后者一个括号里可以多于2个孩子
//其下一个节点均依赖于前一个节点,所以实时更新;当右括号来临,退栈
void CreateCSTree_Stack(BTree &T)
{
fstream inFile("孩子兄弟.txt",ios::in);
if(!inFile)cout<<"文件打开失败!"<<endl;
int flag,top=-1;
char ch;
BTNode* s[N];
BTNode *ppre=NULL,*pcur=NULL;
while(true)
{
inFile>>ch;
if(!inFile)break;cout<<ch;
switch(ch){
case '(':s[++top] = pcur;flag=1;break;
case ')':ppre = s[top--];break;//与建立二叉树的区别1
case ',':flag = 2;break;
default :
pcur = (BTNode*)malloc(sizeof(BTNode));
pcur->data = ch;
pcur->lchild = pcur->rchild = NULL;
if(T == NULL)T = pcur;
else{//cout<<"flag:"<<flag<<" ppre:"<<ppre->data<<" pcur:"<<pcur->data<<endl;
// ppre = s[top];区别二
if(flag == 1)ppre->lchild = pcur;
else ppre->rchild = pcur;
}ppre = pcur;//区别三
}
}
}
void PreOrderTraverse(BTree T)
{
if(T != NULL){
cout<<T->data<<" ";
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
}
void InOrderTraverse(BTree T)
{
if(T != NULL){
InOrderTraverse(T->lchild);
cout<<T->data<<" ";
InOrderTraverse(T->rchild);
}
}
void PostOrderTraverse(BTree T)
{
if(T != NULL){
PostOrderTraverse(T->lchild);
PostOrderTraverse(T->rchild);
cout<<T->data<<" ";
}
}
//前序遍历栈实现:与递归思路类似,不过用模拟栈实现
//先输出当前节点,若T的右孩子非空,入栈;
//若T左孩子非空,T更新为其左孩子;若T->lchild为空&&栈空,结束;否则出栈
void PreOrderTraverse_Stack(BTree T)
{
if(T != NULL)
{
BTNode *pcur=NULL;
int top = -1;
BTNode* s[N];
while(true){
cout<<T->data<<" ";
if(T->rchild != NULL)s[++top] = T->rchild;//栈存储非空右孩子 (递归)
if(T->lchild != NULL)T = T->lchild;// 更新为左孩子
else if(top == -1)break;//左孩子空&&栈空=》结束
else T = s[top--];//栈非空,出栈(回溯)
}
}
}
void InOrderTraverse_Stack(BTree T)
{
if(T != NULL)
{
// BTNode *ppre = NULL,*pcur = NULL;//*root;
int top = -1;
BTNode* s[N];
// s[++top] = T;
do{
while(T != NULL)
{
s[++top] = T;
T = T->lchild;
}
T = s[top--];
// ppre = T;
cout<<T->data<<" ";
if(T->rchild != NULL){
// s[++top] = T->rchild;
T = T->rchild;
}
else T=NULL;
//else if(top == -1)break;
}while(top!=-1 || T!=NULL);
}
}
void PostOrderTraverse_Stack(BTree T)
{
}
//还原表达式:括号和逗号,便于存储于文件中
//递归设计(不可以写成条件分支,if...else..,这样无法回溯:
//1,T非空,即为当前树根,直接输出
//2,T的左子树非空,说明孩子存在,先输出左括号,再对其左子树继续还原
//3,T的右子树非空,说明兄弟存在,先输出逗号,在对其右子树继续还原
//4,T的左右子树均为空,表示当前层次结束,输出有括号即可,接着回溯
//步骤2,3,4顺序不可换,一换输出必乱,因为表达式中孩子优先级高于兄弟,一个节点同时拥有孩子和兄弟时,孩子离其最近
void DisplayByBracketNotation(BTree T)
{
if(T != NULL){
cout<<T->data;
if(T->lchild != NULL){
cout<<'(';
DisplayByBracketNotation(T->lchild);
}
if(T->rchild != NULL){
cout<<',';
DisplayByBracketNotation(T->rchild);
}
if(T->rchild == NULL && T->lchild == NULL){
cout<<')';
}
}
}
//递归查找一个节点,若存在,返回节点指针;若不存在,返回空
//递归设计:
//1,树为空,查找必定失败,直接返回空
//2,树根为查找值,查找成功,立刻返回树根指针
//3,若情况1,2皆不满足,先继续在左子树中查找,回到情况1,2。
//3.1 若查找成功,直接返回节点指针;
//3.2 若查找失败,继续在其右子树中查找,回到情况1,2,3
BTNode* Search(BTree T,char e)
{
if(T == NULL) return NULL;//情况1
if(T->data == e)return T;//情况2
else{ //情况3
BTNode* p = Search(T->lchild,e);//继续在左子树查找
if(p != NULL)return p;//在左子树查找成功立刻返回
else Search(T->rchild,e);//继续右子树查找
}
}
//递归查询指定节点的双亲,存在,返回节点指针;不存在,返回空
//递归设计(状态分析是核心,从最简单的开始分析,因为递归就是不断将问题分解为子问题,然后用相同办法处理)
//1,树为空,必定查找失败,直接返回空
//2,树仅由根构成,双亲不存在,返回空
//3,情况1,2均不满足,但在左或右孩子中发现查找值,查找成功,立刻返回当前指针
//4,情况1,2,3均不满足,且左右孩子均为空,查找失败,返回空
//5,以上情况均不成立,先进入左子树继续查找(回到情况1,2,3,4,5)
//5.1 若查找成功,立刻返回当前指针
//5.2 若查找失败,进入其右子树继续查找(回到情况1,2,3,4,5)
//tips:每个状态的判断顺序应该极其注意,由简入繁,考虑他们之间的递进,包含关系,范围小的需在前
//有些情况在实际编码时可以合并,如情况4,它最终会由情况1,2处理。不过分析时全都得考虑到,否则,后果是很可怕的
//写递归思路必须清晰,否则一写就错
BTNode* SearchParent(BTree T,char e)
{
if(T == NULL)return NULL;//情况1
if(T->data == e)return NULL;//情况2
if(T->lchild != NULL && T->lchild->data == e ||
T->rchild != NULL && T->rchild->data == e)return T;//情况3 ,需要先判空,否则出现空指针报错
// if(T->lchild == NULL && T->rchild == NULL)return NULL;//情况4
else{//情况5
BTNode* p = SearchParent(T->lchild,e);//先在左子树查找
if(p != NULL)return p;//在左子树中查找成功
else SearchParent(T->rchild,e);//继续在右子树查找
}
}
//递归求高度
//递归设计
//1,空树,高度为零
//2,非空,先求其左右子树的高度,取其中较大者,加1即是整棵树的高度
int Depth(BTree T)
{
if(T == NULL)return 0;//情况1
int lh = Depth(T->lchild);//求左子树高度
int rh = Depth(T->rchild);//求右子树高度
//取大结果+1后返回
if(lh > rh)return lh+1;
else return rh+1;
}
//求指定结点所在高度。利用查找函数及查找双亲函数,从当前点想上查询双亲,直到根
int Depth_TNode(BTree T,char e)
{
BTNode* p = Search(T,e);
if(p == NULL)return -1;
int depth = 1;
while(p->data != T->data){
depth++;//cout<<depth<<endl;cout<<"T->data:"<<T->data<<" p->data:"<<p->data<<endl;
p = SearchParent(T,p->data);//当前点的父节点
}
return depth;
}
//寻找两个点的最近祖先 :利用定位函数+双亲定位函数即可(非递归)
BTNode* SearchAncestor(BTree T,int a,int b)
{
//判断树中是否存在这两点
BTNode *p1,*p2,*p;
p1 = Search(T,a);
p2 = Search(T,b);
if(p1 == NULL || p2 == NULL)return NULL;//不存在返回空
if(Depth_TNode(T,a) > Depth_TNode(T,b)){//优化:保证p1为离根近的点
p = p1;
p1 = p2;
p2 = p;
}
//从离根近的点开始判断,以其为根的子树是否包含p2,若包含,则找到最紧祖先;
//若不包含,求出p的父节点p'再次判断。当p'为根节点时直接返回T
p = p1;
while(p != T){
if(Search(p,p2->data) == NULL)p = SearchParent(T,p->data);//更新为p的双亲
else break;
}
return p;
}
//层次遍历:能够计算层数
//两个队列来回倒
void LevelTraverse(BTree T)
{
if(T == NULL)return;
BTNode* p;
int depth = 0;
Queue Q1,Q2;
InitQueue(Q1);
InitQueue(Q2);
EnQueue(Q1,T);
//两个队列来回倒
while(!IsEmpty(Q1)){
while(!IsEmpty(Q1)){//将Q1出到空为止
p = GetTop(Q1);
DeQueue(Q1);
cout<<p->data<<" ";//出队访问,图必须入队前访问
if(p->lchild != NULL)EnQueue(Q2,p->lchild);//同时将出队元素非空左右节点入Q2,和广度优先遍历相似
if(p->rchild != NULL)EnQueue(Q2,p->rchild);
}
depth++;
cout<<endl;
bool bflag = false;
while(!IsEmpty(Q2)){//与Q1同理
bflag = true;
p = GetTop(Q2);
DeQueue(Q2);
cout<<p->data<<" ";//出队访问,图必须入队前访问
if(p->lchild != NULL)EnQueue(Q1,p->lchild);
if(p->rchild != NULL)EnQueue(Q1,p->rchild);
}
if(bflag){
cout<<endl;
depth++;
}
}
cout<<"层次遍历深度:"<<depth<<endl;
}
//练习表达式建立二叉树,基本上一边过,但是引用没加调了好久
void CreateBTree_Practice(BTree &T)//引用一定记得加!!!!!!!
{
fstream inFile("二叉树建立数据.txt",ios::in);
if(!inFile)cout<<"Fail to open file!"<<endl;
char ch;
BTNode* s[100],*pcur,*ppre;
int top = -1,flag = 1;
while(true){
inFile>>ch;
if(!inFile)break;cout<<ch;
switch(ch){
case '(':s[++top] = pcur;flag = 1;break;
case ')':top--;break;
case ',':flag = 2;break;
default :
pcur = (BTNode*)malloc(sizeof(BTNode));
pcur->data = ch;
pcur->lchild = pcur->rchild = NULL;
if(T == NULL)T = pcur;
else{//cout<<"T:"<<T->data<<endl;
ppre = s[top];//cout<<"pre:"<<ppre->data<<endl;
if(flag == 1)ppre->lchild = pcur;
else ppre->rchild = pcur;
}
}
}
inFile.close();
}
//练习建立孩子兄弟链表,8分钟直接pass
void CreateCSTree_Practice(BTree &T)
{
fstream inFile("孩子兄弟.txt",ios::in);
if(!inFile)cout<<"Fail to open file!"<<endl;
char ch;
BTNode* s[100],*ppre,*pcur;
int top = -1,flag = 1;
while(true){
inFile>>ch;
if(!inFile)break;
switch(ch){
case '(':s[++top] = pcur;flag = 1;break;
case ')':ppre = s[top--];flag = 2;break;
case ',':flag = 2;break;
default :
pcur = (BTNode*)malloc(sizeof(BTNode));
pcur->data = ch;
pcur->lchild = pcur->rchild = NULL;
if(T == NULL)T = pcur;
else{
if(flag == 1)ppre->lchild = pcur;
else ppre->rchild = pcur;
}
ppre = pcur;
}
}
inFile.close();
}
//孩子兄弟表示法的层遍历,输出同一代的节点,同时输出总代数
//与二叉树的层次遍历类似,两个队列来回倒,不过每次出队后向左走一步
void LevelCSTree(BTree T)
{
if(T == NULL)return;
int depth=0,flag=1;
Queue Q1,Q2;
InitQueue(Q1);
InitQueue(Q2);
EnQueue(Q1,T);
// cout<<T->data<<endl;
BTNode* p;
while(!IsEmpty(Q1)){
while(!IsEmpty(Q1)){
p = GetTop(Q1);
DeQueue(Q1);
cout<<p->data<<" ";
p = p->lchild;
while(p != NULL){
EnQueue(Q2,p);
p = p->rchild;
}
}cout<<endl;
depth++;
bool bflag = false;
while(!IsEmpty(Q2)){
bflag = true;
p = GetTop(Q2);
DeQueue(Q2);
cout<<p->data<<" ";
p = p->lchild;
while(p != NULL){
EnQueue(Q1,p);
p = p->rchild;
}
}
if(bflag){
cout<<endl;
depth++;
}
}
cout<<"孩子兄弟深度:"<<depth<<endl;
}
int main()
{
BTree T = NULL;
//以下为3种二叉树实现方式,去掉注释即可测试
// CreateBTree_Stack(T);
// CreateBTreeInOrder(T);
// CreateCSTree_Stack(T);
// CreateBTree_Practice(T);
CreateCSTree_Practice(T);
cout<<endl<<"括号标记:";DisplayByBracketNotation(T);
cout<<endl<<"前序遍历_递归:";PreOrderTraverse(T);
cout<<endl<<"前序遍历_栈 :";PreOrderTraverse_Stack(T);
cout<<endl<<"中序遍历_递归:";InOrderTraverse(T);
cout<<endl<<"中序遍历_栈 :";InOrderTraverse_Stack(T);
cout<<endl<<"后序遍历:";PostOrderTraverse(T);
BTNode* p = Search(T,'G');
if(p != NULL)cout<<"查找的值:"<<p->data<<endl;
else cout<<"查无此数!"<<endl;
p = SearchParent(T,'D');
if(p != NULL)cout<<"双亲的值:"<<p->data<<endl;
else cout<<"查无双亲!"<<endl;
cout<<endl<<"树的高度:"<<Depth(T)<<endl;
cout<<endl<<"结点深度1:"<<Depth_TNode(T,'D')<<endl;
// cout<<endl<<"结点深度2:"<<Depth_TNode2(T,'S')<<endl;
cout<<endl<<"最近共同祖先:"<<SearchAncestor(T,'M','N')->data<<endl;
cout<<endl<<"====层次遍历==="<<endl;LevelTraverse(T);
cout<<endl<<"====孩子兄弟层次==="<<endl;LevelCSTree(T);
return 0;
}