二叉树的具体原理及实现

news2024/12/26 21:18:26

文章目录

  • 一.树的专业术语
  • 二.二叉树的原理
  • 三.常见的二叉树分类
    • 1.完全二叉树
    • 2.平衡二叉树
    • 3.二叉搜索树
  • 四.二叉搜索树算法具体实现
  • 五.二叉搜索树具体实现代码

一.树的专业术语

首先先介绍树的专业术语
在这里插入图片描述

二.二叉树的原理

二叉搜索树(Binary Search Tree,BST)是一种常见的数据结构,它在计算机科学中被广泛应用于数据的存储和检索。它是一棵二叉树,其中每个节点都包含一个键值,并满足以下性质:

左子树中的所有节点的键值小于根节点的键值。
右子树中的所有节点的键值大于根节点的键值。
左子树和右子树也都是二叉搜索树。
这个性质使得二叉搜索树具有非常高效的查找、插入和删除操作。

二叉搜索树的原理可以通过以下几个操作来解释:

查找(Search):从根节点开始,比较要查找的键值与当前节点的键值。如果它等于当前节点的键值,则查找成功。如果要查找的键值小于当前节点的键值,则继续在左子树中查找;如果要查找的键值大于当前节点的键值,则继续在右子树中查找。直到找到匹配的键值或者遍历到叶子节点为止。

插入(Insertion):插入操作从根节点开始,比较要插入的键值与当前节点的键值。如果要插入的键值小于当前节点的键值,并且当前节点没有左子节点,则将新节点作为当前节点的左子节点;如果要插入的键值大于当前节点的键值,并且当前节点没有右子节点,则将新节点作为当前节点的右子节点。如果当前节点已有左子节点或右子节点,则继续在相应的子树上进行插入操作,直到找到合适的位置。

删除(Deletion):删除操作是比较复杂的,因为需要考虑不同的情况。首先,找到要删除的节点。如果要删除的节点没有子节点,可以直接删除它。如果要删除的节点只有一个子节点,可以用其子节点替换它。如果要删除的节点有两个子节点,可以找到其右子树中的最小节点(或者左子树中的最大节点)来替换它。替换后,再删除该最小(或最大)节点。删除操作需要保持二叉搜索树的性质。

总结来说,二叉搜索树通过利用节点键值的大小关系,将较小的值放在左子树,较大的值放在右子树。这样的组织结构可以在平均情况下以O(log n)的时间复杂度进行查找、插入和删除操作,但在最坏情况下,如果树的形状极度不平衡,时间复杂度可能会退化为O(n)。因此,在实际应用中,需要进行平衡操作,如红黑树或AVL树,以保证树的平衡性,提高性能。

三.常见的二叉树分类

1.完全二叉树

完全二叉树 — 若设二叉树的高度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数第 h 层有叶子节点,并且叶子结点都是从左到右依次排布,这就是完全二叉树(堆就是完全二叉树)。

在这里插入图片描述
在这里插入图片描述

2.平衡二叉树

平衡二叉树— 又被称为 AVL 树,它是一颗空树或左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树。
在这里插入图片描述

3.二叉搜索树

二叉搜索树 — 又称二叉查找树、二叉排序树(Binary Sort Tree)。它是一颗空树或是满足下列性质的二叉树:
1.若左子树不空,则左子树上所有节点的值均小于或等于它的根节点的值;
2.若右子树不空,则右子树上所有节点的值均大于或等于它的根节点的值;
3.左、右子树也分别为二叉排序树。
在这里插入图片描述

四.二叉搜索树算法具体实现

当我们在数组中查找一个数的时候,需要从前往后逐个遍历,这样效率很忙
二叉搜索树就是把数据用它的规则进行从大到小排序,使用折半查找(二分查找)
在这里插入图片描述
二叉树一般采用链式存储方式:每个结点包含两个指针域,指向两个孩子结点,还包含一个数据域,存储结点信息。在这里插入图片描述
二叉搜索树插入节点
将要插入的结点 e,与节点 root 节点进行比较,若小于则去到左子树进行比较,若大于则去到右子树进行比较,重复以上
操作直到找到一个空位置用于放置该新节点

