数据结构
10.二叉树的表现和实现
1.1二叉树的存储结构
1.顺序存储
二叉树的顺序存储结构就是一组地址连续的存储单元依次自上而下,自左而右地存储二叉树中的结点,并且在存储结点的同时,结点的存储位置(下标)应能体现结点之间的逻辑关系。
那么如何利用数组下标来反映结点之间的逻辑关系呢?性质5中介绍的完全二叉树中结点的编号就可以反映出结点之间的逻辑关系。
顺序实现的特点
1.造成存储空间的浪费:最坏的情况下,一个深度为k且只有k个结点的右单支二又树需要
2
k
−
1
2^k-1
2k−1个存储单元
2.一般只用于一些特殊的场合,如静态的并且结点个数已知的完全二叉树或接近完全二叉树的二叉树。
由于二叉树的每个结点最多有两个孩子,因此可以设置两个指针域left和right,分别指向该结点的左孩子和右孩子,当结点的某个孩子为空时,则相应的指针置为空指针。这种结点结构称为二叉链表结点。
如二叉树中经常进行的操作是寻找结点的双亲,每个结点还可以增加一个指向双亲的指针域parent,根结点的parent指针置为空指针。这种结点结构称为三叉链表结点。
二叉树的二叉链表的表示和实现
template <class elemType>
class BinaryLinkList{
private:
struct Node{ //二叉链表结点
Node *left,*right;//指向左、右孩子的指针
elemType data;//结点的数据域
Node():left(NULL),right(NULL){}//无参构造
Node(elemType value,Node *l=NULL,Node *r=NULL)
{
data=value;
left-=l;
right=r;
}
~Node(){};
};
1.2二叉树的遍历!!!
遍历二叉树,是指按一定的规则和顺序访问二叉树的所有结点,使每一个结点都被访问一次,而且只被访问一次。
由于二又树是非线性结构,因此,二叉树的遍历实质上是将二叉树的各个结点排列成为一个线性序列。
1.深度优先遍历包括:前序、中序和后序遍历
2.广度优先即层次遍历;是指沿着二又树的宽度遍历二又树的结点,即从上至下从左到右逐层遍历二叉树的结点。
前序遍历
NLR(根 左 右):
如果二叉树为空,则操作为空
否则
访问根结点
前序遍历左子树
前序遍历右子树
NRL(根 右 左):与NLR相似。
中序遍历
LNR(左 根 右):
如果二叉树为空,则操作为空
否则
中序遍历左子树
访问根结点
中序遍历右子树
RNL:与LNR相似。
后序遍历
LRN(左 右 根):
如果二叉树为空,则操作为空
否则
后序遍历左子树
后序遍历右子树
访问根结点
RLN:与LRN相似。
例:
二叉树的表现和实现(遍历)8:43
前序(NLR):(A)、(L、B、E)、(C、D、W、X)
中序(LNR):(B、L、E)、(A)、(C)、((W、X)、D)
后序(LRN):(B、E、L)、(((X、W)、D)、C)、(A)、
层次:A、L、C、B、E、D、W、X
递归前序遍历
template <class elemType>
void BinaryLinkList<elemType>::preOrder(Node *t)const{
if(t){
cout <<t->data <<'';//访问当前结点
preOrder(t->left); //前序遍历左子树
preOrder(t->right); //前序遍历右子树
}
}
递归中序遍历
template <class elemType>
void BinaryLinkList<elemType>::preOrder(Node *t)const{
if(t){
preOrder(t->left); //前序遍历左子树
cout <<t->data <<'';//访问当前结点
preOrder(t->right); //前序遍历右子树
}
}
递归后序遍历
template <class elemType>
void BinaryLinkList<elemType>::preOrder(Node *t)const{
if(t){
preOrder(t->left); //前序遍历左子树
preOrder(t->right); //前序遍历右子树
cout <<t->data <<'';//访问当前结点
}
}
层次遍历
由于队列具有先进先出的特点,因此可以借助队列实现层次遍历算法思想:
(1)初始化一个队列,并把根结点入队;
(2)当队列非空时,循环执行步骤3到步骤5,否则遍历结束;
(3)出队一个结点,并访问该结点;
(4)若该结点的左子树非空,则将其的左子树入队;
(5)若该结点的右子树非空,则将其的右子树入队;
二叉树的层次遍历
二叉树的层次遍历
template <class elemType>
void BinaryLinkListcelem Type>:levelOrder Traverse()const{
queue<Node*>que; //STL中的队列
Node *p=root;
if(p)que.push(p); //根结点入队列
while(!que.empty()){ //队列非空
p=que.front(); //取队首元素
que.pop(); //出队
cout<< p->data<<''; //访问当前结点
if(p->left!=NULL)que.push(p->left);//左子树进队列
if(p->right!=NULL)que.push(p->right);//右子树进队列
}
}
1.3二叉树结构的性质
1.已知二叉树的先序序列和中序序列,可以唯一确定一棵二叉树。
2.已知二叉树的后序序列和中序序列,可以唯一确定一棵二叉树;
3.已知二叉树的先序序列和后序序列,不能唯一确定一棵二叉树;
4.已知二叉树的层次序列和中序序列,可以唯一确定一棵二叉树。
(想要唯一确定二叉树,已知中序序列是必要条件)
例:前序 + 中序 唯一确定一棵二叉树
已知二叉树的前序序列:ABDFCEHG,中序序列:DBFAHECG,请唯一确定一棵二叉树。
例:已知二叉树的中序和后序,请唯一确定一棵二叉树。
中序序列:DBFAHECG,(LNR)
后序序列:DFBHEGCA,(LRN)
1.4其他基本运算
求结点总数
1.求二叉树中结点总数
基于前序递归遍历的思想,求二又树的结点总数的算法描述如下:
递归前序遍历二叉树:
(1)若为空子树,则该子树结点数为0;
(2)若子树非空,则该子树的结点总数=1(当前结点)+左子树的结点数+右子树的结点数。
template <class elemType>
int Binarylinklist<elemType>::size(Node *t)const{
if(t==NULL) return 0; //情况(1):空子树
return 1 + size(t->left)+size(t->right);//情况(2):子树非空
求高度
2.求二叉树的高度
基于前序递归遍历的思想,求二叉树的高度的算法描述如下:
递归前序遍历二叉树:
(1)若为空子树,则该子树的高度为0;
(2)若子树非空,则该子树的高度为:左右子树高度大者+1。
template <class elemType>
int BinaryLinkList<elemType>:height(Node *t)const{
if(t==NULL)return 0; //情况(1):空子树
else{ //情况(2):子树非空
int lh=height(t->left),rh=height(t->right);
return 1 +((lh>rh)?lh:rh); //树的高度为左右子树高度大者+1
}
}
求叶子数
3.求叶子数
基于前序递归遍历的思想,求叶子数的算法描述如下:
递归前序遍历二叉树:
(1)若为空子树,则该子树叶结点数为0;
(2)若当前结点没有左右孩子,那么该结点是叶结点,则当前子树叶结点数为1;
(3)若当前结点为非终端结点,则当前子树的叶结点数量=左子树叶结点数+右子树叶结点数。
template <class elemType>
int BinaryLinkList<elemType::leafNum(Node *t)const{
if(t==NULL) return 0; //情况(1):空子树
else if((t->left==NULL)&&(t->right==NULL))
return 1; //情况(2):叶结点
else //情况(3):求左右子树叶子数之和
return leafNum(t->left)+leafNum(t->right);
}