目录
引入树的概念
树是一种
非线性
的数据结构,它是由
n
(
n>=0
)个有限结点组成一个具有层次关系的集合。
把它
叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
简单说下树的一些基本概念:
节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:
A
的为
6
叶节点或终端节点:度为
0
的节点称为叶节点; 如上图:
B
、
C
、
H
、
I...等节点为叶节点
非终端节点或分支节点:度不为
0
的节点; 如上图:
D
、
E
、
F
、
G...
等节点为分支节点
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:
A
是
B
的父节点
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:
B
是
A
的孩子节
点
兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:
B
、
C
是兄弟节点
树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为
6
树的高度或深度:树中节点的最大层次; 如上图:树的高度为
4
二叉树
概念
二叉树才是重头戏,
一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加
上两棵别称为左子
树和右子树的二叉树组成。
二叉树的特点:
1.
每个结点最多有两棵子树,即二叉树不存在度大于
2
的结点。
2.
二叉树的子树有左右之分,其子树的次序不能颠倒。
现实中一颗很标准的二叉树
一般的二叉树
特殊的二叉树
1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉
树。也就是说,如果一个二叉树的层数为
K
,且结点总数是
(2^k) -1
,则它就是满二叉树。
2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对
于深度为
K
的,有
n
个结点的二叉树,当且仅当其每一个结点都与深度为
K
的满二叉树中编号从1
至n
的结点一一对应时称之为完全二叉树。要注意的是满二叉树是一种特殊的完全二叉树。
二叉树的一些重要性质
1.
若规定根节点的层数为
1
,则一棵非空二叉树的
第
i
层上最多有
2^(i-1)
个结点
.
2.
若规定根节点的层数为
1
,则
深度为
h
的二叉树的最大结点数是
2^h- 1
.
3.
对任何一棵二叉树
,
如果度为
0
其叶结点个数为
n0,
度为
2
的分支结点个数为
n2,
则有
n0
=
n2+1.
4.若规定根节点的层数为
1
,具有
n
个结点的满二叉树的深度
,
h=LogN
记住这些性质对我们做选择题非常有帮助!
二叉树储存结构
二叉树一般分为顺序储存和链式储存。
顺序储存
顺序结构存储就是使用
数组来存储
,一般使用数组
只适合表示完全二叉树
,因为不是完全二叉树
会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,
二叉树顺序存储在物理上是一个数
组,在逻辑上是一颗二叉树。
链式储存
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。通常的
方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩
子和右孩子所在的链结点的存储地址。链式结构又分为二叉链和三叉链。
// 二叉链的创建
typedef int BTDataType;
structBinaryTreeNode
{
structBinTreeNode*Left; // 指向当前节点左孩子
structBinTreeNode*Right; // 指向当前节点右孩子
BTDataType_data; // 当前节点值域
}
// 三叉链的创建
structBinaryTreeNode
{
structBinTreeNode*Parent; // 指向当前节点的双亲
structBinTreeNode*Left; // 指向当前节点左孩子
structBinTreeNode*Right; // 指向当前节点右孩子
BTDataType_data; // 当前节点值域
};
链式结构的实现
二叉树链式结构的遍历
所谓遍历
(Traversal)
是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。访
问结点所做的操作依赖于具体的应用问题。遍历是二叉树上最重要的运算之一,是二叉树上进行
其它运算之基础。
前序
/
中序
/
后序的递归结构遍历
:是根据访问结点操作发生位置命名。拿到任何一颗二叉树,首先将其分为三个部分,分别是根、左子树、右子树,左子树又可分为根、左子树、右子树,依次递归下去。
1.
前序遍历
(Preorder Traversal
亦称先序遍历
)——
访问根结点的操作发生在遍历其左右子树之前。
2.
中序遍历
(Inorder Traversal)——
访问根结点的操作发生在遍历其左右子树之中
3.
后序遍历
(Postorder Traversal)——
访问根结点的操作发生在遍历其左右子树之后。
层序遍历
:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的
根节点所在层数为
1
,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然
后从左到右访问第
2
层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问
树的结点的过程就是层序遍历。
代码实现
前中后序的实现
// 二叉树的实现
typedef char TDataType;
typedef struct BTreeNode
{
struct BTreeNode*left;
struct BTreeNode*right;
TDataType data;
}BTNode;
//前序遍历
void PrevOrder(BTNode*root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
else
{
printf("%c ", root->data);
PrevOrder(root->left);
PrevOrder(root->right);
}
}
//中序遍历
void InOrder(BTNode*root)
{
if (root== NULL)
{
printf("NULL ");
return;
}
else
{
InOrder(root->left);
printf("%c ", root->data);
InOrder(root->right);
}
}
//后序遍历
void PostOrder(BTNode*root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
else
{
PostOrder(root->left);
PostOrder(root->right);
printf("%c ", root->data);
}
}
计算二叉树节点个数
//分治算法
int BTreeSize(BTNode*root)
{
return root == NULL ? 0 : BTreeSize(root->left) + BTreeSize(root->right) + 1;
//节点个数=左子树节点个数+右子树节点个数+1,1是指该树根的节点
}
计算叶子节点个数
//计算叶子数 分治算法
int BTreeLeafSize(BTNode*root)
{
if (root == NULL)
return 0;
if (root->left == NULL&&root->right == NULL) //若该树的没有左子树和右子树则就是叶子
return 1;
else //若有右子树或者右子树 则继续向下遍历访问
return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);
}
计算二叉树的最大深度
int maxDepth(struct TreeNode* root)
{
if(root==NULL)
{
return 0;
}
else
{
int leftdepth=maxDepth(root->left); //左子树的最大深度
int rightdepth=maxDepth(root->right); //右子树的最大深度
return leftdepth>rightdepth?leftdepth+1:rightdepth+1; //总的深度 = max(左子树深度,右子树深度)+ 1
}
}
层序遍历
void BTreeLevelorder(BTNode*root)
{
Queue q; //使用队列实现层序遍历
QueueInit(&q);
if (root != NULL)
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode*front = QueueFront(&q);
QueuePop(&q);
printf("%c ", front->data);
//上一层带出下一层
if (front->left != NULL)
QueuePush(&q, front->left);
if (front->right != NULL)
QueuePush(&q, front->right);
}
printf("\n");
}