二叉树基本操作实现及总结(适合复习)


本文包含以下内容

  • 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;	
} 
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值