少年恃险若平地
独倚长剑凌清秋
🔥个人专栏
期待小伙伴们的支持与关注!!!
目录

我们数据结构的前几章都是线性结构,而我们今天来学习非线性结构的数形结构--树
那什么是树形结构呢?
如图所示:根在下,叶朝上 的就是我们生活中的树
树的定义与判定
树是一种 非线性 的数据结构,它是由 n ( n>=0 )个有限结点组成一个具有层次关系的集合
把它叫做树是因 为它看起来像一棵倒挂的树,也就是说它是 根朝上,而叶朝下 的树的定义
|-------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <1>有且仅有一个特殊的结点,称为根结点,根节点没有前驱结点 |
| <2>除根节点外,**其余结点被分成M(M>0)个互不相交的集合:T1、T2、... ...、**Tm 其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树 每棵子树的根结点有且只有一个前驱,可以有零个或多个后继 |
| <3>树是递归定义的 |树的判定
|------------------------------------|
| <1>树形结构中,子树之间不能有交集,否则就不是树形结构 |
| <2>除了根节点以外,每个节点有且只有一个父节点 |
| <3>一颗N节点的数有N-1条边 |像以下的结构就是树形结构
像以下 子树之间有交集 的结构就 不能 叫做树形结构
树的相关概念

|-------------------------------------------------------------|
| 节点的度 : 一个节点含有的子树的个数称为该节点的度 ; 如上图: 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)棵互不相交的树的集合称为森林 |
结点的度
结点拥有的子树数目称为结点的 度
结点层次从 根开始定义起,根为第一层,根的孩子为第二层,以此类推
树的深度树中结点的最大层次数称为树的深度或高度
树的运用
以下是文件系统中目录的树的运用

