第1节 二叉树的概念及其性质
2.1.1 二叉树的概念
什么是二叉树?顾名思义,它是每个非叶子结点最多只能有两个儿子的树结构。为了方便研究,我们对结点的两个儿子进行命名和编号,将左边的结点称为左儿子(left child,简写为lchild或lc),右边的结点称为右儿子(right child,简写为rchild或rc),那么以左右儿子为根的子树分别称为“左子树”(left subtree,简写为ltree)和“右子树”(right subtree,简写为rtree)。
一般情况下左右儿子有序,不能相互颠倒。
2.1.2 二叉树的性质
由于二叉树结构的优美性,因此具有很好的性质。
性质1:若二叉树的叶子数为,度为2的结点数为
,则
。
这个性质表明在二叉树中,叶子结点的个数与度为1的结点个数无关。
性质2:深度为k的二叉树,最多只有个结点。
特别地,深度为k,且有个结点的二叉树,称为满二叉树。这种树的特点是每一层上的结点数都是最大结点数。而在一棵二叉树中,除最后一层外,其余层都是满的,并且最后一层要么是满的,要么是在右边缺少连续若干结点,此二叉树称为完全二叉树。显然,深度为k的完全二叉树,至少有
个结点,至多有
个结点。
性质3:具有n个结点的完全二叉树的深度为。
性质4:若将完全二叉树的每个结点从上至下,从左至右进行编号,那么,对于标号为x的点,若x存在左儿子,则左儿子的编号为2x;若x存在右儿子,则右儿子的编号为2x+1。反之,若x有父亲,则父亲的编号为。
第2节 二叉树的存储方法
2.2.1 二叉树的儿子表示法
对每一个结点,存储该结点的左右儿子。
struct node
{
node *lc,*rc;
node()
{
lc=rc=NULL;
}
};
2.2.2 二叉树的数组表示法
int ch[MAXN][2];//ch[x][0]表示x的左儿子,ch[x][1]表示x的右儿子
2.2.3 完全二叉树的数组表示法
根据之前所讲的完全二叉树的性质,只要确定了结点个数n,完全二叉树的形态也就确定了,对于结点x,它的左儿子是2x,右儿子是2x+1,父亲是。因此无需存储任何和树的形态有关的信息,只需要用一个一维数组来表示每个结点上的信息。这种优美的存储方法在接下来“二叉堆”的内容中将非常有用。
第3节 二叉树的遍历
前序、中序、后序遍历是二叉树的三种遍历方式,本质上都属于DFS(深度优先搜索),它们之间的区别仅仅在于根在遍历中的出现顺序:
前序遍历:根—左—右。
void preorder(node *now)
{
if(now==NULL)
return;
std::cout<<now->val<<' ';
preorder(now->lc);
preorder(now->rc);
}
中序遍历:左—根—右。
void inorder(node *now)
{
if(now==NULL)
return;
inorder(now->lc);
std::cout<<now->val<<' ';
inorder(now->rc);
}
后序遍历:左—右—根。
void postorder(node *now)
{
if(now==NULL)
return;
postorder(now->lc);
postorder(now->rc);
std::cout<<now->val<<' ';
}
除了以上三种遍历方式以外,还有一种被称为层序遍历的遍历方式,本质上属于BFS(广度优先搜索)。
void bfs(node *root)
{
std::queue<node*> q;
q.push(root);
while(q.size())
{
node *now=q.front();
q.pop();
if(now==NULL)
continue;
std::cout<<now->val<<' ';
q.push(now->lc);
q.push(now->rc);
}
}
输入一棵二叉树的中序遍历和前序遍历,输出该树的后序遍历。
输入一棵二叉树,输出其前序遍历。
输入一棵二叉树的中序遍历和后序遍历,输出该树的前序遍历。
输入一棵二叉树,输出其深度。
输入一棵二叉树的前序遍历和后序遍历,输出可能的中序遍历的总数。
第4节 树、森林与二叉树的转化
2.4.1 儿子兄弟表示法