由前序和中序创建二叉树

news2025/1/11 0:33:53

算法分析

 首先,前序是按照 根 -> 左子树 -> 右子树   这样的顺序来进行访问的,也就是说,前序给出的顺序一定是先给出根结点的,那么我们就可以根据前序的顺序来依次递归判断出每个子树的根结点了。

如下所示:

 我们依次向后遍历,就先遍历的根结点,然后在从左子树,和右子树当中来寻找每个子树的根节点。那么我们就可以依次以前序的根节点,从中序当中找出最大的根结点,依此来找出这个根节点对应的左子树和右子树;

 如上图,中序中:在根结点左边的结点就是这个根结点对应的左子树的所有结点在根结点右边的结点就是这个根结点对应的右子树的所有结点。

 由上述的单趟的算法,我们可以递归思想,一层递归判断一个根节点,这样,我们就可以判断所有的根结点的所有左子树和右子树了。

 那么,每一次由根结点所分出来的左子树和右子树的结点,我们可以看做是两个区间,这两个区间就是每一次由根结点分割出来的左子树区间和右子树区间。比如,在左子树区间当中,因为前序的从左向右我们可以依次来找出 根结点。如上述,我们第一趟找出的是 A 这个结点是这个整个数的根节点,那么 B 就是这个 A 这个根结点的左子树的根结点,如下图所示:

 我们在中序当中,循环找出这个 B 这个根结点的 左右子树,如下图所示,用绿色横线表示:

 由 B 这个结点,我们又划分出了两个区间,这两个区间分别是 B 的左子树区间和 右子树区间。

我们还可以继续在 B 的左子树区间和右子树区间当中去递归,继续寻找出其他的根结点,和这个根结点对应的 左子树 和 右子树。

往后的两趟如下所示(我们这里创建二叉树的顺序类似于 前序的 方式创建):

 第三趟:

 第四趟:

 

 当我们访问到这个左右子树都为空的时候就停止递归,并在  return 返回的时候,把这个根结点的 left 和 right 指针置NULL。

基本思想我们了解了,这里主要的创建二叉树的方式就是使用 前序的顺序,来从 前序和中序的顺序当中,一次找出这个根结点,在由这个根结点 分割出两个区间,这个两个区间就是这个 根结点对应的 左子树区间和 右子树区间。

现在主要的问题就是,有一个根结点分割的 左右区间的 范围如果确定?

我们在上述的单趟算法描述的时候,提到了我们要循环找到,前序当中找到的根结点,在存储中序的数组当中的下标位置。这个下标位置很重要,我们要根据这个下标来找出这个 ,每一趟 存储前序数组的区间, 和 存储中序数组的区间。

我们首先来看  存储中序数组的区间:

首先,我们是使用 循环从 前序区间的第一个 结点在中序当中 存储的下标位置(num),那么我们就知道,在 递归左子树 存储中序的数组区间就是 【 L2, num - 1】。其中 L2 是 存储中序数组的,这一层递归的 左区间,因为是左子树递归,所以这个左区间保存;而右区间,要因为 根结点而进行分割,所以是 num 根结点 位置的前一个位置。

而 递归右子树的 存储中序数组的区间应该是 【num + 1, R2】、 。其中 R2 是 存储中序数组的,这一层递归的 有区间,因为是递归右子树,所以有区间不变;而左区间因为 根结点的分割,变成 num 根结点位置的下一个位置。

存储前序数组的区间:

因为由一个 根节点 分出来的左右区间当中的结点个数是相同的,所以,在前序,中序当中的 左区间 和 右区间当中的结点个数也应该是 对应相等的。

 也就是: 前序 左子树区间结点个数 =  中序 左子树区间结点个数;

前序 右子树区间结点个数 = 中序 右子树区间结点个数。

 根据上述,那么 前序 左子树区间 【L1 + 1  ,  L1 + 中序左子树区间的结点个数】,左区间是 L1 + 1 是因为,前序的左区间,要向右来依次递归在中序当中找出根结点 存储 的下标; 那么 左区间 + 区间结点个数  就是 前序 左子树区间。即 【L1 + 1, num + L1 - L2】

根据上述的类似思想,不难推出 前序 的 右子树区间 为:【R1 - 中序右子树区间的结点个数 ,  R1】 , 即 【 num + 1 + R1 - R2 , R1】

 那么代码具体实现,我们采用在函数递归之时,传入当前结点的  left 孩子指针,或者是 right 孩子指针,然后再下一层递归函数当中,创建这个结点的空间,给这个空间结点当中的结点赋值。

 如下所示:

(*Tree) = BuyTreeNode(first[L1]);// 创建一个新的结点

// 递归左子树(构建左子树)
Order_CteatTree(&(*Tree)->left, first, second, L1 + 1, num + L1 - L2 , L2, num - 1);
// 递归右子树(创建右子树)
Order_CteatTree(&(*Tree)->right, first, second, num + 1 + R1 - R2 , R1 , num + 1, R2);

 那么递归的结束条件是,当我们递归到这个区间,不再合法的时候就代表这个 根结点的对应这个区间 已经为空了,没有结点了。代码如下:
 

	if (L1 > R1 || L2 > R2)
	{
		(*Tree) = NULL;
		return;
	}

 创建二叉树的完整代码:

void Order_CteatTree(BTNode** Tree,char* first, char* second, int L1, int R1, int L2 ,int R2)
{
// 递归的结束条件,当区间不在合法的时候,代表这个结点已经不再有孩子结点了
	if (L1 > R1 || L2 > R2)
	{
		(*Tree) = NULL;
		return;
	}
	
	(*Tree) = BuyTreeNode(first[L1]);// 创建一个新的结点

	int num = 0;
	for (num; num < R2; num++)
	{
		if (first[L1] == second[num])
		{
			break;
		}
	}

	Order_CteatTree(&(*Tree)->left, first, second, L1 + 1, num + L1 - L2 , L2, num - 1);
	Order_CteatTree(&(*Tree)->right, first, second, num + 1 + R1 - R2 , R1 , num + 1, R2);
}

如上就是我们所实现的 根据前序和中序来创建二叉树。

我们用 后序 和 层序 来输出一下这个二叉树,来验证一下这个二叉树是否合法:
 

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <string.h>

// 二叉树结点的结构体
typedef char BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType _data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

// 队列的链式结构实现
typedef BTNode* QDatatype;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDatatype data;
}QueueNode;

// 队列的结构体定义
typedef struct Queue
{
	QueueNode* head;
	QueueNode* tail;
	int size;
}Queue;

// 队列的初始化
void QueueInit(Queue* pq)
{
	assert(pq);

	pq->head = pq->tail = NULL;
	pq->size = 0;
}

// 队列的销毁
void QueueDestroy(Queue* pq)
{
	assert(pq);

	QueueNode* cur = pq->head;
	while (cur)
	{
		QueueNode* next = cur->next;
		free(cur);
		cur->next;
	}

	pq->head = pq->tail = NULL;
	pq->size = 0;
}


// 队列的插入
void QueuePush(Queue* pq, QDatatype x)
{
	assert(pq);

	QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
	if (newNode == NULL)
	{
		perror("malloc fail");
		return;
	}

	newNode->next = NULL;
	newNode->data = x;

	if (pq->head == NULL)
	{
		assert(pq->tail == NULL);

		pq->head = pq->tail = newNode;
	}
	else
	{
		pq->tail->next = newNode;
		pq->tail = newNode;
	}

	pq->size++;
}

// 队列删除缘元素
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head);

	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QueueNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
	pq->size--;
}

// 判断队列是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->size == 0;
}

// 拿到队头的数据
QDatatype QueueFront(Queue* pq)
{
	assert(pq);

	return pq->head->data;
}

BTNode* BuyTreeNode(BTDataType x)
{
	BTNode* newNode = (BTNode*)malloc(sizeof(BTNode));
	if (newNode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	newNode->left = NULL;
	newNode->right = NULL;
	newNode->_data = x;

	return newNode;
}

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{
	//if (root == NULL)
	//{
	//	printf("NULL\n");
	//	return;
	//}

	//printf("%d => ", root->_data);

	//BinaryTreePrevOrder(root->left);
	//BinaryTreePrevOrder(root->right);
	if (root)
	{
		putchar(root->_data);
		BinaryTreePrevOrder(root->left);
		BinaryTreePrevOrder(root->right);
	}
}

// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	//if (root == NULL)
	//{
	//	printf("NULL\n");
	//	return;
	//}
	//BinaryTreeInOrder(root->left);
	//printf("%d => ", root->_data);
	//BinaryTreeInOrder(root->right);
	if (root)
	{
		BinaryTreeInOrder(root->left);
		putchar(root->_data);
		BinaryTreeInOrder(root->right);
	}
}

// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	//if (root == NULL)
	//{
	//	printf("NULL\n");
	//	return;
	//}
	//BinaryTreePostOrder(root->left);
	//BinaryTreePostOrder(root->right);
	//printf("%d => ", root->_data);
	if (root)
	{
		BinaryTreePostOrder(root->left);
		BinaryTreePostOrder(root->right);
		putchar(root->_data);
	}
}



// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue qu;  // 队列 结构体
	BTNode* cur;

	QueueInit(&qu); // 队列的初始化

	QueuePush(&qu, root);  // 队列的 入队
	 
	// 如果队列不为空就 继续循环
	while (!QueueEmpty(&qu))
	{
		cur = QueueFront(&qu);

		putchar(cur->_data);

		if (cur->left)
		{
			QueuePush(&qu, cur->left);
		}

		if (cur->right)
		{
			QueuePush(&qu, cur->right);
		}

		QueuePop(&qu);  // 队列的出队
	}

	QueueDestroy(&qu);   // 队列的 销毁
}

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

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

相关文章

源码角度分析多线程并发情况下数据异常回滚方案

一、 多线程并发情况下数据异常回滚解决方案 在需要多个没有前后顺序的数据操作情况下&#xff0c;一般我们可以选择使用并发的形式去操作&#xff0c;以提高处理的速度&#xff0c;但并发情况下&#xff0c;我们使用 Transactional 还能解决事务回滚问题吗。 例如有下面表结…

Go语言并发

Go语言并发学习目标 出色的并发性是Go语言的特色之一 • 理解并发与并行• 理解进程和线程• 掌握Go语言中的Goroutine和channel• 掌握select分支语句• 掌握sync包的应用 并发与并行 并发与并行的概念这里不再赘述, 可以看看之前java版写的并发实践; 进程和线程 程序、进程…

C语言3:根据身份证号输出生年月日和性别

18位身份证号码第7到10位为出生年份(四位数)&#xff0c;第11到12位为出生月份&#xff0c;第13 到14位代表出生日期&#xff0c;第17位代表性别&#xff0c;奇数为男&#xff0c;偶数为女。 用户输入一个合法的身份证号&#xff0c;请输出用户的出生年月日和性别。(不要求较验…

Java数据结构之第十三章、字符串常量池

目录 一、创建对象的思考 二、字符串常量池(StringTable) 三、再谈String对象创建 一、创建对象的思考 下面两种创建String对象的方式相同吗&#xff1f; public static void main(String[] args) {String s1 "hello";String s2 "hello";String s3 …

C# | 线性回归算法的实现,只需采集少量数据点,即可拟合整个数据集

C#线性回归算法的实现 文章目录 C#线性回归算法的实现前言示例代码实现思路测试结果结束语 前言 什么是线性回归呢&#xff1f; 简单来说&#xff0c;线性回归是一种用于建立两个变量之间线性关系的统计方法。在我们的软件开发中&#xff0c;线性回归可以应用于数据分析、预测和…

每日一博 - 对称加密算法 vs 非对称加密算法

文章目录 概述一、对称加密算法常见的对称加密算法优点&#xff1a;缺点&#xff1a;Code 二、非对称加密算法常见的非对称加密算法优点&#xff1a;缺点&#xff1a;Code 概述 在信息安全领域中&#xff0c;加密算法是保护数据安全的重要手段。 加密算法可以分为多种类型&am…

【Linux】线程互斥 与同步

文章目录 1. 背景概念多个线程对全局变量做-- 操作 2. 证明全局变量做修改时&#xff0c;在多线程并发访问会出问题3. 锁的使用pthread_mutex_initpthread_metux_destroypthread_mutex_lock 与 pthread_mutex_unlock具体操作实现设置为全局锁 设置为局部锁 4. 互斥锁细节问题5.…

哈夫曼树(Huffman)【数据结构】

目录 ​编辑 一、基本概念 二、哈夫曼树的构造算法 三、哈夫曼编码 假如<60分的同学占5%&#xff0c;60到70分的占15%…… 这里的百分数就是权。 此时&#xff0c;效率最高&#xff08;判断次数最少&#xff09;的树就是哈夫曼树。 一、基本概念 权&#xff08;we…

Zabbix4.0 自动发现TCP端口并监控

java端口很多&#xff0c;每台机器上端口不固定&#xff0c;考虑给机器配置组不同的组挂载模版&#xff0c;相对繁琐。直接使用同一个脚本自动获取机器上java相关的端口&#xff0c;推送到zabbix-server。有服务端口挂了自动推送告警 一、zabbix-agent配置过程 1、用户自定义参…