树的表示
树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了, 既然保存值域,也要保存结点和结点之间 的关系 ,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的 孩子兄弟表示法
cpptypedef char BTDataType; typedef struct BinaryTreeNode { struct BinaryTreeNode* left; // 第一个孩子结点 struct BinaryTreeNode* right; // 指向其下一个兄弟结点 BTDataType data; // 结点中的数据域 }BTNode;
二叉树的概念及结构
二叉树的概念
二叉树 (Binary Tree):是一个n(n>=0)个节点所构成的集合
该集合分为空树(n = 0),或者非空树
对于非空树:
|----------------------------------------------------|
| <1>有且仅有一个 根节点 |
| <2>由一个 根节点 加上两棵 左子树 和 右子树 (别称)的二叉树组成 |二叉树与树一样具有 递归 性质,二叉树的特性主要有以下两点:
<1>二叉树 不存在度大于2的结点
<2>二叉树的子树 有左右之分,次序不能颠倒 ,因此二叉树是 有序树二叉树的结构
二叉树的五种基本形式:
特殊的二叉树
满二叉树
<1>满二叉树:一个二叉树,每层的结点数都达到最大值,则这个二叉树就是满二叉树
假设一颗满二叉树的高度为h
则总节点的个数:
N =
h =
|-------------------------------------|
| 每一个层的结点数都达到最大值 |
| 如果一个二叉树的层数为K ,且结点总数是 2^k-1 |完全二叉树
<2>**完全二叉树:**完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为 K的,有 n 个结点的二叉树,当且仅当其每一个结点都与深度为 K 的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树
假设一颗完全二叉树的高度为h,高度为h的结点个数为x
则总节点的个数:
N =
h =
|------------------------------|
| 假设树的高度是h,前 h-1 层是 满的 |
| 最后一行不满,但 从左往右是连续的 |如上图所示就 非 完全二叉树:深度为 K 的节点中,从首结点到末结点中有 " 缺口 "
二叉树的性质
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <1>若规定根节点的层数为 1 ,则一棵非空二叉树的 第i层上最多有个结点 |
| <2>若规定根节点的层数为 1 ,则 深度为 h 的二叉树的最大结点数是|
| <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否则无右孩子** |
二叉树的存储结构
二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构
顺序存储
顺序结构存储就是使用 数组来存储 ,一般使用数组 只适合表示完全二叉树 ,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储。 二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树链式存储
二叉树的链式存储结构是指,用 链表 来表示一棵二叉树,即用 链来指示元素的逻辑关系 。 通常的方法是链表中每个结点由 三个域 组成, 数据域和左右指针域 , 左右指针 分别用来给出该结点 左孩子和右孩子所在的链结点的存储地址 。链式结构又分为 二叉链和三叉链
二叉树
cpptypedef char BTDataType; typedef struct BinaryTreeNode { struct BinaryTreeNode* left; //指向当前节点左孩子 struct BinaryTreeNode* right; //指向当前节点右孩子 BTDataType data; //节点中的数据域 }BTNode;
三叉树
cpptypedef char BTDataType; typedef struct BinaryTreeNode { struct BinaryTreeNode* Parent; //指向当前节点的双亲 struct BinaryTreeNode* left; //指向当前节点左孩子 struct BinaryTreeNode* right; //指向当前节点右孩子 BTDataType data; //节点中的数据域 }BTNode;
二叉树的遍历
二叉树遍历 (Traversal): 按照某种特定的规则,依次对二叉 树中的节点进行相应的操作,并且每个节点只操作一次 。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础
按照规则,二叉树的遍历有: 前序 / 中序 / 后序的递归结构遍历
<1> 前序遍历 ------ 访问根结点的操作发生在遍历其 左右子树之前
<2>中序遍历 ------ 访问根结点的操作发生在遍历其 左右子树之中
<3>后序遍历 ------ 访问根结点的操作发生在遍历其 左右子树之后
由于被访问的结点必是某子树的根
所以 N(Node )、 L(Left subtree )和 R(Right subtree)
又可解释为 根、根的左子树和根的右子树
NLR 、 LNR 和 LRN 分别又称为先根遍历、中根遍历和后根遍历二叉树结构定义
cpptypedef char BTDataType; typedef struct BinaryTreeNode { struct BinaryTreeNode* left; // 第一个孩子结点 struct BinaryTreeNode* right; // 指向其下一个兄弟结点 BTDataType data; // 结点中的数据域 }BTNode;


二叉树前序遍历
因为有些节点为空,我们可以选择打印和不打印,为了页面美观我们这里就不打印了
cppvoid PrevOrder(BTNode* root) { if (root == NULL) { return; } printf("%c ", root->data); PrevOrder(root->left); PrevOrder(root->right); }
cppint main() { BTNode* A = (BTNode*)malloc(sizeof(BTNode)); A->data = 'A'; A->left = NULL; A->right = NULL; BTNode* B = (BTNode*)malloc(sizeof(BTNode)); B->data = 'B'; B->left = NULL; B->right = NULL; BTNode* C = (BTNode*)malloc(sizeof(BTNode)); C->data = 'C'; C->left = NULL; C->right = NULL; BTNode* D = (BTNode*)malloc(sizeof(BTNode)); D->data = 'D'; D->left = NULL; D->right = NULL; BTNode* E = (BTNode*)malloc(sizeof(BTNode)); E->data = 'E'; E->left = NULL; E->right = NULL; BTNode* F = (BTNode*)malloc(sizeof(BTNode)); F->data = 'F'; F->left = NULL; F->right = NULL; BTNode* G = (BTNode*)malloc(sizeof(BTNode)); G->data = 'G'; G->left = NULL; G->right = NULL; BTNode* H = (BTNode*)malloc(sizeof(BTNode)); H->data = 'H'; H->left = NULL; H->right = NULL; BTNode* I = (BTNode*)malloc(sizeof(BTNode)); I->data = 'I'; I->left = NULL; I->right = NULL; BTNode* J = (BTNode*)malloc(sizeof(BTNode)); J->data = 'J'; J->left = NULL; J->right = NULL; BTNode* K = (BTNode*)malloc(sizeof(BTNode)); K->data = 'K'; K->left = NULL; K->right = NULL; A->left = B; A->right = C; B->left = D; B->right = E; D->left = H; D->right = I; C->left = F; C->right = G; F->left = K; E->right = J; PrevOrder(A); printf("\n"); system("pause"); return 0; }
以上我们插入树节点数据
代码测试
前序遍历结果:A B D H I E J C F K G二叉树中序遍历
cppvoid InOrder(BTNode* root) { if (root == NULL) { return; } InOrder(root->left); printf("%c ", root->data); InOrder(root->right); }
代码测试
中序遍历结果:H D I B E J A F K C G
二叉树后序遍历
cppvoid PostOrder(BTNode* root) { if (root == NULL) { return; } PostOrder(root->left); PostOrder(root->right); printf("%c ", root->data); }
代码测试
后序遍历结果:H I D J E B K F G C A
二叉树遍历口诀
二叉树的层序遍历
层序遍历太简单了,就是按照一层一层的顺序,从左到右写下来就行了
层序遍历结果:A B C D E F G H I J K
层序遍历我们要用到队列,所以我们这里要包一下队列相关的文件
cppvoid Levelorder(BTNode* root) { Queue q; QueueInit(&q); //树为空,直接返回 if (root == NULL) { return; } QueuePush(&q, root); //先将根节点入队 while (!QueueEmpty(&q)) { BTNode* front = QueueFront(&q); //出队保存队头并访问 QueuePop(&q); printf("%c ", front->data); if (front->left) //将出队结点的左子树根入队 { QueuePush(&q, front->left); } if (front->right) //将出队结点的右子树根入队 { QueuePush(&q, front->right); } } printf("\n"); QueueDestory(&q); //销毁队列 }
二叉树结点的个数
结点的个数的算法 :左子树的结点加上右子树的结点,最后再加上根结点
cppint TreeSize(BTNode* root) { return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1; }
代码测试
二叉树叶子结点的个数
叶子结点的特征 :左右子树为空,我们可以通过递归的方法遍历每一颗子树
cppint TreeLeafSizee(BTNode* root) { if (root == NULL) return 0; //左右为空 if (root->left == NULL && root->right == NULL) return 1; return TreeLeafSizee(root->left) + TreeLeafSizee(root->right); }
我们还是以这个树为例
我们发现有H、I、J、K、G五个左右子树为空,所以叶子结点的个数为5
代码测试
二叉树的高度
树的高度的定义:从 根开始定义 起,根为第一层,根的孩子为第二层,以此类推
cppint TreeHeight(BTNode* root) { if (root == NULL) return 0; int left = TreeHeight(root->left); int right = TreeHeight(root->right); return (left > right ? left : right) + 1; }
代码测试
二叉树第k层节点个数
cppint BinaryTreeLevelKSize(BTNode* root, int k) { if (root == NULL) { return 0; } if (k == 1) { return 1; } return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1); }
代码测试
如图所示:4层的结点为4,2层的节点为2
二叉树查找值为x的节点
先对左子树递归查找,如果未找到x,则返回NULL
如果找到x,便返回x所在节点
根据返回值判断是否需要进行右递归查找操作
cppBTNode* BinaryTreeFind(BTNode* root, BTDataType x) { if (root == NULL) return NULL; if (root->data == x) return root; if (BinaryTreeFind(root->left, x)) return BinaryTreeFind(root->left, x); else return BinaryTreeFind(root->right, x); }
总结:
二叉树主要涉及的算法有 递归 和 分治
递归需要画图理解其真谛