一、基本概念
1、结点:数据元素+若干指向子树的分支
2、结点的度:分支的个数
3、树的度:树中所有结点的度的最大值
4、叶子结点:度为零的结点
5、孩子结点:一个结点的直接后继
6、双亲结点:一个结点的直接前驱
7、兄弟结点:同一双亲结点的孩子结点之间互称
8、结点的层次:根结点的层次为1,第i层的结点的 子树的根结点的层次为i+1
9、树的深度:树中结点的层次的最大值
二、树的存储结构
1、顺序存储——双亲表示法
原理:除了根节点外,其余每个节点,不一定有孩子,但一定有且仅有一个双亲。
data(数据域,存储结点的数据信息) | parent(指针域,存储双亲的下标) |
#define MAX_TREE_SIZE 100
typedef int TElemType;
typedef struct PTnode /*结点的结构*/
{
TElemType data; //结点的数据域名
int parent; //结点的指针域——双亲的位置
}
typedef struct
{
PTnode nodes[MAX_TREE_SIZE]; //结点数组
int r,n; //根的位置和节点数
}PTree;
约定根节点的指针域为-1
2、孩子表示法
原理:每个结点下面很有可能有多个子树,用一个指针域指向一个子树,那么结构中就需要多个指针域。指针域的个数就是树的度。
树的度:树各个节点度的最大值
data(存储一个结点中的数据域) | 指针域1 | 指针域2 | 指针域3 |
优缺点: 如果树中各个节点的度相差很大,那么将会十分浪费空间
如果树中各个节点的度相差很小,充分利用了空间。
改进:让每个节点指针域的个数等于该节点的度数。
data | degree(存储该节点的度数) | 指针域1 | 指针域2 | 指针域3 |
优缺点: 空间利用率提高
由于各个节点的度有差异,运算困难。
改进:
原理:n个节点存储于n维数组中
每个节点的孩子结点用单链表排列起来
child(存储某结点在表头数组中的下标) | next(指向下一个孩子结点的指针) |
data(某结点的数据信息) | firstchild(头指针域,存储孩子链表的头指针) |
#define MAX_TREE_SIZE 100
typedef struct CTNode /*孩子结点的数据结构*/
{
int child; //该孩子结点的下标
struct CTNode *next; //指向下一个孩子结点的指针
}*ChildPtr;
typedef struct /*表头结构*/
{
TElemType data; //各个节点的数据
ChildPtr firstchild; //指向第一个孩子结点的指针
}CTBox;
typedef struct /*树结构*/
{
CTBox nodes[MAX_TREE_SIZE];/*节点数组*/
int r,n;
}CTree;
实际问题: 遍历整个树 查找某个结点的某个孩子 找到某个结点的兄弟结点
3、孩子兄弟表示法——二叉树
特殊:每个节点有且仅有一个左孩子和右孩子
data(数据域) | leftchild | rightchild |
typedef struct CSNode
{
TElemType data;
struct CSNode *firstchild,*rightsib;
}CSNode , *CSTree;
三、二叉树
1、定义:n个结点的有限集合
可以为空集(空二叉树)
由一个根结点和两颗互不相交的、分别称为根结点的左子树和右子树的二叉树组成。
2、特点:每个节点最多有两颗子树,每个结点度<=2
区分左右子树
问题:三个节点的二叉树有几种形态?
3、特殊二叉树
A:斜树
B:满二叉树 :所有的分支结点都存在左右子树,并且所有的叶子节点都在同一层。
C:完全二叉树:对一棵具有n个结点的二叉树按层序编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树编号为i的结点位置完全相同,则这就是完全二叉树
在满二叉树的基础上,我在最底层从右往左删去若干节点,得到的都是完全二叉树。如果编号出现空档,那就说明不是完全二叉树
所以说,满二叉树一定是完全二叉树,但是完全二叉树不一定是满二叉树。
四、二叉树的存储结构
1、顺序存储结构
A:完全二叉树的顺序存储——理想状态
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
数据 | A | B | C | D | E | F | G | H | I | J |
B:普通二叉树——空间利用率低
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
数据 | A | 空 | B | 空 | 空 | 空 | C |
2、二叉链表
节点结构
Lchild(指向左孩子的指针) | data(结点数据) | Rchild(指向右孩子的指针) |
typedef struct BiTNode
{
TElemType data;
struct BiTNode *Lchild,*Rchild;
}BiTNode , *BiTree;
五、二叉树的遍历
1、先序遍历——根左右
若二叉树为空树,则空操作;
否则(1)访问根结点
(2)先序遍历左子树
(3)先序遍历右子树。
访问:根据实际的需要来确定做什么。比如对每个结点进行计算,输出打印。
void PreOrder(BiTree root) /* root是指向根结点的指针 */
{
if (root!=NULL)
{
Visit(root ->data); /*访问根结点*/
PreOrder(root ->LChild); /*先序遍历左子树*/
PreOrder(root ->RChild); /*先序遍历右子树*/
}
}
2、中序遍历(左 根 右)
若二叉树为空树,则空操作;
否则 (1)中序遍历左子树;
(2)访问根结点;
(3)中序遍历右子树。
void InOrder(BiTree root)
{
if (root!=NULL)
{
InOrder(root ->LChild); /*中序遍历左子树*/
Visit(root ->data); /*访问根结点*/
InOrder(root ->RChild); /*中序遍历右子树*/
}
}
3、后序遍历(左 右 根)
若二叉树为空树,则空操作;
否则 (1)后序遍历左子树
(2)后序遍历右子树
(3)访问根结点。
void PostOrder(BiTree root)
{
if (root!=NULL)
{
PostOrder(root ->LChild); /*后序遍历左子树*/
PostOrder(root ->RChild); /*后序遍历右子树*/
Visit(root ->data); /*访问根结点*/
}
}
4、层序遍历
若二叉树为空树,则空操作;
否则从树的根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。