二叉树的建立和遍历

news2025/1/20 5:52:38

目录

  • 创建二叉树中的引用
  • 使用遍历顺序创建二叉树
    • 使用先序遍历和中序遍历创建二叉树
    • 使用中序和后序创建二叉树
    • 中序求二叉树
  • 用栈实现非递归遍历
    • ==先序遍历==
    • ==中序遍历==
    • 后序遍历
  • 用栈通过出栈次数进行遍历
    • 中序遍历
    • ==后序遍历==
  • 队列进行层次遍历
    • 思路
    • 代码
  • 判断是否是满二叉树和完全二叉树
    • 递归
    • 非递归
      • 满二叉树
      • 判断完全二叉树

创建二叉树中的引用

在二叉树的存储中,我们发现在创建二叉树函数的时候,,它的参数是char* &str,此处的&是c++中的引用,什么是引用呢?
格式为:类型 &引用变量名 = 已定义过的变量名。
例如:int a=20;int &b=a;,此处的引用b便是a的重命名,怎么理解呢,其实a,b指的是同一个变量,a,b只是他不同的名字。
此处为什么用引用呢,如果去掉引用会发生什么呢?
在数组元素等于ABC##DE##F##G#H## 的时候,输出值会发生错误,发现只在abc之间,不会遍历到后面的元素。怎么样的思路呢,我们递归调用函数时,比如A结点接下来进入左孩子调用函数之后,其在本身函数内也只自增了一次,右孩子调用函数自增一次,在调用的函数时没有在本质上对其进行增加。所以只会输出到abc。要想使其在调用函数时使其数组进行自增,就用到了引用的概念,
在这里插入图片描述
用了引用有什么不同呢,创建二叉树函数中用了引用,那么在其函数中的str指的便是主函数中str的别名,str自增一次,其数组本身也就会自增一次,函数调用便会对str影响,没有用引用,函数调用完成之后,返回到原函数时str仍然在原位置,没有进行自增。

使用遍历顺序创建二叉树

使用先序遍历和中序遍历创建二叉树

思路:
例如:先序:ABCDEFGH ,中序:CBEDFAGH,线序遍历第一个元素是根节点,用根节点将中序遍历的顺序分成两部分CBEDF和GH,分别是左孩子和右孩子。然后将线序去掉根节点,分成BCDEF和GH,第一个结点为左孩子的根节点B,右孩子根节点是G,,将CBEDF由B分成两份,分别是C和EDF,将右孩子划分,根节点是G,在中序遍历中H在G的右边所以是右孩子。剩下就是左孩子一部分,通过这种放法依次进行分类下去,最终就可以将二叉树创建完成。如下图
在这里插入图片描述
代码:

int FindPos(const char* istr, int n,const char ch) {
	int pos = -1;
	for (int i = 0; i < n; ++i) {
		if (ch == istr[i]) {
			pos = i;
			break;
		}
	}
	return pos;
}
BtNode* CreatBTreePI(const char* pstr, const char* istr,int n) {
	BtNode* s = nullptr;
	if (n > 0) {
		s = Buynode();
		s->data = pstr[0];
		int pos = FindPos(istr, n, pstr[0]);
		if (-1 == pos) exit(1);
		s->leftchild = CreatBTreePI(pstr+1,istr,pos);
		s->rightchild = CreatBTreePI(pstr+pos+1,istr+pos+1,n-pos-1);
	}
	return s;
}
BtNode* CreateBinaryTreePI(const char* pstr, const char* istr) {
	int n = strlen(pstr);
	int m = strlen(istr);
	if (nullptr == pstr || nullptr == istr || n < 1 || m < 1 || n != m) return nullptr;
	else return CreatBTreePI(pstr, istr, n);

}

使用中序和后序创建二叉树

思路此处不做过多展示,同线序和中序一样,只是根节点先序在第一个,后序在最后一个。直接给出代码:

