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

限时加码!20+主流AI编程工具免费用 购周边加赠Coding Plan Lite,Claude Code、Cursor等即刻畅享,学习进阶更高效! 阅读详情

本文包含以下内容

  • 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;	
} 
【数据结构】C语言实现二叉树 【数据结构】第五章——树与二叉树 详细介绍如何通过C语言实现一棵二叉树…… 阅读详情

相关推荐

零成本实现飞牛 OS 内网穿透:CloudFlare 隧道搭建全攻略

一、引言:突破内网限制的最优解当你的内网环境中部署了飞牛 OS 系统(无论是老旧电脑还是 NAS 设备),想要实现公网访问时,传统方案往往面临两大痛点:公网 IP 申请困难且成本高DDNS 动态解析配置复杂专业内网穿透工具存在付费门槛今天为大家带来零成本解决方案:通过免费域名搭配 CloudFlare 隧道技术,无需公网 IP 即可轻松实现飞牛 OS 服务的公网访问。全程操作可视化,新手也能快速上手!二、期准备:必备工具清单(1)域名准备。

Honmaple的博客 7387

华为笔试题目

第一题 题目描述 一群大雁往南 飞,给定一个字符串记录地面上的游客听到的大雁叫声,请给出叫声最少由几只大雁发出。 具体的: 1.大雁发出的完整叫声为"quack",因为有多只大雁同一时间嘎嘎作响,所以字符串中可能会混合多个"quack"。 2.大雁会依次完整发出"quack",即字符串中’q’ ,‘u’, ‘a’, ‘c’, ‘k’ 这5个字母按顺序完整存在才能计数为一只大雁。如果不完整或者没有按顺序则不予计数。 3.如果字符串不是由’q’, ‘u’, ‘a’, ‘c’, ‘k’ 字符组合而成,或者没有找到

MJQ_YQHXLSCS 3199

S32K14414)ADC

1、简介 开发板S32K144+S32DS+JLINK,裸机版的程序编写 S32K14x包含两个12位ADC模块,ADC0和ADC1 应用程序可能需要ADC的连续采样(4K采样/秒),这可能对CPU有相当大的负载。虽然使用可编程延迟块(PDB)来触发ADC可能会减少一些CPU负载,但当ADC以非常高的速率采样或PDB被绕过时,ADC支持DMA请求功能以获得更高的性能。ADC可以在转换完成时触发DMA(通过DMA请求)。TRGMUX会根据不同的应用需求,使用软件为用户提供更灵活的DMA触发方案,例如,可

朗朗上口就行啦的博客 1万+

C语言二叉树的各种功能的实现

//A(B(D,E(G,H)),C(F( ,I)))                           //二叉树的标准输入方法 #include                                        #include   #define MAX 20 typedef char ElementType;   struct TreeNode { Elem

开始淡漠的博客 1717

二叉链表创建二叉树并用括号输入法输入,横向打印出来

【代码】二叉链表创建二叉树并用括号输入法输入,横向打印出来。

m0_59917959的博客 227

【数据结构】二叉树基本操作大全,学会二叉树基本操作这一篇就够了:二叉树序、中序、后序、层次遍历;求二叉树结点个数,叶子结点个数二叉树第k层的结点个数;求二叉树高度;在二叉树中查找值为x的结点

【数据结构】二叉树基本操作大全,学会二叉树基本操作这一篇就够了:二叉树序、中序、后序、层次遍历;求二叉树结点个数,叶子结点个数二叉树第k层的结点个数;求二叉树高度;在二叉树中查找值为x的结点

2301_76606232的博客 6002

JS 二叉树 中序

