[数据结构笔记]二叉树初阶

news2024/9/23 3:13:42

基本知识

-节点的度:一个节点含有的子树的个数称为该节点的度;
-叶节点或终端节点:度为0的节点称为叶节点;
-非终端节点或分支节点:度不为0的节点;
-父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
-子节点:一个节点含有的子树的根节点称为该节点的子节点;
-兄弟节点:具有相同父节点的节点互称为兄弟节点;
-树的度:一棵树中,最大的节点的度称为树的度;
-节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
-树的高度或深度:树中节点的最大层次;
-森林:由m(m>0)棵互不相交的多颗树的集合称为森林;

树的高度默认从1开始,是为了便于将空树高度以0表示。与之相对的,数组下标从0开始是为了方便数组名代表首元素,且能直接使用数组名加减数字找到目标元素位置。

最优一般树结构(孩子兄弟表示法):
只用两个指针表示任意多个子节点

typedef int DataType;
struct Node{
	struct Node* firstChild;//指向当前节点的第一个孩子
	struct Node* nextBrother;//指向当前节点“右边”的兄弟
	DataType data;
};

二叉树

在这里插入图片描述
0.一棵二叉树是结点的一个有限集合,该集合由一个根节点加上两棵别称为左子树和右子树的二叉树组成。空树也是一种子树。
1.每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
2.二叉树的子树有左右之分,其子树的次序不能颠倒。

满二叉树与完全二叉树

在这里插入图片描述

满二叉树:
-每一个层的结点数都达到最大值的二叉树。
-层数为k且结点总数为(2^k) -1的二叉树是满二叉树。

完全二叉树:
-满二叉树是特殊的完全二叉树。
-对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一 一对应时称之为完全二叉树。
-完全二叉树的叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。

满二叉树必定是完全二叉树,而完全二叉树未必是满二叉树。

二叉树的性质

1.若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1)个结点.
2.若规定根节点的层数为1,则深度为h的二叉树的最大结点数是(2^h)-1.
3.对任何一棵二叉树, 如果度为0其叶结点个数为n0 , 度为2的分支结点个数为n2 ,则有n0=n2+1
4.若规定根节点的层数为1,则具有n个结点的满二叉树的深度为:
在这里插入图片描述
5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:
(1)若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
(2)若2i+1<n,左孩子序号:2i+1,2i+1>=n,否则无左子节点
(3)若2i+2<n,右孩子序号:2i+2,2i+2>=n,否则无右子节点

二叉树的存储结构

1.顺序存储
-使用数组来存储。一般使用数组只适合表示完全二叉树,因为若不是完全二叉树会有空间浪费。
-实际应用中只有堆(是堆结构,不是内存堆区)才会使用数组来存储。
-顺序存储的二叉树在物理上是一个数组,在逻辑上是一颗二叉树。

2.链式存储
-用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。
-通常的实现方式是链表中每个结点由三个域组成,数据域和左右指针域。左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。
-链式结构又分为二叉链和三叉链,暂且只讨论二叉链。

二叉树遍历思路

已知:二叉树每个节点可以看作一个根,每个根有两个子树。空子树也算子树,只不过空树没有子树。
在这里插入图片描述

递归实现:
1.前序遍历/先根遍历:根、左子树、右子树
在这里插入图片描述
在这里插入图片描述

2.中序遍历/中根遍历:左子树、根、右子树
在这里插入图片描述
3.后序遍历/后根遍历:左子树、右子树、根
在这里插入图片描述

非递归实现:
4.层序遍历:用队列实现,从第一层开始,每层从左到右,逐层遍历
在这里插入图片描述

二叉树相关代码实现

队列实现代码此处略过,详见相应博文
二叉树相关功能实现:

#include<stdio.h>
#include<stdlib.h>
#include"Queue.h"

typedef char BTDataType;

typedef struct BinaryTreeNode {
	BTDataType _data;
	struct BinaryTreeNode* _left;
	struct BinaryTreeNode* _right;
}BTNode;

BTNode* BuyBTNode(BTDataType x) {
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL) {
		perror("malloc failed");
		exit(-1);
	}
	node->_data = x;
	node->_left = node->_right = NULL;
	return node;
}

