二叉树的相关操作

news2024/11/26 0:24:12

一.二叉树

本文的数据结构基于C语言练习。

C语言中的二叉树是一种数据结构,用于表示具有层次关系的数据集合。它由一个根节点开始,每个节点最多有两个子节点,分别称为左子节点和右子节点。

二叉树有许多相关性质,其中一些重要的包括:

  1. 深度:指从根节点到某个节点的路径长度。树的深度等于所有节点深度的最大值加上1。
  2. 高度:指从某个节点到其子树中的最远叶子节点的路径长度。树的高度等于根节点的高度。
  3. 节点数:指树中节点的总数。如果树的深度为d,则节点数不超过2^d-1个。
  4. 层次遍历:按照从上到下、从左到右的顺序遍历树中的节点。
  5. 先序遍历:先访问根节点,然后按照先序遍历左子树和右子树。
  6. 中序遍历:先按照中序遍历左子树,然后访问根节点,最后按照中序遍历右子树。
  7. 后序遍历:先按照后序遍历左子树和右子树,然后访问根节点。
  8. 平衡二叉树:指任意节点的左右子树高度差不超过1的二叉树。
  9. 满二叉树:指所有非叶子节点都有两个子节点,且所有叶子节点都在同一层的二叉树。
  10. 完全二叉树:指除最后一层之外,每一层都被填满的二叉树,最后一层从左到右填充。

二.二叉树的遍历

这里通过一个例子来详细介绍:

2023/6/8 09:49:27

二叉树遍历是指按照某个顺序依次访问二叉树的每一个节点,目前比较常用的三种遍历分别是先序遍历、中序遍历和后序遍历。下面举例介绍这三种遍历:

假设有如下一棵二叉树:

        1
       / \
      2   3
     / \   
    4   5  

其中节点1为根节点,节点2和3为节点1的子节点,节点4和5为节点2的子节点。

  1. 先序遍历:

先序遍历指先访问根节点,然后按先序遍历左子树和右子树。对于上述二叉树的先序遍历结果为:1, 2, 4, 5, 3。具体方法是从根节点出发,先输出根节点1,然后递归地遍历左子树2和右子树3,对于左子树2,先输出它的根节点2,然后递归遍历它的左子树4和右子树5。

  1. 中序遍历:

中序遍历指按照中序遍历左子树、访问根节点和中序遍历右子树的顺序来遍历二叉树。对于上述二叉树的中序遍历结果为:4, 2, 5, 1, 3。具体方法是先递归遍历左子树2,输出节点4和2,再输出根节点1,最后递归遍历右子树3,输出节点5。

  1. 后序遍历:

后序遍历指按照后序遍历左子树、后序遍历右子树和访问根节点的顺序来遍历二叉树。对于上述二叉树的后序遍历结果为:4, 5, 2, 3, 1。具体方法是先递归遍历左子树2,输出节点4和5,再递归遍历右子树3,输出节点2,最后输出根节点1。

三.线索二叉树

线索二叉树是一种特殊的二叉树,其每个节点都附带了指向其前驱和后继节点的线索,这些线索可以加速对节点的遍历操作。在线索二叉树中,若左子树存在,则左子树的最右下节点的右孩子会指向该节点的后继节点;若右子树存在,则右子树的最左下节点的左孩子会指向该节点的前驱节点。

线索二叉树的遍历分为前序、中序、后序和按照线索遍历四种方式,下面我们以中序遍历为例进行介绍。

假设有如下一棵二叉树:

        1
       / \
      2   3
     / \   
    4   5  

其中节点1为根节点,节点2和3为节点1的子节点,节点4和5为节点2的子节点。

对于线索二叉树,我们需要首先将其转换成线索二叉树,过程如下:

  1. 先建立一个头结点,其中头结点的左孩子指向根节点,右孩子指向中序遍历的最后一个节点。
  2. 对于每一个节点,如果其左孩子不存在,则将其左孩子设置为前驱节点,并将前驱节点的右孩子指向该节点。如果其右孩子不存在,则将其右孩子设置为后继节点,并将后继节点的左孩子指向该节点。
  3. 对根节点进行中序遍历,递归地对左子树进行线索化,然后处理其前驱指针,随后递归地对右子树进行线索化,然后处理其后继指针。

转换成线索二叉树之后,我们可以使用中序遍历来遍历整棵树。具体方法是从头结点开始,依次访问每个节点的后继节点,直到遇到尾节点即可结束遍历。

