基本概念
定义:
1.有且只有一个称为根的节点;
2.有若干个互不相交的子树,这些子树本身也是一棵树;
3.由节点和边组组成的;
4.每个节点只有一个父节点,可以有无数个子节点(除了根节点)。
分类:
|一般树。任意一个子节点个数不受限制,可以是有序树也可以是无序树。
|二叉树。任意一个节点最大度为2,二叉树是有序树,左右节点不能随意互换。
|
一般二叉树
|满二叉树。每一层节点都是满的。
|完全二叉树。除最后一层外,每一层节点都是满的,最后一层节点一定从左向右连续排列。
|森林。n个互不相交的树的集合,可以是互不相连的几个树
一些专业术语:
-
父节点:该节点上面紧挨着的一个节点,一个节点只有一个父节点。
-
祖先节点:一个节点的父节点的父节点,一个节点只有一个。
-
兄弟节点:两个节点具有共同的父节点。
-
堂兄弟节点:两个节点的父节点不同,但各自的父节点彼此是兄弟节点。
-
子孙节点:一个节点的所有子节点和子孙节点。
-
树的深度:从根节点到最底层节点的层数,根节点是第一层。
-
叶子节点:没有子节点的节点。
-
非终端节点:非叶子节点。
-
度:子结点的个数称为度。
-
最大的度:一个树中含有最大度的那个度是这个树最大的度。
树的存储
本质是把非线性结构转化成线性结构进行存储。任何树的存储都是先转化成二叉树之后再按照二叉树的方式进行存储。
二叉树的存储
|连续存储(只能以完全二叉树形式存储)
|链式存储
一般树的存储
-
双亲表示法
*利用结构体数组存储
*数组下表即为该节点编号,结构体内部有两部分,一部分是数据域,一部分存储其父节点编号,如果没有父节点则为-1。
*缺点是只能记录无序树。
-
孩子表示法
*数组结构体
*每个节点由两部分组成,一部分是数据域,一部分存储其子节点的地址,所有孩子像链表一样连起来。后面的子节点是无序的。
*缺点是难以寻找父节点。
-
双亲孩子表示法
*数组结构体
*每个节点对应的数组下标就是各自的编号。每个节点由三部分组成:数据域,父节点下标,孩子节点地址。
*优势:即方便找父节点也方便找孩子节点。
-
二叉树表示法(最常用)
*结构体数组
*思路是先转化为二叉树,再按照二叉树的方法进行存储。每个节点有3个部分组成:一个数据域,一个指向其最左侧第一个孩子,另一个指针域指向其第一个兄弟。这一步可以把一个普通树转化为一个二叉树,这个过程也可以通过画图形象的表示出来。
*普通树转化成二叉树一定没有右子树。
森林的存储
*把森林转化成二叉树。 把根节点互相看做兄弟,接下来转化方法就跟普通树一样。
树的遍历
先序遍历,中序遍历,后序遍历这三种方法有一些共同之处:每遍历到一个节点,这个节点本身就可以看做是又一棵树的根节点。
先序遍历
- 根节点->先序访问左子树->先序访问右子树
- 下图遍历顺序:① -> ② -> ④ -> ⑤ -> ③ -> ⑥ -> ⑦
- 描述:先遍历根节点①;接着来到左孩子②,②这棵树仍然存在左孩子④,所以来到④;④是叶子结点,是一棵空树,左右节点都不存在,所以④这棵树遍历完成,也代表着②的左子树遍历完成;②的左子树遍历完成后,接下来来到其右孩子⑤,⑤的遍历方法和④一样;②的左右节点都遍历完成,②这棵树就遍历完成了,也就是①的左子树遍历完成;接下来以相同的方法遍历①的右子树③,大家自行推断。
①
/ \
② ③
/ \ / \
④ ⑤ ⑥ ⑦
中序遍历
- 左子树->根节点->先序访问右子树
- 上图遍历顺序:④ -> ② -> ⑤ -> ① -> ⑥ -> ③ -> ⑦
- 描述:根节点①的左孩子是②,②仍然有左孩子④,直到④才没有左孩子,所以④的左子树可以认为已经遍历完成,然后遍历空树④的根节点④,其右子树也不存在所以也可以认为遍历完成;④遍历完成,即②的左子树遍历完成,接着遍历②的根节点②,接着以同样的操作遍历⑤;②遍历完成,即①的左子树遍历完成,接着以同样的方法遍历其右子树③,请大家自行脑补。
后序遍历
- 后序遍历左子树->后序遍历右子树->根节点
- 上图遍历顺序:④ -> ⑤ -> ② -> ⑥ -> ⑦ -> ③ -> ①
- 描述:下面我只描述一下如何遍历①的右子树。记得我们上面说的吗,“每遍历到一个节点,这个节点本身就可以看做是又一棵树的根节点”,现在我们就把③看作是一棵树的根节点,所以要先遍历其左子树⑥,再遍历其右子树⑦,最后遍历根节点③。
层序遍历
- 从上往下,从左往右挨个遍历。
- 上图遍历顺序:① -> ② -> ③ -> ④ -> ⑤ -> ⑥ -> ⑦
创建树以及先序,中序,后序遍历代码
#include <stdio.h>
#include <stdlib.h>
typedef struct T{
int data;
struct T *left;
struct T *right;
}T,*PT;
//使用递归的方法创建一个树
PT make()
{
int data;
scanf("%d",&data);
getchar();//吸收scanf的回车键
//判断数据,如果是-1,那么不创建该节点并且向上返回
//如果不是-1,那么就创建该节点并且把该数字存储到数据域
if(data==-1)
return NULL;
else
{
PT tree=(PT)malloc(sizeof(T));
tree->data=data;
printf("请输入节点%d的左子树",data);
tree->left=make();
printf("请输入节点%d的右子树",data);
tree->right=make();
return tree;
}
}
//先序遍历 :根-左-右
void xianxu(PT tree)
{
//判断树是否为空
if(tree==NULL)
return;
else
{
printf("%d",tree->data);
xianxu(tree->left);
xianxu(tree->right);
}
}
//中序遍历:左-根-右
void zhongxu(PT tree)
{
if(tree==NULL)
return;
else
{
xianxu(tree->left);
printf("%d",tree->data);
xianxu(tree->right);
}
}
//后序遍历:左-右-根
void houxu(PT tree)
{
if(tree==NULL)
return;
else
{
xianxu(tree->left);
xianxu(tree->right);
printf("%d",tree->data);
}
}
int main()
{
printf("创建一棵树,请输入根节点");
PT tree=make();
int choose;
printf("先序遍历请按1;中序遍历请按2;后序遍历请按3:\n");
scanf("%d",&choose);
getchar();
switch(choose)
{
case 1:xianxu(tree);
break;
case 2:zhongxu(tree);
break;
case 3:houxu(tree);
break;
default:
break;
}
}
以上对遍历的描述带有我的主观理解,希望能够帮助到你
欢迎各位的指正和请教
![](https://img-blog.csdnimg.cn/36d90cdf94df47b18132dad72c1516fa.png)