树是n(n≥0)个结点的有限集合,n=0时,称为空树,这是一种特殊情况。在任意一棵非空树中应满足:
其实在树的定义中我们不难看出,树是一种递归的数据结构,也是分层结构。所以在后面我们可以进行对树进行递归遍历,层次遍历等操作。需要注意的是对于树而言,除了根节点之外,任何一个节点有且仅有一个前驱
。
大佬可以直接跳过,看后面的线索二叉树
祖先节点
度为m的树,至少有一个节点的度是m
m叉树,每个节点最多m个孩子
度为m的树,第i
层至多有
m
i
−
1
m^{i-1}
mi−1个节点
高度为h,度为m的树 至少有h+m-1个结点
高度为h的m叉树至多有
(
m
h
−
1
)
/
(
m
−
1
)
(m^h-1)/(m-1)
(mh−1)/(m−1)个节点,至少有h个节点。
指定结点数的最小高度分析
度为m,具有n个节点的树的最小高度h为
⌈
l
o
g
m
(
n
(
m
−
1
)
+
1
)
⌉
\lceil log_m(n(m-1) + 1) \rceil
⌈logm(n(m−1)+1)⌉向上取整,推导:
(
m
(
h
−
1
)
−
1
)
/
(
m
−
1
)
<
n
⩽
(
m
h
)
/
(
m
−
1
)
(m^(h-1^)-1)/(m-1) < n ⩽ (m^h)/(m-1)
(m(h−1)−1)/(m−1)<n⩽(mh)/(m−1)
以上内容出题时需注意:
设n0, n1, n2, … nm表示度为m的节点的个数
!!! 二叉树是有序树
属于树的一种特例,也采用递归的形式定义。
二叉树是n(n≥0)个结点的有限集合:
特点
:①每个结点至多只有两棵子树②左右子树不能颠倒(二叉树是有序树)注意区别:度为2的有序树二叉排序树:一棵二叉树或者是空二叉树,或者是具有如下性质的二叉树:
二叉排序树可用于元素的排序、搜索
平衡二叉树: 树上的任一节点的左子树和右子树的深度之差不超过1
正则二叉树:树中只有度为0或2的节点
二叉树的常考性质:
常见考点1:设非空二叉树中度为0、1和2的结点个数分别为no、n1和n2,则n0=n2+1(叶子结点比二分支结点多一个)
假设树中结点总数为n,则
①n=no+n1+n2
②n=n1+2n2+1
树的结点数=总度数+1 ==============>>>>no=n2+1
常见考点2:二叉树第i层至多有 2 ( i − 1 ) 2^(i-1^) 2(i−1)个结点(i≥1) m叉树第i层至多有 m ( i − 1 ) m^(i-1^) m(i−1)个结点(i≥1)
常见考点3:高度为h的二叉树至多有 2 h − 1 2^h-1 2h−1个结点(满二叉树)高度为h的m叉树至多有 ( m h − 1 ) / ( m − 1 ) (m^h-1)/ (m-1) (mh−1)/(m−1)个结点。 等比数列求和
完全二叉树的性质:
满二叉树和完全二叉树采用顺序存储比较合适
#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 100 // 定义二叉树能存储的最大节点数
// 定义二叉树结构体
typedef struct {
char data[MAXSIZE]; // 存储节点数据的数组
int count; // 记录二叉树节点个数
} BinaryTree;
// 初始化二叉树
void initBinaryTree(BinaryTree *tree) {
tree->count = 0;
}
// 插入节点
void insertNode(BinaryTree *tree, char data) {
if (tree->count < MAXSIZE) {
tree->data[tree->count] = data;
tree->count++;
} else {
printf("二叉树已满,无法插入新节点\n");
}
}
// 获取左孩子节点
char getLeftChild(BinaryTree *tree, int index) {
int leftChildIndex = 2 * index + 1;
if (leftChildIndex >= tree->count) {
return '\0';
} else {
return tree->data[leftChildIndex];
}
}
// 获取右孩子节点
char getRightChild(BinaryTree *tree, int index) {
int rightChildIndex = 2 * index + 2;
if (rightChildIndex >= tree->count) {
return '\0';
} else {
return tree->data[rightChildIndex];
}
}
int main() {
BinaryTree binaryTree;
initBinaryTree(&binaryTree);
insertNode(&binaryTree, 'A');
insertNode(&binaryTree, 'B');
insertNode(&binaryTree, 'C');
insertNode(&binaryTree, 'D');
insertNode(&binaryTree, 'E');
insertNode(&binaryTree, 'F');
printf("节点A的左孩子节点是:%c\n", getLeftChild(&binaryTree, 0));
printf("节点A的右孩子节点是:%c\n", getRightChild(&binaryTree, 0));
printf("节点B的左孩子节点是:%c\n", getLeftChild(&binaryTree, 1));
printf("节点B的右孩子节点是:%c\n", getRightChild(&binaryTree, 1));
printf("节点C的左孩子节点是:%c\n", getLeftChild(&binaryTree, 2));
printf("节点C的右孩子节点是:%c\n", getRightChild(&binaryTree, 2));
return 0;
}
由于顺序存储空间利用率低,因此一般都用链式存储,就会造成n+1个空链域,可构成线索二叉树
遍历:按照某种次序把所有节点都访问一遍
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild, *rchild; //声明哪种类型的指针
}BiTnode, *BiTree; // 起别名相当于
BiTnode
:是struct BiTNode
的别名。这意味着你可以使用BiTnode
来代替struct BiTNode
。BiTree
:是struct BiTNode *
(即指向BiTNode
的指针)的别名。这意味着你可以使用BiTree
来声明一个指向二叉树节点的指针。void PreOrder(BiTree T){
if(T!=Null){
visit(T);
PreOrder(T->lchild);
PreOrder(T->rchild);
}
}
void INOrder(BiTree T){
if(T!=Null){
PreOrder(T->lchild);
visit(T);
PreOrder(T->rchild);
}
}
void PreOrder(BiTree T){
if(T!=Null){
PreOrder(T->lchild);
PreOrder(T->rchild);
visit(T);
}
}
现在我们已经知道了基本的遍历方法应用一下吧!
求树的深度,可对照下图进行模拟
int treeDepth(BiTree T){
if (T == Null){
return 0;
}else{
int l = treeDepth(T->lchild);
int r = treeDepth(T->rchild);
return l>r ? l+1 : r+1;
}
}
结论:若只给出一棵二叉树的前/中/后/层 序遍历序列中的一种,不能唯一确定一棵二叉树
前序+中序
语言描述就是:前序遍历的第一个节点是根节点,在中序遍历序列中可以根据根节点将其分为左子树和右子树。然后对齐左子树和右子树重复这个工程即可。
知道这个流程就请练习一下吧
答案:
后序+中序
在熟练掌握上一个之后,我想这个就更简单了,需要注意的是后续遍历的最后一个节点是根节点
层序+中序
总结:
滴滴,未来的准研究生们请注意啦:线索化二叉树的前驱和后继值得是遍历序列中的前驱和后继!!!
线索化,前面提到过在使用链式存储时会多出n+1个指针域是空的,我们在找中序前驱,土办法从根节点遍历,使用一个Pre来存储前驱
土办法的代码奉上:
void findPre(BiTree T){
if(T!=Null){
findPre(T->lchild);
visit(T);
findPre(T->rchild);
}
}
void visit(BiTnode * q){
if (q == p)
final = pre;
else
pre = q;
}
BiTNode *p;
BiTNode * pre= NULL;
BiTNode * final = NULL;
难搞呀,要从头开始遍历,怎么办? 我们有n+1个空链域呀,要充分使用起来啦。所以线索化二叉树就来了。
增加ltag 和 rtag去记录每个结点的左右指针,为1时 ltag 前驱, rtag后继 0则代表的左右孩子。
所以初始化代码奉上,与原来定义二叉树的地方多了两个flag.
//线索二叉树结点
typedef struct ThreadNode{
ElemType data;
struct ThreadNode * lchild, * rchild
int ltag, rtag;//左、右线索标志
}ThreadNode, *ThreadTree;
很好,我们现在已经初始化了,那我们就启动吧。
中序遍历二叉树,一边遍历,一边线索化
)ThreadNode *pre = NULL;
void InThread(ThreadTree T){
if (T != NULL){
InThread(T->lchild);
visit(T);
InThread(T->rchild);
}
}
void visit(ThreadNode *q){
if(q->lchild ==NuLl){//左子树为空,建立前驱线索
q->Lchild=pre;
q->Ltag=1;}
if(pre!=NULL&&pre->rchild==NULL){
pre->rchild =q;//建立前驱结点的后继线索
pre->rtag=1;
}
pre=q;
}
代码执行逻辑结构,先判断左子树建立前驱线索,以及前驱节点的右子树为其建立后继线索
已经知道代码了,下面我们结合图示手推一遍线索化的流程。对于上图,首先使用Pre = NULL作用是第一个节点没有直接前驱,我们需要将其置为NULL,我们进行按照左根右
的顺序中序遍历,访问第一个节点D,其左孩子为空为其建立前驱线索,又因为并没有其前驱元素所以执行q->lcild=pre;将其左孩子置为NULL和ltag=1。pre为空没有后继,pre = D。接着继续遍历G节点左右孩子均无,前驱指向D,D有右孩子无后继,pre=G。遍历B,有左孩子,pre无右孩子,将pre也就是G的右孩子指针指向B…
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- huatuo0.cn 版权所有 湘ICP备2023017654号-2
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务