首页 > 其他分享 >【数据结构】超详解二叉树

【数据结构】超详解二叉树

时间:2024-07-20 14:54:49浏览次数:12  
标签:结点 遍历 BTNode 详解 二叉树 数据结构 root 节点

1、树的概念及结构

堆与树的结构类似

堆的概念及代码实现-CSDN博客

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的
  • 有一个特殊的结点,称为根结点,根结点没有前驱结点
  • 除根结点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
  • 因此,树是递归定义的。

1.1树与非树?

子树是不相交的

除根结点外,每个节点有且只有一个父节点

一棵N个节点的树有N-1个边

1.2树的相关概念 

 

  • 结点的度:一个结点含有的子树的个数称为该结点的度; 如上图:A的为6
  • 叶结点或终端结点:度为0的结点称为叶结点; 如上图:B、C、H、I...等结点为叶结点
  • 非终端结点或分支结点:度不为0的结点; 如上图:D、E、F、G...等结点为分支结点
  • 双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点
  • 孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:B是A的孩子结点
  • 兄弟结点:具有相同父结点的结点互称为兄弟结点; 如上图:B、C是兄弟结点
  • 树的度:一棵树中,最大的结点的度称为树的度; 如上图:树的度为6
  • 结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推;
  • 树的高度或深度:树中结点的最大层次; 如上图:树的高度为4
  • 堂兄弟结点:双亲在同一层的结点互为堂兄弟;如上图:H、I互为兄弟结点
  • 结点的祖先:从根到该结点所经分支上的所有结点;如上图:A是所有结点的祖先
  • 子孙:以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙
  • 森林:由m(m>0)棵互不相交的树的集合称为森林;

1.3树的表示

typedef int DataType;
struct Node
{
 struct Node* firstChild1; // 第一个孩子结点
 struct Node* pNextBrother; // 指向其下一个兄弟结点
 DataType data; // 结点中的数据域
};

2、二叉树的概念及结构

一棵二叉树是结点的一个有限集合,该集合:    1. 或者为空    2. 由一个根结点加上两棵别称为左子树和右子树的二叉树组成

从上图可以看出: 1. 二叉树不存在度大于2的结点 2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
数据结构中的二叉树只存在以下几种情况

2.1特殊的二叉树

1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是2^k-1,则它就是满二叉树。 2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对 应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

2.2二叉树的存储方式

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

1. 顺序存储

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树

2. 链式存储 二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,后面课程学到高阶数据结构如红黑树等会用到三叉链。

typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
 struct BinTreeNode* left; // 指向当前结点左孩子
 struct BinTreeNode* right; // 指向当前结点右孩子
 BTDataType data; // 当前结点值域
}
// 三叉链
struct BinaryTreeNode
{
 struct BinTreeNode* parent; // 指向当前结点的双亲
 struct BinTreeNode* left; // 指向当前结点左孩子
 struct BinTreeNode* right; // 指向当前结点右孩子
 BTDataType data; // 当前结点值域
};

2.3二叉树的性质

  1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1) 个结点.
  2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h- 1.
  3. 对任何一棵二叉树, 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为 n2,则有n0=n2+1
  4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h=Log2(n+1). (ps:Log2(n+1)是log以2为底,n+1为对数)
  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否则无右孩子

 3、二叉树链式的实现

对于二叉树,我们将要完成以下作用

typedef char BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* Create_Tree();
void Preorder_Tree(BTNode* root);
// 二叉树销毁
void BinaryTreeDestory(BTNode* root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root);

二叉树的链式结构

