文章目录
树
树的定义
树(Tree)是n(n>=0)个结点的有限集。当有限集T为空时称为空树;
当T不为空时,它满足两个条件:
- 有且仅有一个特定的称为根(root)的结点;
- 其余的结点可分为m(m>=0)个互不相交的子集T1,T2,T3…Tm,其中每个子集又是一棵树,并称其为子树(Subtree)。
基本术语
- 树的结点包含一个数据元素及若干指向其子树的分支
- 度为0的结点称为叶子结点(leaf)或终端结点
- 结点所拥有的子树的个数称为该结点的度。结点度的最大值为树的度
- 结点的层次:根为第一层,根的孩子为第二层。树中结点的最大层次称为树的深度
- 树中一个结点的子树的根结点称为这个结点的孩子(child)。这个结点称为它孩子结点的双亲(Parent)。具有同一个双亲的孩子结点互称为兄弟(sibling)。
- 如果将树中结点的各子树看成从左至右是有次序的,则称该树为有序树,否则称为无序树。
二叉树
二叉树的定义
二叉树(Binary Tree)是一种特殊的树型结构,它的特点是每个结点至多只有两棵子树(即二叉树中不存在度数大于2的结点),并且,二叉树的子树有左右之分。
二叉树就是度为2的树吗?
不是!
一棵度为二的有序树与一棵二叉树的区别在于:有序树的结点次序是相对于另一结点而言的,如果有序树中的子树只有一个孩子时,这个孩子结点就无须区分其左右次序,而二叉树无论其孩子数是否为2,均需确定其左右次序,也就是说二叉树的结点次序不是相对于另一结点而言而是确定的。
满二叉树和完全二叉树
满二叉树:深度为k,且有(2^k)-1个结点的二叉树。
完全二叉树:深度为k,结点数为n的二叉树,当且仅当每个结点的编号都与相同深度的满二叉树中从1到n的结点一一对应时,称为完全二叉树。
下面两个二叉树不是完全二叉树,因为没有与相同深度的满二叉树中的结点一 一对应
二叉树的性质
- 在二叉树的第i层上至多有 2i-1个结点。
- 深度为k的二叉树至多有 2k-1个结点(k>1)。
- 对任何一棵二叉树T, 如果其叶结点数为 n0, 度为2的结点数为 n2,则n0=n2+1。
- 具有 n (n >=0) 个结点的完全二叉树的深度为|log2(n)|(取整)+1
- 如果对一棵有n个结点的完全二叉树的结点按层次顺序编号,则对任一结点有:
如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点 i/2(取整)
如果2i>n,则结点i无左孩子;否则其左孩子是结点2i(取整)
如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1(取整)
给个图例,自行体会~
二叉树的存储结构
顺序存储表示
按完全二叉树的结点层次编号,用一组地址连续的存储单元,依编号存放二叉树中的数据元素,结点的相对位置蕴含着结点之间的关系。
如图
它的存储序列为:
链式存储表示
可根据不同的结点结构设计不同形式的链式存储结构
对于下图的二叉树,有几种不同的链式存储结构
二叉链表
二叉树的类型描述
typedef char TElemType;
typedef struct BiNode{
TElemType data;
struct BiNode* lchild; //左孩子
struct BiNode* rchild; //右孩子
}BiNode,*BiTree;
三叉链表
三叉链表的类型描述
typedef char TElemType;
typedef struct BiNode{
TElemType data;
struct BiNode* lchild; //左孩子
struct BiNode* rchild; //右孩子
struct BiNode* parent; //父结点
}BiNode,*BiTree;
线索链表
二叉链表中的空链域存储其他有用信息
遍历二叉树
根据某种路径来巡防二叉树中的每一个结点,使得每一个结点均被访问,且只访问一次。二叉树的遍历是对二叉树进行多种操作的基础。
本文采用的是二叉链表的存储结构
如下图所示,有三种遍历方式
- 先序遍历 DLR
- 中序遍历 LDR
- 后序遍历 LRD
再举一个例子
先序遍历
实际上是一种递归算法思想
- 访问根结点
- 先序遍历左子树
- 先序遍历右子树
若遍历二叉树,则需要对二叉树进行一定的操作,这里就对二叉树进行输出操作
status Print(TElemType e)
{ //输出数据值
cout << e;
return OK;
}
void PreOrderTraverse(BiTree T, status (*Print)(TElemType e))
{ //先序遍历算法
if (T)
{
Print(T->data);
PreOrderTraverse(T->lchild, Print);
PreOrderTraverse(T->rchild, Print);
}
}
中序遍历
- 中序遍历左子树
- 访问根结点
- 中序遍历右子树
void InOrder(BiTree T, status(*Print)(TElemType e))
{ //中序遍历算法
if (T)
{
InOrder(T->lchild, Print);
Print(T->data);
InOrder(T->rchild, Print);
}
}
后序遍历
- 后序遍历左子树
- 后序遍历右子树
- 访问根结点
void PostOrder(BiTree T, status(*Print)(TElemType e))
{ //后序遍历算法
if (T)
{
PostOrder(T->lchild, Print);
PostOrder(T->rchild, Print);
Print(T->data);
}
}
创建二叉树
这里默认用先序的方式创建二叉树。
思路如下:
- 首先判断二叉树是否为空(即输入的第一个数据a是否为’#’)。若第一个数据a为空格,则二叉树为NULL;若不为空,进行下列操作
- 为根建立结点,其数据域为a
- 先序创建左子树的二叉树链表
- 先序创建右子树的二叉树链表
(即在满二叉树的相应结点位置如果为空,就输入’#’)
void CreateBiTree(BiTree &T)
{ //先序创建二叉树
TElemType a;
cin >> a;
if (a == '#') T = NULL;
else {
T = (BiNode*)malloc(sizeof(BiNode));
if(!T) exit(0);
T->data = a;
CreateBiTree(T->lchild);
CreateBiTree(T->rchild);
}
}
假如要创建如下图的二叉树
则需要输入以下:
ABC##DE#G##F###
统计二叉树中叶子结点的个数
我们先来回顾一下叶子结点的定义:度为0的结点。
故当一个结点左孩子和右孩子都不存在时,这个结点就为叶子结点。
因为要返回叶子结点的个数,所以需在算法中增添一个“计数器”,并将算法中对结点的操作改为:若是叶子结点,则计数器加1。
注意:要为计数器专门定义一个全局变量,默认值为0,不要让它的赋值语句出现在函数体或主函数中!
int num=0; //计数器num为全局变量
void countleaf(BiTree T, int& num)
{ //返回叶子结点的个数
if (T)
{
if (!T->lchild && !T->rchild) num++;
countleaf(T->lchild, num);
countleaf(T->rchild, num);
}
}
求二叉树树的深度
当二叉树为空树时,深度为0;当二叉树只有根结点时,深度为1;
其他情况时,求左子树和右子树中深度的最大值,再加1,即为树的深度
int Depth(BiTree T)
{
int left, right,h;
if (!T) h = 0;
else if (!T->lchild && !T->rchild) h = 1;
else {
left = Depth(T->lchild);
right = Depth(T->rchild);
h = 1 + (left > right ? left : right);
}
return h;
}
主函数
void main()
{
BiTree T;
int i;
T = (BiNode*)malloc(sizeof(BiNode));
cout << "1.创建二叉树 2.输出(先序) 3.输出(中序) 4.输出(后序) 5.返回叶子结点的个数 6.返回二叉树的深度 7.退出" << endl;
while (1)
{
cout << "请输入操作:";
cin >> i;
switch (i)
{
case 1: cout << "请输入树的结点:"; CreateBiTree(T); break;
case 2: cout << "先序遍历输出为:"; PreOrderTraverse(T, Print); cout << endl; break;
case 3: cout << "中序遍历输出为:"; InOrder(T, Print); cout << endl; break;
case 4: cout << "后序遍历输出为:"; PostOrder(T, Print); cout << endl; break;
case 5: cout << "叶子结点的个数为:"; countleaf(T,num); cout<<num << endl; break;
case 6: cout << "该二叉树的深度为:" << Depth(T); cout << endl; break;
case 7: exit(0); break;
default: cout << "请重新输入!" << endl; break;
}
}
}
运行结果如下:
总而言之,二叉树中,最重要的思想是递归,并且所有操作的基础就是遍历。
我总是喜欢把很多东西慢慢攒起来,等到时机成熟的时候一股脑开启,就像是专门为这一刻准备了这么久一样。这很好,但是这总会给我一种幻觉,仿佛我这一段时间,这些年,甚至这一辈子,就是为此而活的。如今我才明白,这是目标,但不是终极目标,更不是那种达成目标之后如释重负到脱离苦海的感觉。你要知道,人生的终极目标是死亡啊,生活并不是达到高潮就开始享受,堕入低谷就昏天黑日。
一生冗长,不失爱与自由,向死而行的生命都在热烈地生长。