int FindPos(const char* istr, int n,const char ch) {
	int pos = -1;
	for (int i = 0; i < n; ++i) {
		if (ch == istr[i]) {
			pos = i;
			break;
		}
	}
	return pos;
}
BtNode* CreatBTreeIL(const char* istr, const char* lstr, int n) {
	BtNode* s = nullptr;
	if (n > 0) {
		s = Buynode();
		s->data = lstr[n-1];
		int pos = FindPos(istr, n, lstr[n-1]);
		if (-1 == pos) exit(1);
		s->leftchild = CreatBTreeIL(istr , lstr, pos);
		s->rightchild = CreatBTreeIL(istr + pos + 1, lstr + pos, n - pos - 1);
	}
	return s;
}
BtNode* CreateBinaryTreeIL(const char* istr, const char* lstr) {
	int n = strlen(istr);
	int m = strlen(lstr);
	if (nullptr == lstr || nullptr == istr || n < 1 || m < 1 || n != m) return nullptr;
	else return CreatBTreeIL(istr, lstr, n);

}

中序求二叉树

给出如下图这样一个由树先序遍历得到的数组,能否求出该树的中序遍历。
在这里插入图片描述
我们可以看出树中为空的位置都用-1替换了,我们可以想想办法用他的编号和高度来求其元素。代码中左孩子和右孩子的编号分别是双亲节点编号的二倍+1和+2。
代码:

BtNode* InOrder_Ar(int *arr,int i,int n){
 if(i<n&&arr[i]!=-1){
    InOrder_Ar(arr,i*2+1,n);//life
    printf("%5d",arr[i]);
    InOrder_Ar(arr,i*2+1,n);//right
 }
}

int main(){
int arr[]={31,23,12,66,-1,5,17,70,62,-1,-1,-1,88,-1,55};
int n=sizeof(arr)/sizeof(arr[0]);
InOrder_Ar(arr,0,n);
}

用栈实现非递归遍历


给出如图的二叉树,如何不使用递归来遍历二叉树呢,我们用需要用栈进行辅助。

先序遍历

思路:先序遍历我们会发现规律都是先输出最左孩子,然后从左孩子的最后一个结点的右兄弟开始遍历,依次向根节点遍历。因为是栈,尾插尾删,所以我们从根节点的右孩子开始依次将右孩子入栈,直到左孩子为null,然后从栈顶(最后一个)元素依次遍历。

void NicePreOrder(BtNode* ptr) {
	if (nullptr == ptr) return;
	stack<BtNode*>st;
	while (ptr != nullptr || !st.empty()) {
		while (ptr != nullptr) {
			printf("%c ", ptr->data);
			if (ptr->rightchild != nullptr) {
				st.push(ptr->rightchild);
			}
			ptr = ptr->leftchild;
		}
		if (!st.empty()) {
			ptr = st.top(); st.pop()}
	}
	/*
	if (nullptr == ptr) return;
	stack<BtNode*> st;
	st.push(ptr);
	while (!st.empty())
	{
		ptr = st.top(); st.pop();
		printf("%3c", ptr->data);
		if (ptr->rightchild != nullptr)
		{
			st.push(ptr->rightchild);
		}
		if (ptr->leftchild != nullptr)
		{
			st.push(ptr->leftchild);
		}
	}
	printf("\n");
*/
}

中序遍历

思路:因为中序遍历的顺序是左中右,所以从根节点开始遍历最先输出的应该是最左边的结点,从根节点开始入栈,根节点的左节点一直从根节点往下找左节点,直到左孩子为null停止查找,取出栈顶元素,将其出栈,因为左根右,所以应该输出当前结点的data域,接下来遍历他的右孩子,然后继续循环判断其左孩子依次循环吗,直到栈为null结束循环,因为是while循环,并且刚开始栈为空不能进入循环所以进入循环的条件加上ptr!=null.