typedef int BTDataType;
typedef struct BinaryTreeNode
{
 BTDataType _data;
 struct BinaryTreeNode* _left;
 struct BinaryTreeNode* _right;
}BTNode;
学习二叉树结构,最简单的方式就是遍历。所谓 二叉树遍历 (Traversal) 是按照某种特定的规则,依次对二叉 树中的结点进行相应的操作,并且每个结点只操作一次 。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。 按照规则,二叉树的遍历有: 前序 / 中序 / 后序的递归结构遍历
1. 前序遍历 (Preorder Traversal 亦称先序遍历 )—— 访问根结点的操作发生在遍历其左右子树之前。 2. 中序遍历 (Inorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之中(间)。 3. 后序遍历 (Postorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之后。
由于被访问的结点必是某子树的根, 所以 N(Node )、 L(Left subtree )和 R(Right subtree )又可解释为 根、根的左子树和根的右子树 。 NLR 、 LNR 和 LRN 分别又称为先根遍历、中根遍历和后根遍历。

前序遍历构建二叉树

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
// 根 左子树 右子树
// BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
BTNode* Create_Tree()
{
	BTNode* root;

	char ch;
	scanf("%c", &ch);//通过输入的ch是否为特殊符号来判断该节点是否有孩子节点

	if (ch == '#') {	//不存在孩子节点
		return NULL;
	}
	else {
		root = (BTNode*)malloc(sizeof(BTNode));
		if (NULL == root) {
			printf("创建失败\n");
			return NULL;
		}

		root->data = ch;
		root->left = Create_Tree();//存在左孩子节点,递归调用本函数,使得左孩子节点先被赋值
		root->right = Create_Tree();
		return root;
	}
}

前序、中序、后序多使用递归完成

前序遍历 

void Preorder_Tree(BTNode* root)
{
	if (NULL == root) {
		return;
	}

	printf("%c ", root->data);//输出当前节点的数据
	Preorder_Tree(root->left);//将子节点作为下一个根节点遍历左孩子数
	Preorder_Tree(root->right);//将子节点作为下一个根节点遍历左孩子数
}

中序遍历 

// 二叉树中序遍历
// 左子树 根 右子树
void BinaryTreeInOrder(BTNode* root) {
	//递归中序遍历 
	if (root != NULL)
	{
		BinaryTreeInOrder(root->left);
		printf("%c ", root->data);
		BinaryTreeInOrder(root->right);
	}
}

后序遍历

// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root) {
	if (root != NULL) {
		BinaryTreeInOrder(root->left);
		BinaryTreeInOrder(root->right);
		printf("%c ", root->data);
	}
}

二叉树的销毁

// 二叉树销毁
void BinaryTreeDestory(BTNode* root) {
	if (root == NULL) {
		return;
	}
	BinaryTreeDestory(root->left);
	BinaryTreeDestory(root->right);
	free(root);
}

 二叉树节点个数 

思考:对于以下size++,应如何解决

1、传size的指针

2、全局变量size

int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	else
		++size;

	BinaryTreeSize(root->left);
	BinaryTreeSize(root->right);

	return size;
}

二叉树叶子节点个数 

叶子节点:表明左右节点为NULL

int BinaryTreeLeafSize(BTNode* root) {
	if (root == NULL) {
		return 0;
	}
	if (root->left == NULL && root->right == NULL) {
		return 1;
	}
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

二叉树第k层节点个数

与上文中二叉树的节点个数处理方法类似(这里采用传址)

int BinaryTreeLevelKSize(BTNode* root, int k) {
	if (k == 0) {
		return 1;
	}
	else {
		return 0;
	}
	return 	BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

二叉树查找值为x的节点

二叉树查找值为x的节点需要处理以下几种情况

1、没有找到:

递归完,但没有值与之匹配

2、递归出去,找到了,如何使找到的节点不丢失

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x) {
	if (root == NULL)
		return NULL;

	if (root->data == x)
		return root;
//先找左子树
	BTNode* ret1 = BinaryTreeFind(root->left, x);
//使找到后,不需要再次进入右子树
	if (ret1)
		return ret1;

	BTNode* ret2 = BinaryTreeFind(root->right, x);
	if (ret2)
		return ret2;
	//使找到的树节点多函数返回
	return NULL;
}

层序遍历

与二叉树第k层节点个数类似,这里就不说明了

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

完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对 应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root) {
	if (root == NULL) {
		return;
	}
	if ((root->left != NULL && root->right == NULL) || (root->left == NULL && root->right != NULL))
//深度要相同
	{
		return false;
	}
	bool ret = BinaryTreeComplete(root->left);
	bool rec = BinaryTreeComplete(root->right);
	if (rec == false || ret == false) {
		return false;
	}
}