二叉搜索树删除节点
将要删除的节点的值,与节点 root 节点进行比较,若小于则去到左子树进行比较,若大于则去到右子树进行比较,重复以
上操作直到找到一个节点的值等于删除的值,则将此节点删除。删除时有 4 中情况须分别处理:
1.删除节点不存在左右子节点,即为叶子节点,直接删除
2.删除节点存在左子节点,不存在右子节点,直接把左子节点替代删除节点
3.删除节点存在右子节点,不存在左子节点,直接把右子节点替代删除节点
4.删除节点存在左右子节点,则取左子树上的最大节点或右子树上的最小节点替换删除节点。

二叉树的遍历
二叉树的遍历是指从根结点出发,按照某种次序依次访问所有结点,使得每个结点被当且访问一次。共分为四种方式:
前序遍历 - 先访问根节点,然后前序遍历左子树,再前序遍历右子树
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

五.二叉搜索树具体实现代码

stack.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include "tree.h"

#define MaxSize 128

typedef struct _SqStack {
	Bnode* base;		//栈底指针
	Bnode* top;		//栈顶指针
}SqStack;

bool InitStack(SqStack& S) //构造一个空栈 S
{
	S.base = new Bnode[MaxSize];//为顺序栈分配一个最大容量为 Maxsize 的空间
	if (!S.base) return false; //空间分配失败

	S.top = S.base; //top 初始为 base,空栈

	return true;
}

bool PushStack(SqStack& S, Bnode e) {	 插入元素 e 为新的栈顶元素
	if (S.top - S.base == MaxSize) {
		printf("栈为满!\n");
		return false;
	}

	*(S.top++) = e;	//元素 e 压入栈顶,然后栈顶指针加 1,等价于*S.top=e;S.top++;

	return true;
}

bool PopStack(SqStack& S, Bnode& e) {	//删除 S 的栈顶元素,暂存在变量 e中
	if (S.top == S.base) {
		printf("空栈!\n");
		return false;
	}

	e = *(--S.top);	//栈顶指针减 1,将栈顶元素赋给 e

	return true;
}

Bnode* GetTop(SqStack& S) { //返回 S 的栈顶元素,栈顶指针不变
	if (S.base != S.top) {
		return S.top - 1;
	}
	else
	{
		printf("空栈!\n");
		return nullptr;
	}
}

int GetSize(SqStack& S) {	//返回栈中元素个数
	return (S.top - S.base);
}

bool IsEmpty(SqStack& S) {//判断栈是否为空
	if (S.top == S.base) {
		return true;
	}
	else {
		return false;
	}
}

void DestroyStack(SqStack& S) {//销毁栈
	if (S.base) {
		free(S.base);
		S.base = NULL;
		S.top = NULL;
	}
}

tree.h

#pragma once
#define MAX_NODE 1024

#define isLess(a,b)  (a<b)
#define isEqual(a,b) (a==b)

typedef int ElemType;

typedef struct _Bnode {
	ElemType data;	//数据
	struct _Bnode* lchild, * rchild;	//左右孩子节点
}Bnode, Btree;	//Bnode是结构体的指针类型,*Btree是提前定义好了的指向结构体的指针
//	*Btree等于提前创建了一个存储_Bnode类型的指针

main

#include <iostream>
#include <assert.h>
#include <Windows.h>
#include "tree.h"
#include "stack.h"

#define MAX_NODE 1024

using namespace std;


bool InsertBtree(Btree** root, Bnode* node) {	 //插入
	Bnode* tmp = nullptr;
	Bnode* parent = nullptr;
	bool abs = false;

	if (!node) return false;
	else {
		node->lchild = nullptr;
		node->rchild = nullptr;
	}

	if (*root) {	//存在根节点
		tmp = *root;
	}
	else			//不存在根节点
	{
		*root = node;
		return true;
	}

	while (tmp != NULL)
	{
		parent = tmp;	//保存父节点
		printf("父节点:%d\n", parent->data);
		if (isLess(node->data, tmp->data)) {
			tmp = tmp->lchild;
			abs = true;		//
		}
		else {
			tmp = tmp->rchild;
			abs = false;	
		}
	}

	//if (isLess(node->data,parent->data))
	if (abs) {	//找到空位置后,进行插入
		parent->lchild = node;
	}
	else
	{
		parent->rchild = node;
	}

	return true;
}

