二叉树
二叉树的定义
在树的基础上加上两个限制条件即可得到二叉树。
(至于什么是度,什么是树等,可以预先了解下再开始下面关于二叉树的内容)
- 每个子结点最多有两棵子树,二叉树中结点的度只能为0、1、2。
- 子树有左右区别,不能颠倒。
关于二叉树的形态主要关注两个,一个是满二叉树,另一个是完全二叉树;
- 满二叉树:叶子结点(终端结点)都在二叉树的最下面一层。
- 完全二叉树:各个结点编号与深度相同的满二叉树中的结点编号均相同。
- 联系: 完全二叉树就是由满二叉树由右至左、由下至上
依次
删除结点所得,这里不能跳着删除结点,跳着删除就不是完全二叉树。
上图中的左边是满二叉树,而右边就不是所谓的完全二叉树。
二叉树的主要性质
- 性质1:非空二叉树上的叶子节点等于双分支结点数+1
n0 = n2 + 1
( 叶子结点数为 n0 单叶子结点数为 n1 双叶子节点数 n2) - 性质2:二叉树的第 i 层上最多由2 i-1(i ⩾ \geqslant ⩾ 1) 个结点
- 性质3:高度(深度)为 k 的二叉树最多由2 k-1(k ⩾ \geqslant ⩾ 1)个结点
- 性质4:函数Catalan():给定 n 个结点,能构成H(n)种不同的二叉树,代码: H ( x ) = C 2 n n n + 1 H(x)=\frac{C^n_2n }{n+1} H(x)=n+1C2nn
- 性质5:具有 n (n)个结点的完全二叉树的高度(深度)为 ⌊ l o g 2 n ) ⌋ \lfloor log_2n) \rfloor ⌊log2n)⌋+1
二叉树的存储结构
如果前面有关于链表或者队列那两篇博客的同学,这边应该大概明白二叉树的存储结构也分为两种,分别是顺序存储和链式存储。
1.顺序存储结构即利用数组来存储二叉树,这里一般常用于存储完全二叉树,存储一般二叉树相对比较浪费空间,因为顺序存储所分配的内存是连续的。不过这样存储仅仅只能存储结点编号信息,局限性比较大!
2.链式存储结构即利用链接的方式将一个个结点连接起来。具体的结点类型定义如下:
typedef struct BTNode
{
int data; // // data store the node's data field (default type int)
struct BTNode *lchild;
struct BTNode *rchild;
}BTNode;
二叉树的遍历算法
前序遍历
前序遍历的操作过程如下:
如果二叉树为空树则不需要进行,
否则:
1) 访问根节点
2) 前序遍历左子树
3) 前序遍历右子树
/*****************************************************************************
Function : Operate_BTNode
Description : operate the node ,like print the data
Input : BTNode p
Output : void
Return : void
*****************************************************************************/
void Operate_BTNode(BTNode *p)
{
cout << "the p->data is :" << p->data << endl;
cout << "the p->lchild is :" << p->lchild << endl;
cout << "the p->rchild is :" << p->rchild << endl;
}
/*****************************************************************************
Function : preorder
Description : the preorder travesal
Input : BTNode p
Output : void
Return : void
*****************************************************************************/
void preorder(BTNode *p)
{
if(p != NULL) // condition judgement (whether the p is NULL)
{
Operate_BTNode(p); // show the info in p
preorder(p->lchild);
preorder(p->rchild);
}
}
中序遍历
中序遍历的操作过程如下:
如果二叉树为空树则不需要进行,
否则:
1) 中序遍历左子树
2) 访问根节点
3) 中序遍历右子树
/*****************************************************************************
Function : inorder
Description : the inorder travesal
Input : BTNode p
Output : void
Return : void
*****************************************************************************/
void inorder(BTNode *p)
{
if(p != NULL) // condition judgement (whether the p is NULL)
{
preorder(p->lchild);
Operate_BTNode(p); // show the info in p
preorder(p->rchild);
}
}
后序遍历
后序遍历的操作过程如下:
如果二叉树为空树则不需要进行,
否则:
1) 后序遍历右子树
2) 后序遍历左子树
3) 访问根节点
/*****************************************************************************
Function : postorder
Description : the postorder travesal
Input : BTNode p
Output : void
Return : void
*****************************************************************************/
void postorder(BTNode *p)
{
if(p != NULL) // condition judgement (whether the p is NULL)
{
preorder(p->lchild);
preorder(p->rchild);
Operate_BTNode(p); // show the info in p
}
}
层序遍历
层序遍历比较容易理解,按照树一层层来进行遍历。
/*****************************************************************************
Function : level
Description : the level travesal
Input : BTNode p
Output : void
Return : void
*****************************************************************************/
void postorder(BTNode *p)
{
int front = 0,rear = 0;
BTNode *queue[MaxSize]; // define a queue
BTNode *q;
if(p != NULL)
{
rear = (rear+1) % MaxSize;
queue[rear] = p; // enqueue the root node
while(front != rear) // the queue isn't empty
{
front = (front+1) % MaxSize;
q = queue[front];
Operate_BTNode(p);
if(q->lchild != NULL)
{
rear = (rear+1) % MaxSize;
queue[rear] = q->rchild;
}
if(q->rchild != NULL)
{
rear = (rear+1) % MaxSize;
queue[rear] = q->rchild;
}
}
}
}
思考
上面三种遍历方式是利用递归来实现的,相对于非递归的遍历效率肯定低不少,后面会再进一步引入优化算法。主要通过上面的递归算法让我们了解下二叉树的前中后序三种遍历方式的具体过程。
接下来通过实例来回顾下遍历方式:
下图的前中后序遍历的结果分别是什么呢?
按照前面的步骤来是说,答案是显而易见的:
- 前序遍历输出序列为:A,B,C,D,E,F,G,H
- 中序遍历输出序列为:C,B,E,D,F,A,H,G
- 后续遍历输出序列为:C,E,F,D,B,H,G,A
- 层序遍历输出序列为:A,B,G,C,D,H,E,F