对于上述例子,通过中序遍历得到的节点顺序为:4, 2, 5, 1, 3。而在线索二叉树中,4的后继节点是2,2的后继节点是5,5的后继节点是1,1的后继节点是3,最后3的后继节点是尾节点,因此我们依次输出4、2、5、1、3就完成了中序遍历。

四.核心功能实现

1.初始构造一棵二叉树

//我们先初始化构造一个二叉树
void InitBTree(BTnode &T){                  //T是一个结构体指针,指向这个树结点的结构体,而这个结构体又包含两个指针
	T=(BTnode)malloc(sizeof(BTree));
	T->data=50;
	T->lchild=NULL;
	T->rchild=NULL;
	T->ltag=T->rtag=0;
}

//插入一个树结点
void Insert(BTnode &T,int x){             //这里x是我们插入结点需要保存的值
	if(T == NULL){ // 最后结点为空,插入节点
		T = (BTnode)malloc(sizeof(BTree));
		T->data = x;
		T->lchild = NULL;
		T->rchild = NULL;
		T->ltag=T->rtag=0;
	}
	else{
		if(x <= T->data){ // 插入左子树
			Insert(T->lchild, x);
		}
		else{ // 插入右子树
			Insert(T->rchild, x);
		}
	}
}

2.普通二叉树的递归遍历

//访问,也就是输出函数
void Visit(BTnode &T){
	printf("%d\t",T->data);
}

//先序遍历
void Preorder(BTnode &T){
	if(T!=NULL){
		Visit(T);        //在访问函数里面定义我们想要的可视化输出
		Preorder(T->lchild);
		Preorder(T->rchild);
	}
}

//中序遍历
void Inorder(BTnode &T){
	if(T!=NULL){
		Inorder(T->lchild);
		Visit(T);
		Inorder(T->rchild);
	}
}

//后序遍历
void Postorder(BTnode &T){
	if(T!=NULL){
		Postorder(T->lchild);
		Postorder(T->rchild);
		Visit(T);
	}
}

3.二叉树线索化

//中序遍历线索化
void InThread(BTnode &p,BTnode &pre){
	if(p!=NULL){
		InThread(p->lchild,pre);
		if(p->lchild==NULL){
			p->lchild=pre;             //中序遍历左孩子就是根结点的前驱
			p->ltag=1;
		}
		if(pre!=NULL && pre->rchild==NULL){           //刚刚建立了这个结点的前驱,那前驱结点的后继不就是该结点吗
			pre->rchild=p;
			pre->rtag=1;
		}
		pre=p;                   //这个结点标记完了,换下一个
		//中间这一部分可以改写成visit函数,你就看出来这个简单的递归了
		InThread(p->rchild,pre);
	}
}
/*这只是针对某一个结点线索化的处理过程*/

//构造中序线索二叉树
void createITree(BTnode &T){          //调用刚刚线索化的方法来改造我们原来的二叉树
	BTnode pre=NULL;                //刚开始假设没有pre则为NULL
	if(T!=NULL){
		InThread(T,pre);                //把二叉树进行线索化
		pre->rchild=NULL;               //处理最后一个结点
		pre->rtag=1;
	}
	
}

4.线索二叉树的遍历

//该函数用来找二叉树中序序列的第一个结点
BTNode *Firstnode(BTnode &p){
	while(p->ltag==0)              //第一个结点没有前驱结点,所以其lchild=0,其余原本左孩子为空的结点都变成了左线索
		p=p->lchild;
	return p;
}

//该函数用来找后继结点
BTNode *Nextnode(BTnode &p){
	if(p->rtag==0)
		return Firstnode(p->rchild);      //rtag=0说明还是右孩子,找右子树中的第一个结点为其后继
	else
		return p->rchild;  //ratg=1说明右孩子就是后继,直接返回
}

//最后的大招,中序线索二叉树的遍历
void Inorder1(BTnode &T){
	for(BTNode *p=Firstnode(T);p!=NULL;p=Nextnode(p))          //不要for循环只会i+1
		Visit(p);                
}

五.完整代码

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

typedef struct BTNode{
	int data;                          //为了实验简便,这里存储的数据都为整数
	struct BTNode *lchild,*rchild;
	int ltag,rtag;                   //左右线索标志
}BTree,*BTnode;                       //之所以这样是为了区分结点和树