int findMax(Btree* root) {
	assert(root != nullptr);

	//方式一,使用递归
	/*if (root->rchild) {
		root = root->rchild;
	}
	return root->data;*/

	//方式二,使用循环
	while (root->rchild)
	{
		root = root->rchild;
	}

	return root->data;
}

Btree* DeleteNode(Btree* root, int key, Btree*& deleteNode) {
	if (root == nullptr)return NULL;	//没有找到删除的节点

	if (root->data > key) {
		root->lchild = DeleteNode(root->lchild, key, deleteNode);
		return root;
	}
	else if (root->data < key) {
		root->rchild = DeleteNode(root->rchild, key, deleteNode);
		return root;
	}

	deleteNode = root;

	//删除节点不存在左右子节点,即为叶子节点,直接删除
	//所有删除功能待实现
	if (root->lchild == nullptr && root->rchild == nullptr) return NULL;

	//删除节点只存在右子节点,直接用右子节点取代删除节点
	if (root->lchild == nullptr && root->rchild != nullptr)return root->rchild;

	//删除节点只存在左子节点,直接用左子节点取代删除节点
	if (root->lchild != NULL && root->rchild == NULL)return root->lchild;

	//删除节点存在左右子节点,直接用左子节点最大值取代删除节点
	//循环断点仔细看看这段代码
	int val = findMax(root->lchild);
	root->data = val;	//赋值
	root->lchild = DeleteNode(root->lchild, val, deleteNode);	//用完了就要删掉

	return root;
}

//使用递归查询节点
Bnode* queryByRec(Btree* root, ElemType e) {
	if (root == nullptr || isEqual(root->data, e))
		return root;
	else if (isLess(e, root->data))
		return queryByRec(root->lchild, e);
	else
		return queryByRec(root->rchild, e);
}

//使用非递归查询节点
Bnode* queryByLoop(Bnode* root, int e) {
	while (root != nullptr && !isEqual(root->data, e))
	{
		if (isLess(root->data, e)) {
			root = root->rchild;
		}
		else {
			root = root->lchild;
		}
	}

	return root;
}

//采用递归实现前序遍历
void PreOrderRec(Btree* root) {
	if (root == nullptr)return;

	printf("-%d-", root->data);
	PreOrderRec(root->lchild);
	PreOrderRec(root->rchild);
}

//采用非递归实现前序遍历
//借助栈实现前序遍历
void PreOrder(Btree* root) {
	Bnode cur;

	if (root == nullptr)return;

	SqStack stack;
	InitStack(stack);
	PushStack(stack,*root);	//头节点先入栈

	while (!(IsEmpty(stack)))
	{
		PopStack(stack, cur);	//要遍历的节点
		printf("-%d-", cur.data);
		if (cur.rchild != nullptr) {
			PushStack(stack,*(cur.rchild));	//右子节点先入栈,后处理
		}

		if (cur.lchild!=nullptr) {
			PushStack(stack, *(cur.lchild));//左子节点后入栈,接下来先处理
		}
	}
	DestroyStack(stack);
}

int main(void) {
	int test[] = { 19, 7, 25, 5, 11, 15, 21, 61 };
	Bnode* root = NULL, * node = NULL;
	Bnode* Delete = nullptr;	//记录被删除的节点
	node = new Bnode;
	node->data = test[0];
	InsertBtree(&root, node);	//插入根节点
	for (int i = 1; i < sizeof(test) / sizeof(test[0]); i++) {
		node = new Bnode;
		node->data = test[i];
		if (InsertBtree(&root, node)) {
			printf("节点 %d 插入成功\n", node->data);
		}
		else {
			printf("节点 %d 插入失败\n", node->data);
		}
	}

	Bnode* tmp = queryByRec(root, 25);
	printf("搜索二叉搜索树,节点 25 %s\n", tmp ? "存在" : "不存在");

	Bnode* tmp1 = queryByRec(root, 55);
	printf("搜索二叉搜索树,节点 55 %s\n", tmp1 ? "存在" : "不存在");

	cout << endl;
	PreOrderRec(root);
	cout << endl;
	PreOrder(root);
	cout << endl;

	cout << "删除节点 25" << endl;
	Bnode* del = DeleteNode(root, 25, Delete);
	Bnode* tmp2 = queryByRec(root, 25);
	delete Delete;	//销毁内存
	printf("搜索二叉搜索树,节点 25 %s\n", tmp2 ? "存在" : "不存在");

	cout << endl;
	PreOrderRec(root);
	cout << endl;
	PreOrder(root);
	cout << endl;
	
	system("pause");
	return 0;
}

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

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