Apache Doris :Rollup 物化视图

整理了一下目前开启虚拟机需要用到的程序, 包括MySQL,Hadoop,Linux, hive,Doris 3.5 Rollup ROLLUP 在多维分析中是“上卷”的意思&#xff0c;即将数据按某种指定的粒度进行进一步聚合。 1.求每个城市的每个用户的每天的总销售额 select user_id,city,date&#xff0c; sum(…

树的简单介绍

目录 树的概念 ​ 树的相关概念 树的表示 二叉树的概念 特殊的二叉树 二叉树的存储结构 总结 树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#…

Diffie-Hellman密钥交换协议(Diffie-Hellman Key Exchange,简称DHKE)

文章目录 Diffie-Hellman密钥交换协议&#xff08;Diffie-Hellman Key Exchange&#xff0c;简称DHKE&#xff09;一、密码学相关的数学基础1. 素数&#xff08;质数&#xff09;2. 模运算3. 费马小定理4. 对数5. 离散对数6. 椭圆曲线常见椭圆曲线1. NIST系列曲线secp256k1 2. …

Django实现人脸识别登录

Django实现人脸识别登录 Demo示例下载 1、账号密码登录 2、人脸识别登录 3、注册 4、更改密码 5、示例网站 点我跳转 一、流程说明 1、注册页面:前端打开摄像头,拍照,点击确定后上传图像 2、后端获取到图像,先通过face_recognition第三方库识别是否能够获取到人脸特征…

开闭原则正确姿势, 使用AOP优雅的记录日志, 非常的哇塞

&#x1f473;我亲爱的各位大佬们好&#x1f618;&#x1f618;&#x1f618; ♨️本篇文章记录的为 JDK8 新特性 Stream API 进阶 相关内容&#xff0c;适合在学Java的小白,帮助新手快速上手,也适合复习中&#xff0c;面试中的大佬&#x1f649;&#x1f649;&#x1f649;。 …

使用腾讯云服务器快速搭建网站教程

已经有了腾讯云服务器如何搭建网站&#xff1f;腾讯云服务器网以腾讯云服务器&#xff0c;借助宝塔面板搭建Web环境&#xff0c;然后使用WordPress博客程序搭建网站&#xff0c;大致分为三步&#xff0c;首先购买腾讯云服务器&#xff0c;然后在腾讯云服务器上部署宝塔面板&…

Vue + Vite 构建 自己的ChartGPT 项目

前期回顾 两分钟学会 制作自己的浏览器 —— 并将 ChatGPT 接入_彩色之外的博客-CSDN博客自定义浏览器&#xff0c;并集合ChatGPT&#xff0c;源码已公开https://blog.csdn.net/m0_57904695/article/details/130467253?spm1001.2014.3001.5501 目录 效果图 代码步骤&am…

【Linux】软件包管理器/编辑器/yum是应用商店?/vim编辑器什么?

本文思维导图&#xff1a; 文章目录 Linux软件安装关于Linux的软件生态 1.Linux软件包管理器&#xff1a;yum到底是什么关于yum指令&#xff1a;关于yum源 2. rzsz指令1. Linux编辑器——vim编辑器vim编辑器的三种主要模式vim编辑器命令模式常用快捷键&#xff1a;vim操作总结…

spring(事务管理)

事物可以看做是由对数据库若干操作组成的一个单元 事务的作用就是为了保证用户的每一个操作都是可靠的&#xff0c;事务中的每一步操作都 必须成功执行&#xff0c;只要有发生异常就回退到事务开始未进行操作的状态,这些操作 要么都完成&#xff0c;要么都取消&#xff0c;从而…

Linux:/dev/tty、/dev/tty0 和 /dev/console 之间的区别

在Linux操作系统中&#xff0c;/dev/tty、/dev/tty0和/dev/console是三个特殊的设备文件&#xff0c;它们在终端控制和输入/输出过程中扮演着重要的角色。尽管它们看起来很相似&#xff0c;但实际上它们之间存在一些重要的区别。本文将详细介绍这三个设备文件之间的区别以及它们…

浅谈如何fltk项目编译和实现显示中文

目录 一、编译 二、中文显示如何处理&#xff1a; 2.1在发文2天前突然发现&#xff0c;我这个界面显示英文出现问题了&#xff0c;开始我的搜索之旅&#xff0c;一些参考页面有碰到问题也可以看看&#xff1a; 2.2、 那就开始翻翻官方自带的例程吧&#xff0c;看看他如何显…