//我们先初始化构造一个二叉树
void InitBTree(BTnode &T){                  //T是一个结构体指针,指向这个树结点的结构体,而这个结构体又包含两个指针
	T=(BTnode)malloc(sizeof(BTree));
	T->data=50;
	T->lchild=NULL;
	T->rchild=NULL;
	T->ltag=T->rtag=0;
}

//插入一个树结点
void Insert(BTnode &T,int x){             //这里x是我们插入结点需要保存的值
	if(T == NULL){ // 最后结点为空,插入节点
		T = (BTnode)malloc(sizeof(BTree));
		T->data = x;
		T->lchild = NULL;
		T->rchild = NULL;
		T->ltag=T->rtag=0;
	}
	else{
		if(x <= T->data){ // 插入左子树
			Insert(T->lchild, x);
		}
		else{ // 插入右子树
			Insert(T->rchild, x);
		}
	}
}
/*为了方便定义插入规则,我们这里实验二叉排序树的规则即可*/
/*虽然这里用二叉排序树的规则方便了定义插入规则,但复杂了删除操作
  我们这里也只是为了演示二叉树的遍历和线索二叉树,所以不定义删除函数*/

//访问,也就是输出函数
void Visit(BTnode &T){
	printf("%d\t",T->data);
}

//先序遍历
void Preorder(BTnode &T){
	if(T!=NULL){
		Visit(T);        //在访问函数里面定义我们想要的可视化输出
		Preorder(T->lchild);
		Preorder(T->rchild);
	}
}

//中序遍历
void Inorder(BTnode &T){
	if(T!=NULL){
		Inorder(T->lchild);
		Visit(T);
		Inorder(T->rchild);
	}
}

//后序遍历
void Postorder(BTnode &T){
	if(T!=NULL){
		Postorder(T->lchild);
		Postorder(T->rchild);
		Visit(T);
	}
}

/*完成了这几个遍历后,我们要开始构造线索二叉树了*/
/*构造三种线索二叉树,前提是这个树以及存在,我们采用的方法是边遍历边构造*/

//中序遍历线索化
void InThread(BTnode &p,BTnode &pre){
	if(p!=NULL){
		InThread(p->lchild,pre);
		if(p->lchild==NULL){
			p->lchild=pre;             //中序遍历左孩子就是根结点的前驱
			p->ltag=1;
		}
		if(pre!=NULL && pre->rchild==NULL){           //刚刚建立了这个结点的前驱,那前驱结点的后继不就是该结点吗
			pre->rchild=p;
			pre->rtag=1;
		}
		pre=p;                   //这个结点标记完了,换下一个
		//中间这一部分可以改写成visit函数,你就看出来这个简单的递归了
		InThread(p->rchild,pre);
	}
}
/*这只是针对某一个结点线索化的处理过程*/

//构造中序线索二叉树
void createITree(BTnode &T){          //调用刚刚线索化的方法来改造我们原来的二叉树
	BTnode pre=NULL;                //刚开始假设没有pre则为NULL
	if(T!=NULL){
		InThread(T,pre);                //把二叉树进行线索化
		pre->rchild=NULL;               //处理最后一个结点
		pre->rtag=1;
	}
	
}
//这样我们就把原来的那棵二叉树改成了线索二叉树,为了查看我们的线索二叉树是否正确,我们又要写对应线索二叉树的方法

/*对线索树进行遍历时,只要先找到序列的第一个结点,然后依次取其后继,知道其后继为空代表整个二叉树遍历完*/
/*这里又有一点,其右线索标志为1,右孩子就指示其后继,但有时候也有其结点原来左右孩子就都不为空,这个时候就选择其右子树中
  第一个访问的结点(右子树中最左下的结点)为其后继*/

//该函数用来找二叉树中序序列的第一个结点
BTNode *Firstnode(BTnode &p){
	while(p->ltag==0)              //第一个结点没有前驱结点,所以其lchild=0,其余原本左孩子为空的结点都变成了左线索
		p=p->lchild;
	return p;
}

//该函数用来找后继结点
BTNode *Nextnode(BTnode &p){
	if(p->rtag==0)
		return Firstnode(p->rchild);      //rtag=0说明还是右孩子,找右子树中的第一个结点为其后继
	else
		return p->rchild;  //ratg=1说明右孩子就是后继,直接返回
}