相关文章

Java判断时间间隔来决定任务执行

数据库中的任务记录表的某条记录&#xff0c;状态一直无法变更&#xff0c;导致整个任务进程卡住&#xff0c;看了代码如下&#xff0c; 正常情况是要走到这个else里面&#xff0c;执行dockingGroup.setStatus(JobStatus.FAILED.getCode());将任务状态改为失败的 &#xff0c;查…

Windows/Linux(命令、安装包和源码安装)平台各个版本QT详细安装教程

前言 本文章主要介绍了Windows/Linux平台下&#xff0c;QT4&#xff0c;QT5&#xff0c;QT6的安装步骤。为什么要把QT版本分开介绍呢&#xff0c;因为这三个版本&#xff0c;安装步骤都不一样。Windows平台&#xff0c;QT4的Qt Creator&#xff0c;QT库和编译器是分开的&#…

【奇思妙想】【节省磁盘空间】我有一些文件,我不想移动它们,但又想节省磁盘空间,该怎么做呢?

2023年9月17日&#xff0c;周日晚上 今天晚上整理电脑的磁盘空间时&#xff0c;无意间想到了这个事情 我有一个文件夹 这个文件夹里面有很多不常用的文件 但我不想把它们移走或删除&#xff0c;那我怎么才能从中节省磁盘空间呢&#xff1f; 很简单&#xff0c;把这些不常用的…

Maven常见面试题总结

Maven简介 Maven 是一个项目管理和整合工具。Maven 包含了一个项目对象模型 (Project Object Model)&#xff0c;一组标准集合&#xff0c;一个项目生命周期管理系统(Project Lifecycle Management System)&#xff0c;一个依赖管理系统(Dependency Management System)&#x…

JavaScript中的单线程(single-threaded)和异步编程的关系

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 单线程&#xff08;Single-Threaded&#xff09;与异步编程的关系⭐ 单线程的含义⭐ 阻塞与非阻塞⭐ 异步编程⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启…

RK3588 点亮imx586摄像头

一.硬件原理图 mipi摄像头硬件确认点&#xff1a; 1.供电&#xff1a;5V&#xff0c;2.8V&#xff0c;1.2V&#xff0c;1.8V&#xff0c;reset脚&#xff08;硬拉3.3&#xff0c;上电的时候从低到高&#xff09;&#xff0c;pwron脚外接 3.3V。 2,时钟&#xff1a;MCLKOUT是2…

leetcode数组必刷题——二分查找、移除元素、有序数组的平方、长度最小的子数组、螺旋矩阵、螺旋矩阵 II

文章目录 二分查找移除元素有序数组的平方长度最小的子数组螺旋矩阵螺旋矩阵II 二分查找 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0…

Retentive Network: A Successor to Transformer for Large Language Models

论文链接&#xff1a; https://arxiv.org/pdf/2307.08621.pdf 代码链接&#xff1a; https://github.com/microsoft/unilm/tree/master/retnet 引言 transformer的问题就是计算成本太高 RetNet使“不可能三角形”成为可能&#xff0c;同时实现了训练并行性&#xff0c;良好…

VBA技术资料MF57:VBA_自动创建PowerPoint演示文稿

【分享成果&#xff0c;随喜正能量】会因为有情绪而烦闷&#xff0c;也因为没控制情绪而懊悔。莫道幽人一事无&#xff0c;闲中尽有静工夫。情绪就像水&#xff0c;宜疏不宜堵。学会控制情绪&#xff0c;不能把情绪看得过重&#xff0c;也不能一味遏制情绪的产生。倾听所有声音…

Linux 文件创建、查看