输入类似‘a{b{d,e{g,h{,i}}},c{f}}’生成二叉树 let line = 'a{b{d,e{g,h{,i}}},c{f}}' function BiTree(ele) { this.data = ele this.lChild = null this.rChild = null } var n = new BiTree('#') function createBiTree(tree, str) { let top = -1,k let st = [] le

hhxx_11的博客 273

C++二叉排序树的实现

基本功能 int Insert(BTree &T,int key);//按序插入节点 void Creat(BTree &T,int *a,int n);//创建二叉排序树 BTree Search(BTree T,int key);//查找节点 void PostOrder(BTree T);//中序遍历 void LevelOrder(BTree T);//层次遍历 int P...

GKGKD 418

【数据结构的魅力】006.二叉树基本问题

二叉树基本问题 二叉树的结构 二叉树的先序,中序,后序遍历 package dataClass.code02; import java.util.Stack; public class RecursiveTraversalBT { public static class Node { public int value; public Node left; public Node right; public Node(int

我希望许过的愿望一路生花 护送那时的梦抵挡过风沙 286

考研数据结构程序题常见代码【C语言实现

考研院校要求C语言实现数据结构,可期做数据结构都是用Java实现,因此对于C不那么熟悉,花了一些时间重新捡起来C的语法,程序题通常不会考太过复杂的算法,因此自己将辅导书上一些母题进行了实现,其中层序遍历部分需要用到队列,查了一下发现C语言没有现成的头文件调入使用,因此这部分用了C++的语法,除此之外皆为C的语法,现整理如下 链表 结构体实现链表样例 #include<stdio.h> #include<stdlib.h> typedef struct Node{ int nu.

Infinity_and_beyond的博客 4448

二叉树之Java实现二叉树基本操作

参考自《Java数据结构与算法》 定义一个节点类,使节点二叉树操作分离 class Node { int value; Node leftChild; Node rightChild; Node(int value) { this.value = value; } public void display() { System.out.print(th

fengrunche的博客 2万+

数据结构——二叉树基本操作(C语言,完整优美实现

串 文章目录串言一、树是什么?二、该如何理解树?三、各类树的存在形式四、二叉树遍历方式深度遍历先序遍历中序遍历后序遍历层次遍历五、二叉树实现1.二叉树实现1.1 引入库以及设置结构体1.2初始化二叉树1.3队列的基本操作实现1.4二叉树深度遍历的三种方法1.5层次遍历1.6求子叶个数以及结点个数1.7串的输出功能1.8主函数实现上述全部功能2.运行截图总结 1.本文就介绍了数据结构中二叉树的五条性质以及二叉树的存储结构 2.二叉树基本操作的编程实现,掌握二叉树的建立、先序,中序,后序

明宇逸风的博客 1万+

【数据结构】·励志大厂版(复习+刷题):二叉树

二叉树是计算机科学与技术中最基础、最重要的数据结构之一,它既是递归思想的完美载体,也是众多高级数据结构(如二叉搜索树、堆、B树)的基石。无论是算法面试、系统设计还是日常开发,二叉树相关的知识都扮演者不可或缺的角色。文章准备了二叉树的概念讲解、OJ题,助你巩固所学!

月亮永悬不落,剑指巅峰 6011

数据结构 c语言版 二叉树基本操作及算法的实现

数据结构已经上到树的内容了,面的一些结构有所遗忘, 准备出一个系列写一下已经学过的的数据结构类型的基本操作, 主要内容:创建和删除该数据结构 数据的访问,增加,插入,删除,遍历 也有一些拓展的内容 一来作为自己学习的复习巩固和阶段见证 二来也希望能给以后同样学数据结构的同学们提供参考借鉴 这是这个系列的第二个内容,二叉树基本操作和算法 这个系列的其他内容: 数据结构 c语言版 顺...

想变得强大,虽然现在还弱不禁风 1276

二叉树各种遍历算法 Java 实现总结

PS: 作者是正在学习Java的小白,在这里会总结一些与Java相关知识(Java代码实现),如有不足,欢迎来讨论指正,十分感谢 !!! 目录二叉树各种遍历算法 Java 实现总结0 二叉树简述0.0 概述0.1 分类0.2 数据结构1 二叉树遍历1.1 遍历1.2 中序遍历1.3 后序遍历1.4 层序遍历1.5 小总结 二叉树各种遍历算法 Java 实现总结 0 二叉树简述 0.0 概述 二叉树是树的特殊一种,具有如下特点: 1、每个结点最多有两颗子树,结点的度最大为22、左子树和右子树是有顺

qq_41866626的博客 8173

【数据结构】二叉树遍历

本章将会详细讲解二叉树遍历的四种方式,分别为遍历、中序遍历、后续遍历和层序遍历。在学习遍历,会先带大家回顾一下二叉树的基本概念。学习二叉树基本操作,需要先创建一颗二叉树,然后才能学习其相关的基本操作,考虑到我们刚刚接触二叉树,为了能够先易后难地进行讲解,我们将暂时手动创建一颗简单的二叉树,用来方便大家学习。等二叉树结构了解的差不多后,后期我们会带大家研究二叉树地真正的创建方式。

有道无术, 术尚可求。有术无道, 止于术。 1万+

java基础复习——二叉树以及代码实现

说到二叉树,那都是很久以学的了。。。 最近听到之的一个同事说,有人说他基础很好,可以写出二叉树等数据结构的实现,这。。。虽然在很多人看来并没有什么,但是。。。我TM对二叉树完全没印象了,突然觉得自己好菜啊~~~ 所以痛下决心,需要复习一下基础知识了~~~ 那么就先从这个二叉树开始吧。 所谓的二叉树,它是一种数据结构,同时具有数组和链表各自的特点:它可以像数组一样快速查找,也可以像链表一样快

树懒的博客 3068

二叉树基本操作汇总

0. 言 树结构是面试最长考察的一种数据结构,因为数这种结构的难度介于普通的数组和图之间,一般能够比较好的考察一个人的空间想象能力和对递归的使用。通常手写算法部分考察到树结构并不会很难,都是一些最基本的操作,例如后序遍历(某些大厂要求比较高,会让非递归方式实现,这个需要使用到栈进行存储,思考上有一定难度,后续会进行整理),构建一棵树,求数的深度,节点个数等。只要理解树的结构和递归的过程,面...

Kaiyang_Shao的博客 398

java复习之顺序二叉树实现

java复习之顺序二叉树实现 package learn.tree; import java.util.Enumeration; /** * 顺序存储二叉树 */ public class ArrayTree { public static void main(String[] args) { int[] array = {1,2,3,4,5,6,7,8}; ArrayT arrayT = new ArrayT(array); arrayT

剑在手,跟我狗 385

复习二叉树

一些不熟悉的基本概念 结点的度(degree):结点拥有的子树的棵数 叶子leaf:度为0 分支结点:除了叶子结点意外的结点 树的度:树中结点的度的最大值 深度为k的二叉树,至多有2^k-1结点 对于任何一颗二叉树,如果其叶子结点数为N0,度为2结点树为N2,那么N0=N2+1 总的边数为N,N=N0+N1+N2-1,因为根结点没有边,所以减去一个1 N=1xN1+2xN2,度为一的点有一条边,度为二的点有两条边 两个式子结合就得到N0=N2+1二叉树与完全二叉树的区别(最后一层从左到右有

weixin_42857002的博客 1765

二叉树复习与整理

递归建立二叉树,先序,中序,后序遍历,层次遍历

qq_29170835的博客 543
上一篇: 简单易懂的迷宫走法--栈应用(c++)
下一篇: 二叉排序树基本功能实现(C++)
是阿俊呐
博客等级 码龄9年 245粉丝 129原创
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值