//前序遍历字符串构建二叉树,#代表空
BTNode* BinaryTreeCreate(BTDataType* str, int* pi) {
	//i传地址,以保证++的是同一个i
	//遇#返回空
	if (str[*pi] == '#') {
		(*pi)++;
		return NULL;
	}
	//非#创建新节点
	BTNode* root = (BTNode*)malloc(sizeof(BTNode));
	root->_data = str[(*pi)++];

	root->_left = BinaryTreeCreate(str, pi);
	root->_right = BinaryTreeCreate(str, pi);
	return root;
}

//前序遍历
void PrevOrder(BTNode* root) {
	//空树作为终止条件
	if (root == NULL) {
		printf("NULL ");
		return;//返回到发起本次调用的栈帧,也就是递归的上一层。
		//调用结束的栈帧会被销毁
	}
	//前序:根、左子树、右子树
		//若非空,则访问当前节点
	printf("%c ", root->_data);
	//先从左子树的根向下递归,然后才是右子树
	PrevOrder(root->_left);
	PrevOrder(root->_right);
	//从右子树的递归调用的栈帧返回本次调用的栈帧后,本次调用结束
	//返回到发起本次调用的栈帧,也就是递归的上一层。
	//调用结束的栈帧会被销毁
}

//中序遍历
void InOrder(BTNode* root) {
	if (root == NULL) {
		printf("NULL ");
		return;
	}
	//中序:左子树、根、右子树
	InOrder(root->_left);
	printf("%c ", root->_data);
	InOrder(root->_right);
}

//后序遍历
void PostOrder(BTNode* root) {
	if (root == NULL) {
		printf("NULL ");
		return;
	}
	//后序:左子树、右子树、根
	PostOrder(root->_left);
	PostOrder(root->_right);
	printf("%c ", root->_data);
}

//层序遍历-使用队列,一层带一层直至队列pop到空
void LevelOrder(BTNode* root) {
	Queue q;
	QueueInit(&q);
	if (root) {
		QueuePush(&q, root);//树根入队
	}
	while (!QueueEmpty(&q)) {
		BTNode* front = QueueFront(&q);//队头地址保存到front
		printf("%c ", front->_data);//打印队头
		QueuePop(&q);//队头出队
		//通过front判断原队头是否存在子节点,存在则将子节点依次入队排在后面
		if (front->_left) {
			QueuePush(&q, front->_left);
		}
		if (front->_right) {
			QueuePush(&q, front->_right);
		}
	}
	printf("\n");
	QueueDestroy(&q);
}

//二叉树销毁:使用后序遍历(最优)
void TreeDestroy(BTNode* root) {
	if (root == NULL) {
		return;
	}
	TreeDestroy(root->_left);
	TreeDestroy(root->_right);
	free(root);
	//这个free可以不用跟置空,因为是形参
	//由于没用二级指针,树指针的置空操作需要由调用者进行
}

//求二叉树节点个数
int TreeSize(BTNode* root) {
	return root == NULL ? 0 :
		TreeSize(root->_left) + TreeSize(root->_right) + 1;
}

//求二叉树叶节点个数
int TreeLeafSize(BTNode* root) {
	if (root == NULL) {
		return 0;
	}
	if (root->_left == NULL && root->_right == NULL) {
		return 1;
	}
	return TreeLeafSize(root->_left) + TreeLeafSize(root->_right);
}

//求树的高度/深度
int TreeHeight(BTNode* root) {
	if (root == NULL) {
		return 0;
	}
	int leftHeight = TreeHeight(root->_left);
	int rightHeight = TreeHeight(root->_right);
	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

//求第k层的节点个数,k>=1
int TreeKLevelSize(BTNode* root, BTDataType k) {
	if (root == NULL) {
		return 0;
	}
	if (k == 1) {
		return 1;
	}
	return TreeKLevelSize(root->_left, k - 1) + TreeKLevelSize(root->_right, k - 1);
}

//查找值为x的节点,这里的x为字符
BTNode* TreeFind(BTNode* root, BTDataType x) {
	if (root == NULL) {
		return NULL;
	}
	if (root->_data == x) {
		return root;
	}

	//每层递归的返回值都只会返回给上一层,要确保该值被上一层接收且能够继续向上传递
	struct BinaryTreeNode* ret1 = TreeFind(root->_left, x);
	if (ret1) {
		return ret1;
	}
	struct BinaryTreeNode* ret2 = TreeFind(root->_right, x);
	if (ret2) {
		return ret2;
	}
	return NULL;
}

//判断完全二叉树:使用层序遍历思路
//相比前面的层序遍历,空节点也入队,便能在空节点出队时看后续是否为全空队列来判断完全二叉树
//不能通过节点数量判断,因为最后一层的节点可能不连续
bool TreeComplete(BTNode* root) {
	Queue q;
	QueueInit(&q);
	if (root) {
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q)) {
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front == NULL) {
			break;//遇空则跳出,开始检查
		}
		else {//未遇空则继续层序遍历
			QueuePush(&q, front->_left);
			QueuePush(&q, front->_right);
		}
	}
	//遇空后检查队列中后续节点是否全空,全空则为完全二叉树
	while (!QueueEmpty(&q)) {
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front != NULL) {
			QueueDestroy(&q);
			return false;//空后有非空则该树不是完全二叉树
		}
	}
	QueueDestroy(&q);//即便到这一步队列后续必为空,也不省略destroy,
	//,这样能确保即便是带哨兵位的队列也不会内存泄漏,使得本函数不受队列的实现方式影响
	return true;
}

