一、二叉树的性质
- 一个二叉树第i层的最大结点数为:2i-1, i>=1。
- 深度为K的二叉树有最大结点总数:2k-1, k>=1。
- 叶结点个数n0 和度为2的非叶结点个数n2 的关系为:n0=n2 +1。
二、二叉树的存储结构
1. 顺序存储结构(数组)
一般二叉树虽然也可以通过补齐为完全二叉树来使用这种结构,但会造成大量空间浪费,所以通常采用链式存储。
2. 链表存储
定义一个二叉树
struct TreeNode {
int data;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : data(x), left(NULL), right(NULL) {} //构造函数
};
三、二叉树的遍历
二叉树的遍历分为前序、中序、后序和层次遍历
1. 递归实现
//中序
void InorderTraversal( TreeNode* BT )
{
if( BT ) {
InorderTraversal( BT->Left );
/* 此处假设对BT结点的访问就是打印数据 */
printf("%d ", BT->Data); /* 假设数据为整型 */
InorderTraversal( BT->Right );
}
}
//前序
void PreorderTraversal( TreeNode* BT )
{
if( BT ) {
printf("%d ", BT->Data );
PreorderTraversal( BT->Left );
PreorderTraversal( BT->Right );
}
}
//后序
void PostorderTraversal( TreeNode* BT )
{
if( BT ) {
PostorderTraversal( BT->Left );
PostorderTraversal( BT->Right );
printf("%d ", BT->Data);
}
}
//层次遍历,用队列辅助实现。
void LevelorderTraversal ( TreeNode* BT )
{
Queue Q;
TreeNode* T;
if ( !BT ) return; /* 若是空树则直接返回 */
Q = CreatQueue(); /* 创建空队列Q */
AddQ( Q, BT ); /* 往队列加元素 */
while ( !IsEmpty(Q) ) {
T = DeleteQ( Q ); /* 删除并取出元素 */
printf("%d ", T->Data); /* 访问取出队列的结点 */
if ( T->Left ) AddQ( Q, T->Left );
if ( T->Right ) AddQ( Q, T->Right );
}
}
2. 非递归实现
非递归算法实现的基本思路是使用堆栈
- 中序遍历
遇到一个结点,就把它压栈,并去遍历它的左子树;
当左子树遍历结束后,从栈顶弹出这个结点并访问它;
然后按其右指针再去中序遍历该结点的右子树。
void InOrderTraversal(TreeNode* BT)
{
TreeNode* T=BT;
Stack S=CreatStack(Maxsize);//创建并初始化堆栈
while(T || !IsEmpty(S) ){
while(T){ //一直向左并把沿途结点压入堆栈
Push(S,T);
T= T->left;
}
if(!IsEmpty(S)){
T = Pop(S); //结点弹出堆栈
printf("%5d", T->data); //访问该结点
T = T->right; //转向右子树(若右子树为空,则继续弹出下一个栈顶元素)
}
}
}
- 前序遍历
前序遍历跟中序遍历类似,区别在于 遇到结点就可以访问,然后压栈并去遍历它的左子树;当左子树遍历结束后,从栈顶弹出这个结点并转向右子树。
程序就变动了printf 的输出位置
void InOrderTraversal(TreeNode* BT)
{
TreeNode* T=BT;
Stack S=CreatStack(Maxsize);//创建并初始化堆栈
while(T || !IsEmpty(S) ){
while(T){ //一直向左并把沿途结点压入堆栈
printf("%5d", T->data); //访问该结点
Push(S,T);
T= T->left;
}
if(!IsEmpty(S)){
T = Pop(S); //结点弹出堆栈
T = T->right; //转向右子树(若右子树为空,则继续弹出下一个栈顶元素)
}
}
}
- 后序遍历
后序遍历比较麻烦,关键在于每个结点都要进栈两次,第二次退栈时才打印结点。第一次出栈说明其左子树已遍历完成,第二次出栈说明右子树也遍历完成,可以输出结点信息。
/*
给数的结点增加一个访问次数(visit)属性
遍历左子树,依次入栈
到底后出栈一个元素,判断访问次数是否为2
若不是,则访问次数为1,访问次数+1,再次入栈,T指向右子树(访问右子树),进入下次循环
若是,则输出,T指向空(左右子树都访问了),进入下次循环
*/
void PostOrderTraversal(TreeNode* BT)
{
TreeNode* T=BT;
Stack S=CreatStack(Maxsize);
while(T||!IsEmpty(s)){
while(T){
T->visit++;//visit初值为0
Push(S,T);
T=T->Left;
}
if(!isEmpty(S)){
T=Pop(S);//出栈判断
if(T->visit==2){
printf("%5d",T->data);
T=NULL;
}
else{
T->visit++;
Push(S,T);//访问次数不等于2,二次入栈
T=T->Right;
}
}
}
}
方法2比较好理解:
先序的访问顺序是root, left, right 假设将先序左右对调,则顺序变成root, right, left,暂定称之为“反序”。后序遍历的访问顺序为left, right,root ,刚好是“反序”结果的逆向输出。
void InOrderTraversal( TreeNode* BT )
{
TreeNode* T=BT;
Stack S = CreatStack( MaxSize ); /*创建并初始化堆栈S*/
Stack Q = CreatStack( MaxSize ); /*创建并初始化堆栈Q,用于输出反向*/
while( T || !IsEmpty(S) ){
while(T){ /*一直向右并将沿途结点压入堆栈*/
Push(S,T);
Push(Q,T);/*将先序遍历到的结点压栈,用于反向*/
T = T->Right; /*转向右子树*/
}
if(!IsEmpty(S)){
T = Pop(S);
T = T->Left; /*转向左子树*/
}
}
while( !IsEmpty(Q) ){
T = Pop(Q);
printf(“%5d”, T->Data);
}
}
3. 遍历二叉树的应用