【数据结构】树与二叉树
一、树的基本概念
- 树(tree)是 N(N>=0)个结点组成的有限集T,树是一种层次结构,元素中是一对多的。
-
当N = 0时,称为空树 记作:∅
-
当N > 0时,满足以下条件:
- 有且仅有一个称为树根(root)的结点
- 当N > 1 时,除根结点以外的其余 N - 1个结点可以划分成m(m>0)个互不相交的有限集,其中每个集合本身也是树,又称为根的子树(subtree),树中有且仅有一个结点称为树根(root)的结点,树中各子树是互不相交的集合。
- 结点(node) 表示树中的元素,包括数据项及若干指向其子树的分支。
- 结点的度(degree) 结点拥有的子树的数量,如图:结点A的度为3,结点B的度是2,结点M的度为0。
- 树的度 – 树中最大结点的度就是树的度,如图:此树的度为3
- 叶子(leaf)结点(终端结点) 度为0的结点称为叶子结点,也称为终端结点,如图:叶子结点有F、G、I、J、K、L、M
- 分支结点(非终端结点) 度不为0的结点称为分支结点,也称为非终端结点,如图:分支结点有A、B、C、D、E、H
- 分支结点(非终端结点) 度不为0的结点称为分支结点,也称为非终端结点,如图:分支结点有A、B、C、D、E、H
- 双亲结点(parents) 孩子结点的上层结点称为该结点的双亲结点,如图:结点I的双亲结点为D,结点L的双亲结点为E
- 兄弟结点(sibling) 具有同一双亲结点的孩子结点之间互称为兄弟结点,如图:结点B、C、D互为兄弟结点,结点K、L互为兄弟结点
- 结点的层次(level) 从根结点开始算起。若根为第一层,它的孩子为第二层,若某结点在第i层,则其孩子结点就在第i +1 ,以此类推。如图:结点A的层次为1,结点M的层次为4
- 树的高度(depth) 树中结点的最大层次数.如图:此树的高度为4
- 有序树与无序树 – 如果树中结点的子树从左到右看,谁在左边,谁在右边,是有规定的,这棵树称为有序树;反之称为无序树。在有序树中,一个结点最左边的子树称为"第一个孩子",最右边的称为"最后一个孩子"。如图:如果是其本身是一棵有序树,则以结点 B 为根结点的子树为整棵树的第一个孩子,以结点 D 为根结点的子树为整棵树的最后一个孩子
二、二叉树的基本概念
- 二叉树:由N(N>=0)个结点组成的有限集T构成,当N = 0时为空树,当N>1时,可能由一个根结点及两颗互不相交的左、右子树组成,并且左右子树都是二叉树。二叉树的子树有左右之分,因此二叉树是有序树
- 二叉树中仅且仅有一个被称为树根(root)的结点
- 当N>1时,每个结点最多有两颗子树 (即:二叉树中不存在度大于2的结点)
- 二叉树的子树有左右之分,其次序不能任意颠倒,它是有序树
- 二叉树的五种基本形态:
- 二叉树与树的区别 :
- 树中结点的最大度数没有限制,而二叉树结点的最大度数为2
- 树的结点无左、右之分,而二叉树的结点有左、右之分
- 满二叉树与完全二叉树:
- 满二叉树:除了叶结点外每一个结点都有左右子结点且叶子结点都处在最底层的二叉树
- 完全二叉树:对于一个树高为 h 的二叉树,如果其第0层至第 h-1 层的节点都满。如果最下面一层节点不满,则所有的节点在左边的连续排列,空位都在右边。这样的二叉树就是一棵完全二叉树
三、二叉树的性质
-
性质一:在二叉树的第 i 层上最多有 2^n-1个结点(i>=1)
-
性质二:深度(高度)为 k 的二叉树最多有 2^k - 1个结点(k>=1)
-
性质三:对任意一颗二叉树,如果其叶子结点数为 N0,度为2的结点数为N2,则 N0 = N2 + 1
-
性质四:
-
性质五:
四、二叉树的存储结构
- 顺序存储
- 根据二叉树的第五条性质可以得知双亲节点为 [i / 2] (向下取整)
- 左孩子为 2*i
- 右孩子为 2*i + 1
以下为存储方式:
- 根节点为a
- 通过计算发现也满足二叉树的第五条性质
- 链式存储(二叉链表)
- 通过链式存储的方式来实现链表
- 每个结点内包含一个数据域和两个指针域,数据域用来存放数据,指针域中Lchild指向的是左孩子,Rchild指向的是右孩子
- 若当前结点没有左/右孩子,则令其为空
- 设一个二叉树中有 n 个结点,那么就有 2 * n 个指针域 ,n + 1个空指针域 n-1个非空指针域
五、二叉树的遍历(访问)
- 先根遍历二叉树:顺序为DLR 若二叉树为空,则空操作否则依次执行以下3个操作
- 访问根结点
- 先根遍历左子树
- 先根遍历右子树
如上图使用先根遍历二叉树: -+a*b-cd/ef
- 中根遍历二叉树: 顺序为LDR 若二叉树为空,则空操作否则依次执行以下3个操作
- 中根遍历左子树
- 访问根结点
- 中根遍历右子树
如上图使用中根遍历二叉树 :a+b*c-d-e/f
- 后根遍历二叉树: 顺序为LRD 若二叉树为空,则空操作否则依次执行以下3个操作
- 后根遍历左子树
- 后跟遍历右子树
- 访问根结点
如上图使用后根遍历二叉树 :abcd-*+ef/-
注意:先根和后根结果可以确定根是谁,而中跟是用来区分根的左右的
六、根据遍历结果画出二叉树
- 根据条件确定根与根的左右
- 画出根和根的左右子树
- 再把左右子树当作一个序列来看,以此类推
例子:
七、树的存储结构
- 双亲表示法(顺序存储结构)
- 这种方法是用一组连续的存储空间来存储树中的结点
- 每个数组元素中不但包含结点本身,还要保存该结点的双亲结点的位置
- 这种方法可以快速的找到当前结点的双亲结点,但是找子结点比较麻烦
- 结构如下:
注意:因为 a 没有双亲结点 ,所以 parent 使用了 -1进行标记
- 孩子链表表示法
- 把每个结点的孩子结点排列起来构成一个单链表,这个单链表就是本结点的孩子链表
- 这种存储结构下,很容易找到当前结点的孩子结点,但是找双亲结点就比较麻烦了
- 孩子兄弟(左孩子,右兄弟)链表表示法
- 这种表示法又被称为二叉链表表示法,既以二叉链表作为树的存储结构
- 链表中每个结点设有一个数据域和指针域
- 与二叉树的二叉链表表示法所不同的是,两个指针域分别指向第一个孩子结点和下一个兄弟(右兄弟)结点
八、树,森林和二叉树之间的转换
- 树转换为二叉树
- 加线:在树中所有相邻的兄弟之间加一连线
- 抹线:对树中每个结点,抹去除了左孩子外其余孩子之间的连线。
- 整理:以树的根结点为轴心,将整树按顺时针旋转45°
- 二叉树转树
- 加线:若某结点是其双亲的左孩子,则把该结点右链上所有的结点都与该结点的双亲结点用线连起来
- 抹线:删除掉原来二叉树中所有双亲结点与其左孩子右链上所有结点的连线。
- 整理:整理由前两步所得到的树
- 森林转二叉树(森林和二叉树之间的转换)
-
森林:树的集合称之为森林
-
步骤
- 将森林中的每棵树转换成相应的二叉树
- 第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右子树,当所有二叉树连在一起后,所得到的二叉树就是由森林转换得到的二叉树。
- 树和森林都可以转换为二叉树,所不同的是树转换成的二叉树,其根结点必然无右孩子 ,而森林转换后的二叉树,其根结点有右孩子
- 二叉树转森林
- 将二叉树中根结点与其右孩子间的连线全部抹掉,使之变成孤立的二叉树
- 将孤立的二叉树还原成树
九、树的遍历
- 先根遍历
-
若树非空,则遍历方法为
- 先访问根结点
- 从左到右依次先根遍历根结点的每一棵子树
-
例如:
- 先根遍历序列为:ABEFIGCDHJKLNOM
- 后根遍历
-
若树非空,则遍历方法为
- 从左到右,依次后根遍历根结点的每一棵子树
- 访问根结点
-
例如
- 后根遍历序列为:EIFGBCJKNOLMHDA
- 层次遍历
-
若树非空,则遍历方法为
- 先访问第一层上的结点
- 然后依次遍历第二层至第n层的结点
-
例如
- 层次遍历序列为:ABCDEFGHIJKLMNO
十、森林的遍历
- 先根遍历
-
若森林非空,则遍历方法为
- 访问森林中第一棵树的根结点
- 先根遍历第一颗树的根结点的子树森林
- 先根遍历除去第一棵树之后剩余的树构成的森林
-
效果等同于依次对各个树进行先根遍历
-
第二种方法:先将森林转为对应的二叉树之后再去遍历
-
例如
- 先根遍历序列为:ABCDEFGHIJ
- 中根遍历
-
若森林非空,则遍历方法为
- 中根遍历森林中第一棵树的根结点的子树森林
- 访问第一棵树的根结点
- 中根遍历除去第一棵树之后剩余的树构成的森林
-
效果等同于依次对各个树进行后根遍历
-
第二种方法:先将森林转为对应的二叉树之后再去遍历
-
例如
- 中根遍历序列为:BCDAFEHJIG
- 后根遍历
-
若森林非空,则遍历方法为:
- 后根遍历森林中第一棵树的根结点的子树森林
- 后根遍历除去第一棵树之后剩余的树构成的森林
- 访问第一棵树的根结点
-
第二种方法:先将森林转为对应的二叉树之后再去遍历
-
例如
- 后根遍历序列为:DCBFJIHGEA
十一、二叉树的基本操作(附代码)
- 定义二叉树
struct DoubleTree{
struct DoubleTree * left;
char data;
struct DoubleTree * right;
};
- 创建二叉树
struct DoubleTree* createDoubleTree(){
char data;
struct DoubleTree *root;
printf("请输入结点:");
fflush(stdin);
scanf("%c",&data);
if(data == '0'){
return NULL;
}
root = (struct DoubleTree *)malloc(sizeof(struct DoubleTree));
root->data = data;
root->left = createDoubleTree();
root->right = createDoubleTree();
return root;
}
- 先根遍历二叉树
void preOrder(struct DoubleTree *tree){
if(tree!=NULL){
printf("%c ",tree->data);
preOrder(tree->left);
preOrder(tree->right);
}
}
- 中根遍历二叉树
void inOrder(struct DoubleTree *tree){
if(tree!=NULL){
inOrder(tree->left);
printf("%c ",tree->data);
inOrder(tree->right);
}
}
- 后根遍历二叉树
void postOrder(struct DoubleTree * tree){
if(tree!=NULL){
postOrder(tree->left);
postOrder(tree->right);
printf("%c ",tree->data);
}
}
- 求树高
int getTreeHeight(struct DoubleTree * tree){
int height,left,right;
if(tree!=NULL){
left = getTreeHeight(tree->left);
right = getTreeHeight(tree->right);
if(left>right){
height = left + 1;
}else{
height = right + 1;
}
}else{
return 0;
}
return height;
}
- 求叶子
void getLeaf(struct DoubleTree * tree){
if(tree!=NULL){
if(tree->left == NULL && tree->right == NULL){
printf("%c ",tree->data);
}
getLeaf(tree->left);
getLeaf(tree->right);
}
}
- 求叶子数量
int leafNum(struct DoubleTree * tree){
static int count = 0;
if(tree!=NULL){
if(tree->left == NULL &&tree->right == NULL){
count++;
}
leafNum(tree->left);
leafNum(tree->right);
}
return count;
}
- 交换左右子树
void exChange(struct DoubleTree *tree){
struct DounleTree * temp;
if(tree){
exChange(tree->left);
exChange(tree->right);
temp = tree->left;
tree->left = tree->right;
tree->right = temp;
}
}
- 计算二叉树中有几个节点
int countTree(struct Ditree *tree){
if(tree){
return countTree(tree->left) + countTree(tree->right) + 1;
}else{
return 0;
}
}
- 计算二叉树中左右子孙数量
void countChildTree(struct Ditree *tree){
if(tree){
printf("%c:%d个子孙\n",tree->data,countTree(tree)-1);
countChildTree(tree->left);
countChildTree(tree->right);
}
}
- main函数测试
int main(){
struct DoubleTree * tree = createDoubleTree();
printf("先根:");
preOrder(tree);
printf("\n");
printf("中根:");
inOrder(tree);
printf("\n");
printf("后根:");
postOrder(tree);
printf("\n");
printf("树的高度:%d\n",getTreeHeight(tree));
printf("树的叶子有:");
getLeaf(tree);
printf("\n");
printf("树的叶子数量有:%d\n",leafNum(tree));
return 0;
}