void NiceInOrder(BtNoder* ptr){
if(nnullptr==ptr) return;
st::stack<BtNode*>st;
while(ptr!=nullptr||!st.empty()){
   while(ptr!=nullptr){
    st.push(ptr);
    ptr=ptr->leftchild;
   }
   ptr=st.top();st.pop();
   printf("%3c",ptr->data);
   ptr=ptr->rigthchild;
 }
printf("\n");
}

后序遍历

我们的后序遍历和中序遍历唯一不同的地方是其先判断右孩子,右孩子不存在或者已经输出过了之后才会输出结点的data域,因此我们选择做标记。我们定义一个结点tag,如果结点输出过了,令tag=右孩子,所以我们在输出前加入了条件如果该结点的右孩子等于null或者右孩子等于我们的标记,条件满足就输出。如果不满足我们就将右孩子入栈,然后令ptr=右孩子。结点变为右孩子进入重新遍历

void NicePastOrder(BtNoder* ptr){
if(nnullptr==ptr) return;
st::stack<BtNode*>st;
BtNode* tag=nullptr;
while(ptr!=nullptr||!st.empty()){
   while(ptr!=nullptr){
    st.push(ptr);
    ptr=ptr->leftchild;
   }
   ptr=st.top();st.pop();
   if(ptr->rigthchild==nullptr||ptr->rigthchild==tag){
    printf("%3c",ptr->data);
    tag=ptr;
    ptr=nullptr;
   }
   else{
   st.push(ptr);
   ptr=ptr->rigthchild;
   }
 }
printf("\n");
}

用栈通过出栈次数进行遍历

思路我们以后序遍历为例做一阐述:每次出栈队队其结点的计数器进行+1,我们通过结点的计数器判断其输出到了该结点的哪个元素。首先根结点入栈,计数器为0,取栈顶元素并且出栈,判断计数器+1是否等于3,若等于3则输出,(为什么此处前置++呢,因为出栈依次就需要+1,如果不等于3就是第一次或者第二次出栈,就要再次入栈),判断左节点的计数器是否等于1如果是则进行左节点入栈,判断计数器是否等于2,若是右节点入栈,注意此处必须左右孩子不为null。这样一个结点的计数器等于3时变不需要进行再次入栈,直接输出即可。
因为先序遍历直接输出data域即可,所以用出栈次数没有意义

中序遍历

void NiceInOrder_2(BtNode* ptr) {
	if (ptr == nullptr) return;
	stack<StkNode>st;
	st.push(StkNode{ ptr,0 });
	while (!st.empty()) {
		StkNode node = st.top(); st.pop();
		if (++node.popnum == 2) {
			printf("%c ", node.pnode->data);
			if (node.pnode->rightchild != nullptr) {
				st.push(StkNode{ node.pnode->rightchild,0 });
			}
		}
		else {
			st.push(node);
			if (node.popnum == 1 && node.pnode->leftchild != nullptr) {
				st.push(StkNode{ node.pnode->leftchild ,0 });
			}
		}

	}

}

后序遍历

void NicePastOrder_2(BtNode* ptr) {
	if (ptr == nullptr) return;
	stack<StkNode>st;
	st.push(StkNode{ptr,0});
	while (!st.empty()) {
		StkNode node = st.top(); st.pop();
		if (++node.popnum == 3) {
			printf("%c ",node.pnode->data);
		}
		else {
			st.push(node);
			if (node.popnum == 1&&node.pnode->leftchild!=nullptr) {
				st.push(StkNode{ node.pnode->leftchild ,0});
			}
			if (node.popnum == 2 && node.pnode->rightchild != nullptr) {
				st.push(StkNode{node.pnode->rightchild,0});
			}
		}
		
	}

}

队列进行层次遍历

在这里插入图片描述

思路

如上图:层次遍历的顺序是ABGCDHEF,因为我们用的是队列,先入先出,所以我们的层次遍历是从右孩子向左孩子依次入队,先将根节点入队,接判断其右孩子左孩子是否存在,若存在依次入队,接着继续判断队列是否
队列为null进入循环,取出队首元素并且出队,依次进行判断左右孩子入队循环下去即可。用上图举例,首先a入队,a的左孩子右孩子都存在,所以gb依次入队,取出队首元素并且出队,b判断b的左右孩子dc依次入队,接着依次遍历下去,直到队列为null即可。