int main() {
	//手动创建节并链接以构建树
	BTNode* n1 = BuyBTNode('1');
	BTNode* n2 = BuyBTNode('2');
	BTNode* n3 = BuyBTNode('3');
	BTNode* n4 = BuyBTNode('4');
	BTNode* n5 = BuyBTNode('5');
	BTNode* n6 = BuyBTNode('6');
	BTNode* n7 = BuyBTNode('7');
	n1->_left = n2;
	n1->_right = n4;
	n2->_left = n3;
	n2->_right = n7;
	n4->_left = n5;
	n4->_right = n6;

	/*BTDataType str[100] = "123##7##45##6##";
	int i = 0;
	BTNode* n1 = BinaryTreeCreate(str, &i);*/

	PrevOrder(n1);
	printf("\n");

	InOrder(n1);
	printf("\n");

	PostOrder(n1);
	printf("\n");

	LevelOrder(n1);
	printf("\n");

	printf("TreeSize:%d\n", TreeSize(n1));
	printf("TreeLeafSize:%d\n", TreeLeafSize(n1));
	printf("TreeHeight:%d\n", TreeHeight(n1));
	printf("Tree2LevelSize:%d\n", TreeKLevelSize(n1, 2));
	printf("Tree3LevelSize:%d\n", TreeKLevelSize(n1, 3));
	printf("Tree4LevelSize:%d\n", TreeKLevelSize(n1, 4));
	printf("TreeFind '4':%d\n", (int)TreeFind(n1, '2'));
	printf("TreeComplete:%d\n", TreeComplete(n1));

	TreeDestroy(n1);
	n1 = NULL;

	return 0;
}

执行结果:
在这里插入图片描述

例题

单值二叉树

在这里插入图片描述

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */

//思路:
//递归判断每个子树的根与左右子节点是否相等,遇到空树直接返回
//先判断当前子树,再向下递归

bool isUnivalTree(struct TreeNode* root){
    if(root==NULL){
        return true;
    }
    if(root->left && root->left->val != root->val){
        return false;
    }
    if(root->right && root->right->val != root->val){
        return false;
    }
    return isUnivalTree(root->left) && isUnivalTree(root->right);
}

判断两树是否相同

在这里插入图片描述

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */

//思路:同时遍历并比较

bool isSameTree(struct TreeNode* p, struct TreeNode* q){
    //当前节点都为空则同
    if(p==NULL && q==NULL){
        return true;
    }
    //已判断当前节点并不都为空,故其中一个为空则异
    if(p==NULL || q==NULL){
        return false;
    }
    //至此,当前节点都不为空,遂判断值是否相异
    if(p->val != q->val){
        return false;
    }
    //同时递归遍历比较
    return isSameTree(p->left,q->left) 
        && isSameTree(p->right,q->right);
}

另一棵树的子树

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

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */

//思路:
//复用isSameTree函数来比较每一棵子树是否相同
//每个节点都可以看作一棵子树的根

bool isSameTree(struct TreeNode* p, struct TreeNode* q){
    if(p==NULL && q==NULL){
        return true;
    }
    if(p==NULL || q==NULL){
        return false;
    }
    if(p->val != q->val){
        return false;
    }
    return isSameTree(p->left,q->left) 
        && isSameTree(p->right,q->right);
}

bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
    if(root == NULL){
        return false;
    }
    if(isSameTree(root,subRoot)){
        return true;
    }
    return isSubtree(root->left,subRoot)
        || isSubtree(root->right,subRoot);
}

对称二叉树

在这里插入图片描述

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */

 //思路:将一侧子树翻转后与另一侧子树比对(只在效果上是翻转的)

 //用于比对的子函数
 bool _isSymmetric(struct TreeNode* root1,struct TreeNode* root2){
     if(root1 == NULL && root2 == NULL){
        return true;
     }
     if(root1 == NULL || root2 == NULL){
        return false;
     }
     if(root1->val != root2->val){
         return false;
     }
     return _isSymmetric(root1->left,root2->right)
        && _isSymmetric(root1->right,root2->left);
 }

bool isSymmetric(struct TreeNode* root){
    return !root || _isSymmetric(root->left,root->right);
}

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

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

相关文章

Mybatis 通过接口实现 sql 执行原理解析

使用过 mybatis 框架的小伙伴们都知道&#xff0c;mybatis 是个半 orm 框架&#xff0c;通过写 mapper 接口就能自动实现数据库的增删改查&#xff0c;但是对其中的原理一知半解&#xff0c;接下来就让我们深入框架的底层一探究竟1、环境搭建首先引入 mybatis 的依赖&#xff0…

Consul服务注册与发现

目录 一、Consul简介 &#xff08;一&#xff09;官网 &#xff08;二&#xff09;特点 二、安装并运行Consul &#xff08;一&#xff09;官网安装说明 &#xff08;二&#xff09;下载 &#xff08;三&#xff09;使用开发模式启动 三、服务提供者 四、服务消费者 …

怎么压缩pdf文件?选对方法其实很简单!

相信许多人在使用设备的时候都会面对这样一个问题&#xff0c;那就是设备内存不足。仿佛不管我们多么努力的节省空间&#xff0c;总是会到头来遇到储存空间不足得难题&#xff0c;尤其是一些比较大的pdf文件&#xff0c;特别占据我们的设备内存&#xff0c;那么你知道怎么压缩p…

0128 Web API基本认知

作用使用JS去操作html和浏览器分类DOM&#xff08;文档对象模型&#xff09;BOM&#xff08;浏览器对象模型&#xff09;DOM是什么&#xff1a;Document Object Model-----文档对象模型&#xff0c;用来呈现以及与任意HTML或XML文档交互的API&#xff0c;浏览器提供的一套专门用…

【Linux】一文掌握Linux权限

环境&#xff1a;centos7&#xff0c;腾讯云服务器Linux文章都放在了专栏&#xff1a;【Linux】欢迎支持订阅&#x1f339;前言权限 一词相信大家都不陌生&#xff0c;与我们的生活密切相关。小区里的门禁制度、公司里的管理制度、学校里的校规规定、甚至是社交平台上的一些设置…

python 手机相机传感器信息计算

