二叉树(Binary Tree
)是 n(n≥0) n ( n ≥ 0 ) 个结点的有限集合, 该集合或者为空集(称为空二叉树),或者由一个根节点和两棵互不相交的、分别称为根节点的左子树和右子树的二叉树组成
2
的结点斜树:所有结点都只有左子树的二叉树叫左斜树,对应有右斜树,统称为斜树
满二叉树:所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上的二叉树
2
完全二叉树: 对一棵具有n
个结点的二叉树按层序编号,如果编号为 i(1≤i≤n) i ( 1 ≤ i ≤ n ) 的结点与同样深度的满二叉树中编号为i
的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树
1
,则该结点只有左孩子,即不存在只有右子树的情况i
层上至多有 2i−1 2 i − 1 个结点 (i≥1) ( i ≥ 1 ) k
的二叉树至多有 2k−1 2 k − 1 个结点 (k≥1) ( k ≥ 1 ) T
, 如果其终端结点数为 n0 n 0 , 度为2
的结点树为 n2 n 2 , 则 n0=n2+1 n 0 = n 2 + 1 n
个结点的完全二叉树的深度为 ⌊log2n⌋+1 ⌊ l o g 2 n ⌋ + 1 (⌊x⌋表示不大于x的最大整数) ( ⌊ x ⌋ 表 示 不 大 于 x 的 最 大 整 数 ) n
个结点的完全二叉树(其深度为 ⌊log2n⌋+1 ⌊ l o g 2 n ⌋ + 1 )的结点按层序编号(从第1
层到第 ⌊log2n⌋+1 ⌊ l o g 2 n ⌋ + 1 层,每层从左到右),对任一结点 i(1≤i≤n) i ( 1 ≤ i ≤ n ) 有: i = 1
, 则结点i
是二叉树的根,无双亲;如果 i>1 i > 1 , 则其双亲是结点 ⌊i/2⌋ ⌊ i / 2 ⌋ i
无左孩子(结点i
为叶子结点); 否则即 2i≤n 2 i ≤ n 时其左孩子是结点 2i 2 i i
无右孩子;否则 2i+1≤n 2 i + 1 ≤ n 时, 其右孩子是结点 2i+1 2 i + 1 顺序存储结构一般只用于 完全二叉树,否则会造成空间浪费
二叉树每个结点最多有两个孩子,故结点结构为一个数据域和两个指针域
/* 二叉树的二叉链表结点结构定义 */
typedef struct BiTNode
{
TElemType data; // 结点数据
struct BiTNode *lchild, *rchild; // 左右孩子指针
}BiTNode, *BiTree;
二叉树的遍历(traversing binary tree)是指从根节点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。 由前序和后序无法得到唯一二叉树
规则是若二叉树为空,则空操作返回,否则先返回根节点,然后前序遍历左子树,再前序遍历右子树。下图遍历顺序为ABDGHCEIF
void PreOrderTraverse(BiTree T)
{
if(T == NULL)
return;
printf("%c", T->data);
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
思想: 由前序遍历顺序可知,首先访问根节点,然后分别访问左子树和右子树。对于每个子树来说,以同样的顺序进行
步骤
p
, 然后将其入栈p
的左子树是否为空,若不为空,将p
左子树作为当前的结点p
。若为空,则取出栈顶结点并进行出栈操作,并将栈顶结点的右子树作为当前结点p
p
为NULL
并且栈为空,遍历结束void PreOrderTraverseNoRecursive(BiTree T)
{
stack s;
BiTree p = T;
while(p != NULL || !s.empty())
{
while(p != NULL)
{
printf("%c", p->data);
s.push(p);
p = p->lchild;
}
if(!s.empty())
{
p = s.top();
s.pop();
p = p->rchild;
}
}
}
规则是若树为空,则空操作返回,否则从根节点开始(注意并不是先访问根节点),中序遍历根节点的左子树,然后是访问根节点,最后中序遍历右子树。下图遍历顺序为GDHBAEICF
void InOrderTraverse(BiTree T)
{
if(T == NULL)
return;
InOrderTraverse(T->lchild);
printf("%c", T->data);
InOrderTraverse(T->rchild);
}
思想:按中序遍历顺序,先访问左子树,再访问根节点,后访问右子树。对于每个子树来说,按同样顺序进行遍历。
步骤
p
的左子树不为空,则将p
入栈,并将p
左子树置为当前节点,按相同规则进行p
置为该栈顶结点的右子树 p
为NULL
并且栈为空遍历结束void InOrderTraverseNoRecursive(BiTree T)
{
stack s;
BiTree p = T;
while(p != NULL || !s.empty())
{
while(p != NULL)
{
s.push(p);
p = p->lchild;
}
if(!s.empty())
{
p = s.top();
s.pop();
printf("%c", p->data);
p = p->rchild;
}
}
}
规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根节点。下图遍历顺序为GHDBIEFCA
void PostOrderTraverse(BiTree T)
{
if(T == NULL)
return;
PostOrderTraverse(T->lchild);
PostOrderTraverse(T->rchild);
printf("%c", T->data);
}
思想:后序遍历的非递归版本比较复杂,使用一个栈的话,过程比较繁琐。此处选择使用两个栈。
步骤
p
入栈一s
st
s
s
为空st
,且按照后序遍历的顺序存放,直接全部出栈,访问结点即可void PostOrderTraverseNoRecursive(BiTree T)
{
if(!T)
return;
stack s, st;
BiTree p;
s.push(T);
while(!s.empty())
{
p = s.top();
st.push(p);
s.pop();
if(p->lchild)
s.push(p->lchild);
if(p->rchild)
s.push(p->rchild);
}
while(!st.empty())
{
printf("%c", st.top()->data);
s.pop();
}
}
规则是若树为空,则空操作返回,否则从树的第一层,也就是根节点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。下图遍历顺序为ABCDEFGHI
思想:层序遍历由于层级的关系,需要使用队列存储,从左到右,从上到下。依次将该结点,该结点的左子树,该结点的右子树入队,即可保证顺序是层序排列。
步骤
/* 二叉树的层序遍历算法 */
void LevelOrderTraverse(BiTree T)
{
if(T == NULL)
return;
queue q;
BiTree p;
q.push(T);
while(!q.empty())
{
p = q.front();
printf("%c", p->data);
q.pop();
if(p->lchild)
q.push(p->lchild);
if(p->rchild)
q.push(p->rchild);
}
}
/* 按前序输入二叉树中结点的值(一个字符), # 表示空树,构造二叉链表表示二叉树 T */
void CreateBiTree(BiTree &T)
{
TElemType ch;
scanf("%c", &ch);
if(ch == '#')
*T = NULL;
else
{
(*T) = (BiTree)malloc(sizeof(BiTNode));
if(!*T)
exit(OVERFLOW);
(*T)->data = ch; // 生成根节点
CreateBiTree(&(*T)->lchild); // 构造左子树
CreateBiTree(&(*T)->rchild); // 构造右子树
}
}
有关二叉树的完整代码示例 code
有关于树的算法题还是很多的,后续会整理相关知识。Fighting