一.使用递归的方法进行二叉树的遍历
二叉树是一种非常重要的数据结构,很多其他数据机构都是基于二叉树的基础演变过来的。二叉树有前、中、后三种遍历方式,因为树的本身就是用递归定义的,因此采用递归的方法实现三种遍历,不仅代码简洁且容易理解,但其开销也比较大,而若采用非递归方法实现三种遍历,则要用栈来模拟实现(递归也是用栈实现的)。
三种遍历方式的递归实现
1、先序遍历——按照“根节点-左孩子-右孩子”的顺序进行访问。
1.void pre_traverse(BTree pTree)
2.{
3. if(pTree)
4. {
5. printf(”%c ”,pTree->data);
6. if(pTree->pLchild)
7. pre_traverse(pTree->pLchild);
8. if(pTree->pRchild)
9. pre_traverse(pTree->pRchild);
10. }
11.}
2、中序遍历——按照“左孩子-根节点-右孩子”的顺序进行访问。
1.void in_traverse(BTree pTree)
2.{
3. if(pTree)
4. {
5. if(pTree->pLchild)
6. in_traverse(pTree->pLchild);
7. printf(”%c ”,pTree->data);
8. if(pTree->pRchild)
9. in_traverse(pTree->pRchild);
10. }
11.}
3、后序遍历——按照“左孩子-右孩子-根节点”的顺序进行访问。
1.void beh_traverse(BTree pTree)
2.{
3. if(pTree)
4. {
5. if(pTree->pLchild)
6. beh_traverse(pTree->pLchild);
7. if(pTree->pRchild)
8. beh_traverse(pTree->pRchild);
9. printf(”%c ”,pTree->data);
10.}
二.非递归进行二叉树遍历(可以借助栈实现二叉树的递归遍历转换为非递归遍历)
先序遍历算法基本思路:使用堆栈
a. 遇到一个节点,访问它,然后把它压栈,并去遍历它的左子树;
b. 当左子树遍历结束后,从栈顶弹出该节点并将其指向右儿子,继续a步骤;
c. 当所有节点访问完即最后访问的树节点为空且栈空时,停止。
typedef struct TreeNode *PtrToNode;
typedef struct TreeNode *BinTree;
struct TreeNode
{
int Data; //为简单起见,不妨假设树节点的元素为int型
BinTree Left;
BinTree Right;
};
void PreOrderTraversal(BinTree BT)
{
BinTree T = BT;
Stack S = CreatStack(MAX_SIZE); //创建并初始化堆栈S
while(T || !IsEmpty(S))
{
while(T) //一直向左并将沿途节点访问(打印)后压入堆栈
{
printf("%d\n", T->Data);
Push(S, T);
T = T->Left;
}
if (!IsEmpty(S))
{
T = Pop(S); //节点弹出堆栈
T = T->Right; //转向右子树
}
}
}
中序遍历非递归辅助栈实现
void InOrderTraversal(BinTree BT)
{
BinTree T = BT;
Stack S = CreatStack(MaxSize); //创建并初始化堆栈S
while(T || !IsEmpty(S))
{
while(T) //一直向左并将沿途节点压入堆栈
{
Push(S,T);
T = T->Left;
}
if(!IsEmpty(S))
{
T = Pop(S); //节点弹出堆栈
printf("%d\n", T->Data); //(访问) 打印结点
T = T->Right; //转向右子树
}
}
}
后序遍历非递归辅助栈实现
思想:因为后序非递归遍历二叉树的顺序是先访问左子树,再访问右子树,最后访问根节点。当用栈来存储结点,必须分清返回根节点时,是从左子树返回,还是从右子树返回。使用辅助指针r,指向最近访问过的结点。也可以在结点中增加一个标志域,记录是否已被访问。
struct BiTreeNode
{
int data;
BiTreeNode* lchild;
BiTreeNode* rchild;
};
void PostOrder(BiTreeNode* T)
{
stack s;
BiTreeNode*p = T, *r = NULL, temp;
while (p || !s.empty())
{
if (p)
{
s.push(p);
p = p->lchild;
}
else
{
p = s.top();
if (p->rchild&&p->rchild != r)
{
p = p->rchild;
s.push(p);
p = p->lchild;
}
else
{
p = s.top();
s.pop();
cout << p->data;
r = p;
p = NULL;
}
}//else
}//while
}
二叉树遍历的核心问题是二维结构的线性化。我们通过节点访问其左右儿子时,存在的问题是访问左儿子后,右儿子怎么访问。因此我们需要一个存储结构保存暂时不访问的节点。前面三种遍历方式的非递归实现,我们是通过堆栈来保存。事实上也可以通过队列来保存。
队列实现的基本思路:遍历从根节点开始,首先将根节点入队,然后执行循环:节点出队,访问(访问)根节点,将左儿子入队,将右儿子入队,直到队列为空停止。
这种遍历方式的结果是将二叉树从上到下,从左至右一层一层的遍历,即层序遍历,代码实现如下:
void LevelOrderTraversal(BinTree BT)
{
BinTree T;
Queue Q; //声明一个队列
if (BT == NULL)
return; //如果树为空,直接返回
Q = CreatQueue(MAX_SIZE); //创建并初始化队列
AddQ(Q, BT); //将根节点入队
while (!IsEmpty(Q))
{
T = DeleteQ(Q); //节点出队
printf("%d\n", T->Data); //访问出队的节点
if (T->Left) AddQ(Q, T->Left); //若左儿子不为空,将其入队
if (T->Right) AddQ(Q, T->Right) //若右儿子不为空,将其入队 }
}