//最后的大招,中序线索二叉树的遍历
void Inorder1(BTnode &T){
	for(BTNode *p=Firstnode(T);p!=NULL;p=Nextnode(p))          //不要for循环只会i+1
		Visit(p);                
}

int main(){
	BTnode T;
	InitBTree(T);
	int nums[]={20,45,68,54,8};   //初始化二叉树待插入的数据,注意我们初始化定义了根结点的值为50
	for(int i=0;i<5;i++){
		Insert(T,nums[i]);
	}
	//到这里我们脑海里应该有了二叉树的画面了
	printf("先序遍历:");
	Preorder(T);
	printf("\n");
	printf("中序遍历:");
	Inorder(T);
	printf("\n");
	printf("后序遍历:");
	Postorder(T);
	printf("\n");
	//这一行下面开始我们转向线索二叉树
	createITree(T);
	printf("中序线索二叉树遍历:");
	Inorder1(T);
}

/*这里提一嘴,学习了栈和队列后,我们知道递归背地里是通过栈来实现的,所以这里的三种遍历我们如果
  不想使用递归,就得使用栈,比较麻烦,为了快速演示,递归虽然效率低但我们还是选择使用它*/

六.运行结果

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

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

相关文章

记录一下idea黄色警戒线问题

记录一下idea黄色警戒线问题 一、通用文件中解决黄色波浪线问题1.选中File中的Settings进入2.点击Editor&#xff0c;选中Inspections&#xff0c;找到General&#xff0c;找到Dulicated code fragment点击取消 二、SQL文件中黄色警告线 一、通用文件中解决黄色波浪线问题 1.选…

jvm之7种垃圾回收器解读(下)

目录 G1回收器&#xff1a;区域化分代式 G1回收器的特点&#xff08;优势&#xff09; 空间整合 可预测的停顿时间模型&#xff08;即&#xff1a;软实时soft real-time&#xff09; G1垃圾收集器的缺点 G1回收器的参数设置 G1收集器的常见操作步骤 G1收集器的适用场景 分…

Spring SpringMVC Mybatis 整合 SSM整合 一篇就够了!

SSM详细整合教程 因为XML注解方式实现更加方便&#xff0c;所以我门选用它 文章目录 SSM详细整合教程一、整合思路二、整合步骤0. 前期准备引入Jar包与Web目录创建1. Spring框架编写1.1 创建Application配置文件 2. SpringMvc框架编写2.1 创建Springmvc-config文件2. 2 配置前端…

用栈模拟实现队列(c语言版)

前言 用"栈实现队列",力扣中一道oj题,可以帮助刚接触"栈"和"队列"的新手更好的理解栈和队列这两种结构. 题目来源于力扣: 题目链接:https://leetcode.cn/problems/implement-queue-using-stacks/ 难度:简单 目录 前言一、队列的各接口:1.1 类型…

机器学习实战六步法之数据收集方法(四)

要落地一个机器学习的项目&#xff0c;是有章可循的&#xff0c;通过这六个步骤&#xff0c;小白也能搞定机器学习。 看我闪电六连鞭&#xff01;&#x1f923; 数据收集 数据是机器学习的基础&#xff0c;没有数据一切都是空谈&#xff01;数据集的数据量和数据的质量往往决…

潮牌搭配APP的设计与实现

摘 要&#xff1a;本文开发过程以android为中心&#xff0c;通过数据库进行的数据访问操作。软件以面向对象的思维进行开发和设计&#xff0c;针对于广大群众进行下载使用&#xff0c;对用户提供了时尚的搭配&#xff0c;带领用户形成自己的一套搭配系统&#xff0c;做自己搭配…

C++ std::thread 与Qt qthread多线程混合编程

C与Qt深度融合&#xff1a;高效设计多线程应用框架 1. C与Qt线程的混合使用1.1 C线程与Qt线程的基本概念1.2 线程间的相互依赖关系1.3 设计合理的代码框架 二、深入理解C和Qt线程模型2.1 C线程模型2.2 Qt线程模型2.3 C和Qt线程模型的比较 三、C和Qt线程间的互操作性3.1 std::th…

Web应用技术(第十六周/END)

本次练习基于how2j的教程完成对SpringBoot的初步学习。 初识Springboot 学习导入&#xff1a;1.第一个基于SpringBoot的项目&#xff1a;&#xff08;1&#xff09;application.java&#xff1a;该文件中的核心代码&#xff1a; &#xff08;2&#xff09;HelloController.java…