代码

void LevelOrder(BtNode* ptr) {
	if (ptr == nullptr) return;
	queue<BtNode*>qu;
	qu.push(ptr);
	while (!qu.empty()) {
		ptr = qu.front(); qu.pop();
		printf("%c ",ptr->data);
		if (ptr->rightchild != nullptr) {
			qu.push(ptr->liftchild);
		}
		if (ptr->leftchild != nullptr) {
			qu.push(ptr->rightchild);
		}
	}
	printf("\n");
}

判断是否是满二叉树和完全二叉树

递归

利用二叉树的大小和深度关系进行计算比较

int max(int a, int b) {
	return a > b ? a : b;
}
int GetSize(BtNode* ptr) {
	if (ptr == nullptr)return 0;
	else	return 1 + GetSize(ptr->leftchild) + GetSize(ptr->rightchild);
}
int GetDepth(BtNode* ptr) {
	if (ptr == nullptr) return 0;
	else return 1 + max(GetDepth(ptr->leftchild), GetDepth(ptr->rightchild));
}
bool IsFullBTree(BtNode* ptr) {
	if (ptr == nullptr) return false;
	else return pow(2, GetDepth(ptr))-1 == GetSize(ptr);
}

非递归

满二叉树

在这里插入图片描述

思路:如上图,我们用到两个队列,先将1存入a队列,遍历a队列中的元素,同时将a的左右孩子存入b队列,接下来将b队列元素依次遍历将b这一层的元素2,3的孩子都依次入到a栈中去,就这样依次a,b两个队列相互配合,但是判断的条件在哪里呢?因为是完全二叉树所以第一层肯定是一个元素,第二层是1+1=2个元素,第三层是2+2=4个元素,很明显就是1,2,4,8,16这样下去。我们判断出队的循环中如果小于此时那一层的数时,就不是满二叉树,如果那一个的队列因为数据都会null(其下一层没有左右孩子)而队列为null此时就退出循环,返回真,说明是完全二叉树。

bool IsFullBinaryTree(BtNode* ptr) {
	bool ret = true;
	if (ptr == nullptr) return ret;
	queue<BtNode*>qua;
	queue<BtNode*>qub;
	int n = 1;
	qua.push(ptr);
	while (!qua.empty() || !qub.empty()) {
		int i= 0;
		for (; i < n&&!qua.empty(); ++i) {
			ptr = qua.front(); qua.pop();
			if (ptr->leftchild != nullptr) {
				qub.push(ptr->leftchild);
			}
			if (ptr->rightchild != nullptr) {
				qub.push(ptr->rightchild);
			}
		}
		if (i < n) {
			ret = false;
			break;
		}
		if (!qub.empty()) break;
		n += n;
		for (int i = 0; i < n && !qua.empty(); ++i) {
			ptr = qub.front(); qub.pop();
			if (ptr->leftchild != nullptr) {
				qua.push(ptr->leftchild);
			}
			if (ptr->rightchild != nullptr) {
				qua.push(ptr->rightchild);
			}
		}
		if (i < n) {
			ret = false;
			break;
		}		
		if (!qub.empty()) break;
		n += n;
	}
	return ret;
}

判断完全二叉树

思路:将二叉树从根开始,从左向右依次入队,因为是队列,头删尾插,所以应该从左向右依次入队。入队直到ptr为null,所以队列的退出入队,开始遍历剩下的,因为是完全二叉树,所以最后的一部分不能存在数据,也就是说树中最后的元素都必须为null,否则就是不完全二叉树。