touch、cat、more命令 ①touch命令——创建文件 ②cat命令——查看文件内容全部显示 这是txt.txt文件内容 使用cat命令查看 ③more命令——查看文件内容支持翻页 在查看的过程中&#xff0c;通过空格翻页&#xff0c;通过q退出查看

Docker搭建ELK日志采集服务及Kibana可视化图表展示

架构 ES docker network create elkmkdir -p /opt/ELK/es/datachmod 777 /opt/ELK/esdocker run -d --name elasticsearch --net elk -p 9200:9200 -p 9300:9300 -e "discovery.typesingle-node" -v /opt/ELK/es/plugins:/usr/share/elasticsearch/plugins -v /opt/…

电商API的应用价值:淘宝1688京东API接口系列

API接口是一种软件应用程序&#xff0c;它充当两个不同软件应用程序之间的中介。它帮助不同的应用程序相互通信&#xff0c;共享数据&#xff0c;从而使用户能够完成不同的任务。API接口的用途非常广泛&#xff0c;下面是一些常见的用途&#xff1a; 数据共享&#xff1a;API接…

vue3-vant4-vite-pinia-axios-less学习日记

代码地址 GitHub&#xff1a;vue3-vant4-vite-pinia-axios-less 效果如图 1.首页为导航栏 2.绑定英雄页 3.注册页 4.英雄列表页 5.后面不截图了&#xff0c;没啥了 模块 1.vant4&#xff1a;按需引入组件样式文档 2.安装该vite-plugin-vue-setup-extend插件可以直接在…

基于SpringBoot的电影购票系统

基于SpringBootVue的电影购票系统、影视商城管理系统&#xff0c;前后端分离 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 【主要功能】 管理员&#xff1a;个人…

elasticsearch7-DSL查询与精准查询

个人名片&#xff1a; 博主&#xff1a;酒徒ᝰ. 个人简介&#xff1a;沉醉在酒中&#xff0c;借着一股酒劲&#xff0c;去拼搏一个未来。 本篇励志&#xff1a;三人行&#xff0c;必有我师焉。 本项目基于B站黑马程序员Java《SpringCloud微服务技术栈》&#xff0c;SpringCloud…

用一个RecyclerView实现二级评论

先上个效果图&#xff08;没有UI&#xff0c;将就看吧&#xff09;&#xff0c;写代码的整个过程花了4个小时左右&#xff0c;相比当初自己开发需求已经快了很多了哈。 给产品估个两天时间&#xff0c;摸一天半的鱼不过分吧&#xff08;手动斜眼&#xff09; 需求拆分 这种大家…

Spring Authorization Server入门 (十七) Vue项目使用授权码模式对接认证服务

Vue单页面项目使用授权码模式对接流程说明 以下流程摘抄自官网 在本例中为授权代码流程。 授权码流程的步骤如下&#xff1a; 客户端通过重定向到授权端点来发起 OAuth2 请求。 如果用户未通过身份验证&#xff0c;授权服务器将重定向到登录页面。 身份验证后&#xff0c;用…

Spring Bean循环依赖学习与探究

文章目录 原理学习源码溯源 本文参考&#xff1a; 画图带你彻底弄懂三级缓存和循环依赖的问题 Spring 三级缓存解决bean循环依赖&#xff0c;为何用三级缓存而非二级_笑矣乎的博客-CSDN博客 Spring为何需要三级缓存解决循环依赖&#xff0c;而不是二级缓存&#xff1f;_石杉…

如何用在线模版快速制作活动海报?

在时代的发展和信息传播的快速发展下&#xff0c;活动海报成为了宣传活动的重要方式之一。设计一张吸引眼球的活动海报&#xff0c;不仅能够有效传递信息&#xff0c;还能够吸引人们的注意力。那么&#xff0c;在这里我将教会大家如何设计活动海报&#xff0c;只需要三分钟&…

SAP MM学习笔记31 - 已割当供给元的购买依赖

上次学习了未割当供给元的购买依赖&#xff08;未分配供应商采购申请&#xff09;&#xff0c;咱们本章来学习一下 已割当供给元的购买依赖如何处理。 SAP MM学习笔记30 - 未割当供给元的购买依赖_东京老树根的博客-CSDN博客 如下图所示&#xff0c;利用 - 购买依赖割当一览&…