基础概念
- 结点:数据结构的基本组成单位
- 树:n个点的有限集合,n=0的树为空树,在任意非空树中:
a. 有且仅有一个根节点
b. n>1时其他节点可以分为m个互不相交的集合,其中每个集合本身有时一棵树,同时为根的子树,如果集合发生相交,则形成一张图。 - 度:图中有入度和出度,在树中,度数为指向子节点的数量
- 结点关系:
a. 父节点(双亲节点):当前结点向上指向的结点,仅有一个
b. 子节点(孩子结点):当前结点向下指向的结点,有多个
c. 兄弟节点:具有同一个父节点 - 叶子结点:无子节点的结点
- 树的深度(节点层次):如图所示,最大的层次即为树的深度
二叉树
在上述定义的基础上,一个根节点做多仅有两个子节点的树,称为二叉树
二叉树有左子树和右子树,二者顺序不能颠倒
二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树组成。
二叉树的特点:
每个节点最多有两颗子树
左右子树是由顺序的(不同的遍历方式会得到不同的结果)
即是包含空节点,也要明确顺序
二叉树的性质:
- 第i层上最多有2^(i-1)个结点
- 深度为k的二叉树最多有(2^k)-1个结点
- n0=n2+1 n0表示度数为0的节点数,n2表示度数为2的节点数
- 在完全二叉树中,具有n个节点的完全二叉树的深度为[log2n]+1,其中[log2n]是向下取整。
- 若对含 n 个结点的完全二叉树从上到下且从左至右进行 1 至 n 的编号,则对完全二叉树中任意一个编号为 i 的结点有如下特性
(1) 若 i=1,则该结点是二叉树的根,无双亲, 否则,编号为 [i/2] 的结点为其双亲结点;
(2) 若 2*i>n,则该结点无左孩子, 否则,编号为 2*i 的结点为其左孩子结点;
(3) 若 2*i+1>n,则该结点无右孩子结点, 否则,编号为 2*i+1 的结点为其右孩子结点。
特殊的树:
斜树,只有左子树(左斜树)或者只有右子树(右斜树)的二叉树
满二叉树,叶子结点均在同一层上,并且其余结点均具有左右节点,因此相同深度的二叉树,满二叉树结点最多,叶子最多
完全二叉树,满二叉树的叶子结点从右向左删除,即形成完全二叉树,满足以下几点
叶子结点仅出现在最下层和次下层,且最下层叶子结点在左边,次下层叶子结点在右边
叶子结点在每一层一定连续
如果节点度数为1则只有左子树没有右子树
同样节点数量的二叉树,完全二叉树深度最小
满二叉树一定是完全二叉树,反之不然
存储方式
顺序存储(指针模拟)
利用数组下标模拟树的节点坐标;
根据二叉树的性质,假定一个节点下标为i,则其父亲节点为i/2,其左节点为i*2,右节点为i*2+1;
但也正因如此,只有当树为完全二叉树的时候才能填数组,其他情况会有数组中存放空节点。
该种存储方式主要用在堆中。
二叉链表
通过链式存储,通过一个数据域两个或三个(包含父指针)指针域构成一个节点,过程类似链表的插入。
结点的定义:
struct node{
int data;
node *l,*r;
node *h;//指向父结点
}
使用数组模拟的方式较为简单再次就不赘述了,主要以链式存储为进行介绍。
二叉树的遍历:
前序遍历
中序遍历
后序遍历
层序遍历
所谓前中后序实际上就遍历到父节点的顺序,前序为父-左-右,中序为左-父-右,后序为左-右-父。
这三种不同的遍历序列,可以建出相同的树。给定中序遍历序列,另外再给出前序或后序遍历的序列即可确定一颗二叉树。
层序遍历即是按照一层一层的顺序进行,就是对树进行了一次BFS
二叉树的建立:
这里使用“,”表示空节点。
struct node{
int data;
node *l,*r;
}
node* buildtree(node *root)
{
if(a[cnt]!=',')
{
root=new node;
root->data=a[cnt++];
root->l=bulidtree(root->l);
root->r=bulidtree(root->r);//前序遍历序列生成的树。
}else {
root=NULL;
cnt++;
}
return root;
}
如果想根据其他遍历序列建树可以:
中序遍历
node* buildtree(node *root)
{
if(a[cnt]!=',')
{
root=new node;
root->l=bulidtree(root->l);
root->data=a[cnt++];
root->r=bulidtree(root->r);
}else {
root=NULL;
cnt++;
}
return root;
}
后序遍历
node* buildtree(node *root)
{
if(a[cnt]!=',')
{
root=new node;
root->l=bulidtree(root->l);
root->r=bulidtree(root->r);
root->data=a[cnt++];
}else {
root=NULL;
cnt++;
}
return root;
}
树的遍历的实现:
前中后序
如果已经建好了一棵树,想要得到他的三种序列,可以:
void preorder(node root)
{
if(root)
{
cout<<data; preorder(root->l);
preorder(root->r);
}
}
void midorder(node root)
{
if(root)
{
midorder(root->l);
cout<data; midorder(root->r);
}
}
void postorder(node root)
{
if(root)
{
cout<data; postorder(root->l);
postorder(root->r);
}
}
层序遍历:
一般是一个简单的BFS,需要借助到队列;
struct node{
int data;
node *l,*r;
}
queue <node*> q;
q.push(&root);
while(!q.empty())
{
node *t;
t=q.front();
cout<data; q.pop() if(t->l)
q.push(t->l);
if(t->r)
q.push(t->r);
}
根据不同遍历序列生成一棵树
根据前序或后序序列找到根节点;
找到根节点后,在中序序列找到该节点;
通过中序序列重新在对左右子树进行上述操作(递归)
结束条件是定位出的根节点是当前序列中的唯一一个
先序和中序
string pre,mid;
struct node
{
char data;
node *l,*r;
};
node* build(int f,int l,int r,node* root)
{
int i=l;
while(pre[f]!=mid[i])
{
i++;
}
root=new node;
root->data=mid[i];
if(l<=i-1)
{
root->l=build(f+1,l,i-1,root->l);
}else root->l=NULL;
if(i+1<=r)
{
root->r=build(f+i-l+1,i+1,r,root->r);
}else root->r=NULL;
return root;
}
后序和中序
后序和先序类似,区别在与递归时后序序列中根节点的下标值,
node* build(int f,int l,int r)
{
int i=l;
while(post[f]!=mid[i])
{
i++;
}
node *root=new node;
root->data=mid[i];
if(l<=i-1)
{
root->l=build(f-(r-i+1),l,i-1);
}else root->l=NULL;
if(i+1<=r)
{
root->r=build(f-1,i+1,r);
}else root->r=NULL;
return root;
}

