在学习二叉树时,必须要掌握的要点包括:二叉树的创建,遍历和了解二叉树的相关特性(二叉树的深度、高度、层数等等),这部分的代码实现很多教材直接以递归的方式展现,递归是个很好的编程思想,但是确实不是很容易理解,如果使用不当的话,往往写出来的程序运行效率较低,无论在耗费的时间还是占用的内存空间上比非递归较多,而且非递归能够将数据元素的出栈与进栈过程描述得更为清晰。因此我采用非递归的方式实现一遍。由于关于二叉树的非递归遍历,网上有很多代码可参考,我就不写了,这里只说树的建立和树的深度。
首先是二叉树的先序建立。二叉树的先序创建与先序遍历有些相似,只不过不同的地方在于,创建的过程中无法确定是添加左节点还是添加右节点,于是需要用一个标识Tag来记录当前的状态。大致过程就是遇到合法节点就压栈操作,遇到空节点则出栈。大致分为2个情况做相应的处理:
1.数据元素不是"*"(*代表空节点):如果Tag=‘L’,则表明当前要创建的节点作为其上一级父节点的左子节点,此时需要把新增元素入栈;如果Tag='R',则表名明当前要创建的节点作为其上一级父节点的右子节点,此时仍需要把新增元素入栈,然后将Tag重置为'L',因为我们下面要访问新增节点的左子节点。
2.数据元素是“*”(*代表空节点):如果Tag='L',则表明栈顶元素的左子节点为空,此时需要将Tag置为'R',去访问栈顶元素的右子节点;如果Tag='R',则表明栈顶元素的右子节点也为空,此时要出栈,当然,还需要利用循环判断出栈前的栈顶元素是不是上级父节点的右子节点。如果是,则说明上级父节点的左右子节点均已处理完毕,应继续出栈。具体的处理代码如下:
#define ElementType char
#define Maxsize 100
typedef struct BinaryTree
{
ElementType data;
struct BinaryTree *lchild;
struct BinaryTree *rchild;
};
typedef struct SeqStack
{
struct NodeFlag *data[Maxsize];
int top;
};
typedef struct NodeFlag
{
struct BinaryTree *btnode;
int flag;//0为第一次访问,1为第二次访问,为1时,允许出栈
};
BinaryTree* CreateBinaryTreePreOrder(char *str)
{
BinaryTree *bttree=NULL;
BinaryTree *p=NULL;
SeqStack *s=NULL;
s=CreateStack();
char arr[Maxsize];
char Tag='L';//标志创建左子树还是右子树,L代表创建左子树,R代表创建右子树
strcpy( arr , str );
int i=1;
if(arr[0]=='*')
{
printf("Input Error.\n");
exit(1);
}
//创建根节点并入栈
p=(BinaryTree*)malloc(sizeof(BinaryTree));
if(p==NULL)
{
printf("Init Failed.\n");
exit(1);
}
p->data=arr[0];
p->lchild=NULL;
p->rchild=NULL;
bttree=p;
NodeFlag *_node=(NodeFlag*)malloc(sizeof(NodeFlag));
_node->btnode=bttree;
s->data[++s->top]=_node;
while(arr[i]!='\0')
{
if(arr[i]!='*') //非空节点
{
BinaryTree *temp=(BinaryTree*)malloc(sizeof(BinaryTree));
temp->data=arr[i];
temp->lchild=NULL;
temp->rchild=NULL;
//NodeFlag *node=s->data[s->top];
//p=node->btnode;
if(Tag=='L')
{
//开始创建左节点
s->data[s->top]->btnode->lchild=temp;
p=temp;
NodeFlag *temp_node=(NodeFlag*)malloc(sizeof(NodeFlag));
temp_node->btnode=temp;
s->data[++(s->top)]=temp_node;
}
else
{
//创建右节点
s->data[s->top]->btnode->rchild=temp;
p=temp;
NodeFlag *temp_node=(NodeFlag*)malloc(sizeof(NodeFlag));
temp_node->btnode=temp;
Tag='L';
s->data[++(s->top)]=temp_node;
}
}
else //当前空节点
{
if(Tag=='L')
{
Tag='R';
}
else
{
//出栈
BinaryTree *temp=s->data[s->top]->btnode;
s->data[s->top]=NULL;
s->top--;
while(s->top>-1&&temp==s->data[s->top]->btnode->rchild)
{
//如果刚出站的元素是其父节点的右孩子,则继续退栈,直到尚未处理左右孩子的上级节点
temp=s->data[s->top]->btnode;
s->data[s->top]=NULL;
s->top--;
}
}
}
i++;
}
return bttree;
}
求二叉树的深度,从根结点到叶子结点形成最长路径的长度为树的深度。解决这个问题,只要比较所有根——叶子节点路径的长度,然后找出最大的即可。因此,想到了利用二叉树后序遍历的方法求出,因为根据二叉树后序遍历的定义,先访问根结点的左子节点,然后访问根结点的右子节点,最后访问根结点。如果节点值非空,首先应该进入该节点的左子树访问,此时由于该节点的右子树及根结点尚未访问,因此必须将该节点保存起来,放入栈中,以便访问完左子树后,从栈中取出该节点,进行其右子树及根结点的访问。确切的说,当一个元素位于栈顶即将处理时,其左子树的访问一定已经完成,如果其右子树尚未遍历,接下来应该进入其右子树的访问,而此时该栈顶元素是不能出栈的,因为其根结点还未被访问;只有等到其右子树也访问完成后,该栈顶元素才能出栈。所以,后序遍历能够找出最长的路径,每次节点进栈后,求出当前栈的大小,遍历结束后返回最大的栈长度即为树的深度。下面是处理代码:
int TreeDeep(BinaryTree *bt)
{
if(NULL==bt) return 0;
BinaryTree *p=NULL;
SeqStack *s=NULL;
s=CreateStack();
p=bt;
int depth=0;
while(p!=NULL||s->top>-1)
{
if(p!=NULL)
{
NodeFlag *node=(NodeFlag*)malloc(sizeof(NodeFlag));
node->flag=0;
node->btnode=p;
s->data[++(s->top)]=node;
if((s->top)+1>depth)
depth=(s->top)+1;
p=p->lchild;
}
else
{
NodeFlag *_node=s->data[s->top];
if(_node->flag==0) //开始遍历右孩子
{
_node->flag=1;
p=_node->btnode->rchild;
}
else
{
s->data[s->top]=NULL;
s->top--;
}
}
}
return depth;
}
输入:AB*DE**F**C*G**
中序遍历结果:BEDFACG
深度:4.