bool IsCompBinaryTree(BtNode* ptr) {
	bool ret = true;
	queue<BtNode*>qu;
	qu.push(ptr);
	while (!qu.empty()) {
		ptr = qu.front(); qu.pop();
		if (ptr->leftchild == nullptr) {
			break;
		}
			qu.push(ptr->leftchild);
			qu.push(ptr->rightchild);
	}
	while (!qu.empty()) {
		ptr = qu.front(); qu.pop();
		if (ptr == nullptr) {
			ret = false;
			break;
		}
	}
	return ret;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/26535.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

面向开发者的开源低代码开发工具,强烈推荐!

每家公司在发展过程中都需要构建大量的内部系统&#xff0c; 比如运营使用的用户管理后台&#xff0c;销售线索后台&#xff0c;双十一活动后台&#xff0c;圣诞节活动后台等。 许多公司内部也都有专门的研发团队负责开发各种各样的后台和内部工具&#xff0c;大量的公司为此付…

Qt开发-QT Quick

前言 QT Quick和Qt widgets这两种技术&#xff0c;官方是强推QT Quick的。 QT Quick中布局一般有如下四种方式&#xff0c; 绝对坐标&#xff1a;x、y、z、width、height、top、left锚(anchors) 布局定位器&#xff08;Row、Column、Grid、Flow&#xff09;布局管理器&#…

(微信开发)Laya转发H5网页到微信,带图片

网页转发到微信时&#xff0c;带图片和自定义标题。2022年11月22号 关键解说 _wx.config({ debug: _wx_configdebug, appId: e.appId, timestamp: e.timestamp, nonceStr: e.nonceStr, signature: e.signature, jsApiList: [ // 所有要调用的 API 都要加到这个列表中 ‘onMen…

网络威胁情报git【全面】

开源地址如下: https://github.com/fastfire/deepdarkCTI 网络威胁情报 (CTI) 被定义为收集和分析有关威胁和对手的信息以及绘制模式&#xff0c;这些模式提供了针对各种网络攻击的准备、预防和响应行动做出明智决策的能力。 CTI 涉及收集、研究和分析网络威胁领域的趋势和技…

APP测试面试题汇总(基础篇、进阶篇)

一、基础篇 1、请介绍一下&#xff0c;APP测试流程&#xff1f; APP测试流程与web测试流程类似&#xff0c;分为如下七个阶段&#xff1a; 1.根据需求说明书编写测试计划&#xff1b; 2.制定测试方案&#xff0c;主要是测试任务、测试人员和测试时间的分配&#xff1b; 3.…

Elasticsearch GC优化实践

近期业务查询线上ES集群出现频繁超时告警&#xff0c;尤其是早晨某个时间点固定的报一波超时&#xff0c;从调用链监控上很难看出是什么业务行为导致的。 初步猜测 查看Grafana上Elasticsaerch的基础监控&#xff0c;发现业务告警与ES的Old GC&#xff08;老年代GC&#xff0…

功率放大器的参数和应用场景是什么

功率放大器是电子测量行业比较常见的一种电子放大器&#xff0c;主要目的是增加给定输入信号的功率幅度&#xff0c;使输入信号功率增加&#xff0c;从而驱动到发射器等输出设备的负载水平。和电流放大器与电压放大器有所不同的是&#xff0c;功率放大器是直接驱动负载并且最终…

SessionCookie

会话 会话&#xff1a;用户打开浏览器进行的一系列操作直至关闭浏览器的过程看作是一次会话 HTTP协议是无状态的&#xff0c;不能实现跟踪对话。比如进入一个网站&#xff0c;每次操作的请求之间相互独立&#xff0c;无法相互联系。也就是说你每次请求过后得到的服务器响应或…

web前端-javascript-基本语法(注释,常用语法,代码格式)

文章目录基本语法1. JS 注释2. 常用语法3. 代码格式基本语法 <!DOCTYPE html> <html><head><meta charset"utf-8" /><title></title><script type"text/javascript">/** 多行注释*///单行注释alert("hell…

学生学python编程---实现贪吃蛇小游戏+原码

学生学python编程---实现贪吃蛇小游戏原码前言主要设计1、蛇的表示2、蛇怎么移动&#xff1f;3、玩家控制小蛇移动功能的实现4、如何判定游戏结束&#xff1f;应用知识点1、python知识点1.1 列表append()在列表未尾增加一个元素del 删除最后一个元素在指定位置增加元素用insert…

vCenter命令行升级

1.为当前vCenter打快照 2.为vCenter关联新的iso镜像 3.SSH登录vCenter 4.检查ISO镜像 software-packages stage --iso software-packages list --staged 5.安装vCenter&#xff0c;安装预计40分钟 software-packages install --staged 6.重启vCenter Command>shell #re…

【论文】撰写小论文用到的资料

一、小论文算法的学习 &#xff08;一&#xff09;资料链接 1.联邦学习&#xff1a;https://www.baidu.com/s 2.迁移学习概述&#xff08;Transfer Learning&#xff09;https://blog.csdn.net/dakenz/article/details/85954548 3.迁移学习&#xff1a;经典算法解析&#xff…

前端怎么解决跨域

JSONP jsonp的原理就是利用<script>标签没有跨域限制&#xff0c;通过<script>标签src属性&#xff0c;将本地的全局函数通过callback传到服务器&#xff0c;服务端将接口返回数据拼凑到callback函数中&#xff0c;返回给客服端 实现思路 服务端的代码&#xff…

第七章 数学 AcWing 1533. 1 的个数

第七章 数学 AcWing 1533. 1 的个数 原题链接 AcWing 1533. 1 的个数 算法标签 数学 枚举 数位DP 思路 显然&#xff0c;直接暴力枚举时间复杂度 230(枚举N个数)∗10(枚举N个数每一位)≈10102^{30}(枚举N个数)*10(枚举N个数每一位)\approx10^{10}230(枚举N个数)∗10(枚举…

windows下通过远程桌面访问linux图形界面

一、安装epel库 epel库安装之前无法使用yum install xrdp命令安装xrdp 命令&#xff1a;yum install epel-release之后会自动匹配对应版本的rpm包&#xff0c;并解决依赖关系进行安装。 二、安装xrdp xrdp作为linux的图形化界面 1.命令&#xff1a;yum install xrdp2.开启…

【POJ No. 3368】 最频繁值 Frequent values

【POJ No. 3368】 最频繁值 Frequent values 北大OJ 题目地址 【题意】 给定n 个整数的非递减序列a 1 , a 2 ,…, an &#xff0c;对每个索引i 和j 组成的查询&#xff08;1≤i ≤j ≤n &#xff09;&#xff0c;都确定整数ai , …, aj 中的最频繁值&#xff08;出现次数最多…

Jmeter工具下载并直连MySQL数据库

优秀链接&#xff1a; Jmeter汉化 Jmeter初认识 前提有JDK&#xff0c;我的是1.8 下载Jmeter 下载的Jmeter版本是5.5无需配置Jmeter路径&#xff0c;下载后解压便可以运行 官网地址&#xff1a;https://jmeter.apache.org/download_jmeter.cgi 官网下载比较慢&#xff0c;在…

[附源码]计算机毕业设计JAVA健身健康规划系统

[附源码]计算机毕业设计JAVA健身健康规划系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybati…

ADPCM(自适应差分脉冲编码调制)的原理和计算

关于ADPCM ADPCM(Adaptive Differential Pulse Code Modulation, 自适应差分脉冲编码调制) 是一种音频信号数字化编码技术, 音频压缩标准G.722, G.723, G.726 中都会使用到 ADPCM G.722 is an ITU-T standard 7 kHz wideband audio codec operating at 48, 56 and 64 kbit/s. …

【Linux】linux中,你不得不爱的命令集(下)

我们将要介绍的命令并不是linux中所有的命令&#xff0c;是我们常见的和经常要使用的命令。 我们所用的linux版本是centos7&#xff0c;我们的linux搭建是在腾讯云服务器上搭建的&#xff0c;借助Xshell登录服务器&#xff0c;在root下进行命令行的操作。 目录 mv指令&#x…