浅结代码混淆2

文章目录 SMC 自解码什么是SMC&#xff1f;原理示例动调 &#xff4d;ov混淆 SMC 自解码 什么是SMC&#xff1f; 简而言之&#xff0c;就是程序中的部分代码在运行之前被加密成一段数据&#xff0c;不可反编译&#xff0c;通过程序运行后执行相关的解码功能&#xff0c;对加密…

Nginx搭建Https反向代理,使用阿里云免费SSL证书 - Docker

Docker安装Nginx - 需要有域名 没有docker需提前安装docker&#xff0c;不知怎么安装的请自行百度。 1、拉取镜像 docker pull nginx2、去阿里云或者其他云服务提供商申请免费证书&#xff0c;申请到之后下载下来&#xff0c;上传到服务器 # 创建nginx-proxy目录 mkdir ngi…

软考A计划-系统架构师-官方考试指定教程-(13/15)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

IP签名档PHP开源版:轻松打造网站个性签名档

今天&#xff0c;我们将为大家介绍一个有趣的IP签名档项目。通过将源代码部署在服务器上&#xff0c;您可以轻松地为自己的社交媒体、论坛等地创建一个独特的签名档&#xff0c;使您的网站更加出彩&#xff01; 接下来&#xff0c;我们将详细向大家展示如何搭建PHP开源版IP签名…

ASP.NET Core Web API入门之一:创建新项目

ASP.NET Core Web API入门之一&#xff1a;创建新项目 一、引言二、创建新项目三、加入Startup类&#xff0c;并替换Program.cs内容四、编辑Program.cs代码五、修改控制器的路由六、运行项目 一、引言 最近闲着&#xff0c;想着没真正从0-1开发过ASP.NET Core Web API的项目&a…

SpringMVC原理分析 | Hello程序

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; SpringMVC Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架&#xff0c;本质上相当于 Servlet&#xff1b; 拥有结构最清晰的 ServletJSPJav…

uni-app APP、html引入html2canvas截图以及截长图

下载安装html2canvas 方式一&#xff0c;https://www.bootcdn.cn/ CDN网站下载html2canvas插件 这里下载后放在测项目目录common下面 页面中引入 方式二、npm方式安装html2canvas 1、npm方式下载 npm i html2canvas2、引入html2canvas import html2canvas from html2can…

linux系统CAN驱动问题分析

在TI sam3354芯片上&#xff0c;使用3.13及4.19版内核&#xff0c;编译CAN驱动&#xff0c;加载启动后&#xff0c;查看有CAN设备&#xff0c;但无法直接使用ifconfig操作CAN设备&#xff0c;以下简单分析下问题。 加载驱动后&#xff0c;查看网络设备&#xff1a; 可以看到有…

分布式项目15 用户注册,单点登陆,用户退出dubbo来实现

用户注册 分析&#xff1a;当用户填写完成注册信息之后,将请求发送给前台服务器.之后前台消费者利用dubbo框架实现RPC调用。之后将用户信息传递给jt-sso服务提供者.之后完成数据的入库操作。 01.页面url分析 02.查看页面JS $.ajax({ type : "POST", url : "/…

煤矿安全防范,DTU为采矿过程提供实时数据支持

在当今快节奏的时代&#xff0c;采矿行业为我们提供了丰富的资源。然而&#xff0c;随着采矿作业的不断扩大和复杂化&#xff0c;我们也面临着一系列潜在的挑战。其中&#xff0c;数据传输和安全问题尤为突出。 想象一下&#xff0c;在一个繁忙的矿山中&#xff0c;海量的数据需…

让软件研发可视化可量化,华为云CodeArts持续加速企业研发转型

导读&#xff1a;软件开发工具从未像今天这样重要。 “没有度量&#xff0c;就没有管理。” (If you can’t measure it, you can’t manage it.) 管理学大师彼得德鲁克的话时刻提醒人们&#xff0c;度量是管理的必要条件。 在高科技领域&#xff0c;研发投入是企业核心竞争力的…

Linux常用命令——grep命令

在线Linux命令查询工具 grep 强大的文本搜索工具 补充说明 grep&#xff08;global search regular expression(RE) and print out the line&#xff0c;全面搜索正则表达式并把行打印出来&#xff09;是一种强大的文本搜索工具&#xff0c;它能使用正则表达式搜索文本&…