传感器信息计算 输入传感器尺寸以上已红米12pro为例 输入传感器尺寸 1/1.4英寸 0.7142857 输入像素2亿 200000000 得到以下结果 和宣传中的传感器信息一致 附源码 import sympyclass CMosInfo(object):"""传感器信息计算"""def __init__(…

C 程序设计教程(19)—— 数组和指针(二):字符数组与字符串

C 程序设计教程&#xff08;19&#xff09;—— 数组和指针&#xff08;二&#xff09;&#xff1a;字符数组与字符串 该专栏主要介绍 C 语言的基本语法&#xff0c;作为《程序设计语言》课程的课件与参考资料&#xff0c;用于《程序设计语言》课程的教学&#xff0c;供入门级用…

word实用操作:几个关于录入和排版的小妙招

对于职场人来说&#xff0c;工作中使用Word早已成了习惯。而如何提高Word的技术&#xff0c;那简直是职场人一生都需要研究探索的课题。因此&#xff0c;今天小编将为大家分享几个实用的Word小技巧&#xff0c;这些技巧貌似不起眼&#xff0c;但是学会后&#xff0c;可以大大提…

如何查linux服务器的带宽占用?哪些进程占用带宽?

前言操作系统&#xff1a; Linux操作环境&#xff1a; Centos7 / ubuntulinux查看服务器带宽具体方法   一、使用speedtest-cli命令查看下载和上传最大流量值因为命令是python的&#xff0c;所以需要先下载一个python&#xff0c;用pip下载次命令&#xff1b;123yum -y insta…

七个 Vue 项目用得上的 JavaScript 库分享

文章目录前言一、vueuse二、vue-js-modal三、vue-wait四、good-table五、vue-notification六、tree select七、egjs-infinite grid总结前言 借助开源库加速 Vue 项目的开发进度是现代前端开发比较常见的方式&#xff0c;平常收集一些 JavaScript 库介绍&#xff0c;在遇到需要的…

上古神兵,先天至宝,Win11平台安装和配置NeoVim0.8.2编辑器搭建Python3开发环境(2023最新攻略)

毫无疑问&#xff0c;我们生活在编辑器的最好年代&#xff0c;Vim是仅在Vi之下的神级编辑器&#xff0c;而脱胎于Vim的NeoVim则是这个时代最好的编辑器&#xff0c;没有之一。异步支持、更好的内存管理、更快的渲染速度、更多的编辑命令&#xff0c;是大神Thiago de Arruda对开…

第九层(5):STL之stack

文章目录前情回顾stack概念stack容器需要注意的地方stack类内的构造函数stack类内的赋值操作stack类内的插入stack类内的删除stack类内的访问stack类内的大小操作下一座石碑&#x1f389;welcome&#x1f389; ✒️博主介绍&#xff1a;一名大一的智能制造专业学生&#xff0c;…

Knowledge-based-BERT(三)

多种预训练任务解决NLP处理SMILES的多种弊端&#xff0c;代码&#xff1a;Knowledge-based-BERT&#xff0c;原文&#xff1a;Knowledge-based BERT: a method to extract molecular features like computational chemists&#xff0c;代码解析继续downstream_task。模型框架如…

HTML中的div和span标签

<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>HTML中的div和span标签</title> </head> <body> <!-- 1、div和span是什么&#xff1f;有什么用…

【linux】文件操作(IO)详述

文件操作一、引入二、系统调用接口2.1 open与close2.2 write2.3 read三、文件描述符3.1 分配规则四、重定向4.1 输出重定向4.2 追加重定向4.3 输入重定向4.4 独立性五、缓冲区5.1 缓冲区刷新策略5.2 缓冲区位置5.3 现象解释六、文件系统6.1 文件系统分区6.1.1 分区图6.1.2 介绍…

实时推荐业务介绍 grpc接口对接

5.1 实时推荐业务介绍 学习目标 目标 无应用 无 5.1.1 实时推荐逻辑 逻辑流程 1、后端发送推荐请求&#xff0c;实时推荐系统拿到请求参数 grpc对接2、根据用户进行ABTest分流 ABTest实验中心&#xff0c;用于进行分流任务&#xff0c;方便测试调整不同的模型上线3、推荐中心…

k8s之部署有状态应用

写在前面 本文一起看下k8s对于有状态应用部署提供的解决方案。 1&#xff1a;有状态应用和无状态应用 如果是一个应用每次重启时依赖环境都能和第一次启动时的完全一致&#xff0c;则就可以称这类应用是无状态应用用&#xff0c;反之&#xff0c;就是有状态应用&#xff0c;如…

自动写代码的AI工具,已经支持 VsCode 插件安装使用

自动写代码的AI工具&#xff0c;已经支持 VsCode 插件安装使用&#xff0c;它的功能并不是「代码补全」&#xff0c;而是「代码生成」。 之前有个比较火的 GitHub Copilot&#xff0c;但是这是商业产品&#xff0c;并且没有开源&#xff0c;现在又被告了。 GitHub Copilot 面…

SQLSERVER 事务日志的 LSN 到底是什么?

一&#xff1a;背景 1. 讲故事 大家都知道数据库应用程序 它天生需要围绕着数据文件打转&#xff0c;诸如包含数据的 .mdf&#xff0c;事务日志的 .ldf&#xff0c;很多时候深入了解这两类文件的合成原理&#xff0c;差不多对数据库就能理解一半了&#xff0c;关于 .mdf 的合…

代码随想录--二叉树章节总结 Part II

代码随想录–二叉树章节总结 Part II 1.Leetcode222 求完全二叉树结点的个数 给你一棵 完全二叉树 的根节点 root &#xff0c;求出该树的节点个数。 完全二叉树 的定义如下&#xff1a;在完全二叉树中&#xff0c;除了最底层节点可能没填满外&#xff0c;其余每层节点数都达…