标签:结点,遍历,BTNode,详解,二叉树,数据结构,root,节点
From: https://blog.csdn.net/Asuku_/article/details/140504220

相关文章

  • 二叉树的种类
    二叉树:二叉树是每个节点最多有两个子树的树结构。完全二叉树:除最后一层外,每一层上的节点数量均达到了最大值,在最后一层上只缺少右边的若干节点。满二叉树:除最后一层无任何子节点外,每一层上的所有节点都有两个子节点的二叉树。二叉搜索树(二叉排序树、二叉查找树):左子树<根节点<右......
  • 数据结构-队列、栈
    功能受限的表结构一、栈和队列介绍栈和队列是两种重要的线性结构,从数据结构角度,他们都是线性表,特殊点在于它们的操作被限制,也就是所谓的功能受限,统称功能受限的线性表从数据类型角度,它们也可以是看成处理、管理数据的一种规则二、栈结构栈(stack)是限定在表尾进行数据的插......
  • C语言指针详解(进阶)
    二、指针的进阶    本章重点:            1.字符指针        2.数组指针        3.指针数组        4.数组传参和指针传参        5.函数指针        6.函数指针数组    ......
  • 【数据结构】ArrayList与顺序表
    目录1.前言2.顺序表2.1定义一个IList接口2.2实现接口功能2.3自定义异常类2.4主程序代码3.ArrayList3.1ArrayList简介3.2ArrayList的构造3.3ArrayList常见操作3.4ArrayList的遍历 4.ArrayList的具体使用4.1简单的洗牌算法4.2杨辉三角  5.总结1.前言通过上......
  • 《数据结构》--顺序表
    C语言语法基础到数据结构与算法,前面已经掌握并具备了扎实的C语言基础,为什么要学习数据结构课程?--我们学完本章就可以实践一个:通讯录项目简单了解过后,通讯录具备增加、删除、修改、查找联系人等操作。要想实现通讯录项目必须有两个技术关键:(1)C语言语法基础(2)数据结构之顺序表/......
  • 数据结构(00)
    1.序:`数据结构`将与`菜鸟的Leetcode之路`不定时更新,也是一个系列的内容,将会包含许多的数据的逻辑结构,物理结构,数据运算。(具体怎么说,我也不太明白,我的理解是:对于不同类型数据,进行不同的排序和存储,通过指针和数组,方便后续算法对其`增加,删除,修改,查询`。)呃,正如英雄哥所言:数据结构......
  • 山东大学数据结构与算法实验8散列表(线性开型寻址/链表散列)
    A : 线性开型寻址题目描述要求使用线性开型寻址实现描述给定散列函数的除数D和操作数m,输出每次操作后的状态。有以下三种操作:插入x,若散列表已存在x,输出“Existed”,否则插入x到散列表中,输出所在的下标。查询x,若散列表不含有x,输出“-1”,否则输出x对应下标。......
  • 数据结构-线性表、链表
    一、线性表介绍1、线性结构​ 在数据元素存在非空有限集中:存在唯一的一个被称为“第一个”的数据元素存在唯一的一个被称为“最后一个”的数据元素除了第一个外,集合中每个数据元素都只有一个前趋元素除了最后一个外,集合中每个数据元素都只有一个后继元素2、线性表线性表......
  • Linux系统安装的详细步骤详解
    在VM虚拟机上安装Linux系统全过程,闭眼跟着走就行!!!1、准备好VMwareWorestation虚拟机软件和Linux系统的映像文件2、点击创建新的虚拟机3、在新建虚拟机向导中,选择典型安装模式。典型安装模式可以通过几个简单的步骤快速安装虚拟机,更方便操作。点击下一步。4、在安装来源......
  • PYTHON学习笔记(六、python数据结构--字典)
    (3)dict字典字典数据类型的含义是:根据一个信息查找另一个信息的方式构成了“键值对”,它表示索引用的键和对应的值构成对应的关系。1、字典的创建方式1)使用{ }直接创建字典使用{ }创建字典的语法结构如下:d={key1:value1,key2:value2......}例如:#使用{}创建字典d=......