树的定义与性质
- 1️⃣树可以没有结点,这种情况下把树称为空树(empty tree)
- 2️⃣树的层次(layer)从根结点开始算起,即根结点为第一层,根结点子树的根结点为第二层,以此类推
- 3️⃣把结点的子树棵数称为结点的度(degree),而树中结点的最大的度称为树的度(也称为树的宽度),例如下图中的三棵树的度分别为2、3、5
- 4️⃣由于一条边连接两个结点,且树中不存在环,因此对有
n个结点
的树,边数一定是n-1
且满足连通、边数等于顶点数减1的结构一定是一棵树 - 5️⃣叶子结点被定义为度为
0
的结点,因此当树中只有一个结点(即只有根结点)时,根节点也算是叶子节点 - 6️⃣结点的深度(depth)是指从根结点(深度为1)开始自顶向下逐层累加至该结点时的深度值;结点的高度(height)是指从最底层叶子结点(高度为1)开始自底向上逐层累加至该结点时的高度值。树的深度是指树中结点的最大深度,树的高度是指树中结点的最大高度。对树而言,深度和高度是相等的,例如下图中的三棵树的深度和高度分别为4、4、2,
但是具体到某个结点来说深度和高度就不一定相等了。 - 7️⃣多棵树组合在一起称为森林(forest),即森林是若干棵树的集合。
二叉树的递归定义
- 二叉树的递归定义:
- 要么二叉树没有根结点,是一个棵空树
- 要么二叉树由根结点、左子树、右子树组成,且左子树和右子树都是二叉树
- 二叉树与度为2的树的区别
- 度为2的树只能说明树中每个结点的子结点个数不超过2
- 二叉树虽然也满足每个结点的子结点个数不超过2,但是它的左右子树是严格区分的,不能随意交换左子树和右子树的位置
- 两种特殊的二叉树
- 满二叉树:每一层的结点个数都达到了当前层能达到的最大结点数,如下图中的E
- 完全二叉树:除了最下面一层之外,其余层的结点个数都达到了当层能达到的最大结点数,且最下面一层只从左至右连续存在若干结点,而这些连续结点右边的结点全部不存在。下图中的树D和E均为一棵完全二义树。
- 孩子结点、父亲结点、兄弟结点、祖先结点、子孙结点
- 一个结点的子树的根节点称为它的孩子结点,而它称为孩子结点的父亲结点。与该结点同父亲的结点称为该结点的兄弟结点(同一层次非同父亲的结点称为堂兄弟结点)。如果存在一条从结点X到结点Y的从上至下的路径,那么称结点X是结点Y的祖先结点,结点Y是结点X的子孙结点。注意:自己既是自己的祖先结点,也是自己的子孙结点
- 例如下图中,B是E的父亲结点,E是B的孩子结点,B与C互为兄弟节点,ABD都是D的祖先结点,D同时也是ABD的子孙结点。
二叉树的存储结构和基本操作
二叉树用链表来表示,和普通链表的区别是,由于二叉树每个结点都有两条出边,因此指针域变成了两个,即指向左子树的根结点地址和右子树的根结点地址,如果某个子树不存在,则指向NULL,其他和普通链表一样,所以把这种链表称为二叉链表
struct node{
typename data; // 数据域
node* lchild; // 指向左子树根结点的指针
node* rchild; // 指向右子树根结点的指针
}
二叉树建树前根结点不存在,所以其地址一般设置为NULL
node* root = NULL;
如果需要新建结点,如要往二叉树中插入结点的时候
node* newNode(int v){
node* Node = new Node; // 申请一个node型变量的地址空间
Node->data = v; // 结点权值为v
Node->lchild = Node->rchild = NuLL; // 初始状态下没有左右孩子
return Node; // 返回新建结点的地址
}
二叉树的常用操作
二叉树结点的查找、修改
查找操作是指在给定数据域的条件下,在二叉树中找到所有数据域为给定数据域的结点,并将它们的数据域修改为给定的数据域
用递归完成查找修改操作:
- 递归式:对当前结点的左子树和右子树进行分别递归
- 递归边界:结点为NULL为止
void search(node* root, int x, int newdata){
if(root == NULL){
return; // 空树(递归边界)
}
if(root->data == x){ // 找到数据域为x的结点,把它修改成newdata
root->data = newdate;
}
search(root->lchild, x, newdata); // 往左子树递归搜索x
search(root->rchild, x, newdata); // 往右子树递归搜索x
}
二叉树结点的插入
二叉树结点插入位置就是数据域在二叉树查找失败的位置,而由于这个位置是确定的,因此在递归查找的过程中一定要只根据二叉树的性质来选择左子树或者右子树中的一颗子树进行递归,且最后到达空树的地方就是查找失败的地方,也就是结点需要插入的地方
void insert(node* &root, int x){
if(root == NULL){
root = newNode(x);
return;
}
if(由二叉树的性质,x应该插在左子树){
insert(root->lchild, x);
} else {
insert(root->rchild, x);
}
}
-
根结点指针root必要使用引用&:在
insert
函数中新建了结点,并把新结点的地址赋给了当前层的root,如果不使用引用,root = newNode(x)
这个语句对root的修改就无法作用到原变量(即上一层的root->lchild
与root->rchild
)上去,也就不能把新结点接到二叉树上面 -
search函数不需要引用:因为
search
函数中修改的是指针root指向的内容,而不是root本身,而对指针实现的结点内容的修改是不需要加引用的 -
判断是否加引用
- 如果函数中需要新建结点,即对二叉树的结构做出修改,就需要引用
- 如果只是修改当前结点的内容,或者是仅仅遍历树,就不需要加引用
-
务必令新结点的左右指针域为NULL
二叉树的创建
二叉树的创建其实就是二叉树结点的插入过程
node* create(int data[], int n){
node* root = NULL; // 新建空根结点root
for(int i = 0; i < n; i++){
insert(root, data[i]);
}
return root;
}
二叉树存储结构图示
区别root==NULL
与*root==NULL
:
- 在递归时,总是往左子树根结点和右子树根结点递归,此时如果子树是空树,那么root一定是NULL,表示这个结点不存在
*root==NULL
是错误的,因为*root
的含义是获取root指向的空间的内容,但是无法说明地址root是否为空,也就无法确定是否存在这个结点
完全二叉树的数据结构
对于完全二叉树,除了可以使用二叉链表外,还可以使用数组来存放,完全二叉树的结点可以按照如下方式进行排序:
-
对完全二叉树当中的任何一个结点(设编号为
x
),其左孩子的编号一定是2x
,右孩子的编号一定是2x+1
-
完全二叉树可以通过建立一个大小为(2^k)的数组来存放所有结点的信息
- k为完全二叉树的最大高度
- 1号位必须是根结点
-
如果题目给了完全二叉树,那么数组大小只需设为结点上限个数加1
-
该数组中元素存放的顺序恰好为该完全二叉树的层序遍历序列
-
判断某个结点是否为叶结点的标志:该结点(下标root)的左子结点的编号
root*2
大于结点总个数n -
判断某个结点是否为空节点:该结点的下标root大于结点总个数n
Write by Gqq