一、二叉树的ADT
首先我们定义二叉树的结点Node
struct Node
{
int data;
Node* left;
Node* right;
Node(int _data):data(_data),left(0),right(0){}
bool isLeaf() {return left==NULL && right==NULL;}
};
然后我们定义二叉树的抽象类型:要注意的是二叉树只需要一个成员变量Node* root指向树根。如果root为空指针,则此二叉树为空树
typedef stack<Node*> Stack;
typedef queue<Node*> Queue;
class BinaryTree
{
private:
//what we only need is a root node
Node* root;
public:
BinaryTree():root(0){}
BinaryTree(Node* _root):root(_root){}
~BinaryTree(){destroy(root);}
void destroy(Node* n) {/*按照后序遍历的思想,先递归析构左右子树*/}
void iterate_vlr(Node* v);
void iterate_vlr() {iterate_vlr(getTree());}
void iterate_lvr(Node* v);
void iterate_lvr() {iterate_lvr(getTree());}
void iterate_lrv(Node* v);
void iterate_lrv() {iterate_lrv(getTree());}
void queue_layer(Node* v);
void queue_layer() {queue_layer(getTree());}
void stack_vlr(Node* v);
void stack_vlr() {stack_vlr(getTree());}
void stack_lvr(Node* v);
void stack_lvr() {stack_lvr(getTree());}
void stack_lrv(Node* v);
void stack_lrv() {stack_lrv(getTree());}
bool isEmpty() {return root==0;}
Node* getTree(){return root;}
};
二、二叉树的深度优先递归遍历
这个很简单,cout加在哪里就是什么遍历
void BinaryTree::iterate_vlr(Node* v)
{
if (v==NULL)
{
//cout<<"the tree is empty"<<endl;
return;
}
cout<<(v->data)<<' ';
iterate_vlr(v->left);
iterate_vlr(v->right);
}
三、二叉树的深度非递归遍历
要想搞清非递归遍历背后的数学规律,我们有必要研究一下二叉树遍历的函数帧(stack frame)。
我们先忽略cout输出,只关注函数栈的变化
以如图二叉树为例:
函数帧应当如图所示:
从函数帧本身来说,函数每发生一次递归调用,栈就会升高一个单位;每接受一次返回,栈就会降低一个单位。因此每发生一次调用,栈必然或升或降一个单位,既不会与上一次调用持平,也不会升降更多。
从二叉树来说,每个非空结点都会发出两次调用,因此必然接受两次返回。
现在我们把栈顶用曲线连接起来,我们定义:
“单调递增点”就是指由递增产生的,并且继续递增的点,“单调递增边沿”就是由相邻的这些点组成的边沿。
同理也可以定义“单调递减边沿”。
结合cout出现的位置,我们发现:
i. 后序遍历的结果出现在单调递减边沿。
ii. 前序遍历的结果出现在单调递增边沿。
iii. 中序遍历的结果出现在“波谷”。
要想把这些发现转化成代码,还需要研究两种边沿和波谷的产生条件。
i. 有且只有栈的push操作才能产生递增边沿。对于二叉树来讲,(a)沿着左子树深搜以及(b)第一次到达右子树的根结点,这两种情况能引发入栈。
ii. 有且仅有栈的pop操作才能产生递减边沿。对于二叉树来讲,(a)左右子树都为空以及(b)左右子树都完成访问,这两种情况能引发出栈。
iii. 对于二叉树来说,(a)访问到叶子节点会产生波谷。同时,(b)如果某时刻一个节点的左子树已经访问完毕,但是还没有访问右子树,那么这个节点此时也在波谷。
void BinaryTree::stack_traverse(Node* v)
{
Node* now = v;
Node* tmp = 0; //tmp是用来记忆右子树是否被访问过的
Stack s;
while (now != 0 || !s.empty())
{
while (now != 0) //这个循环是用来深搜的
{
s.push(now);
/*cout在这里就是先序遍历*/
now = now->left;
}
now = s.top();
if (now->right != tmp || now->right==0)
{
/*cout在这里就是中序遍历*/
}
if (now->right==0 || now->right == tmp)
{
tmp = now;
s.pop();
/*cout在这里就是中序遍历*/
now = 0;
//now=0确保执行下一轮主循环的时候,跳过深搜直接now=top
}
else
now = now->right;
}
cout<<endl;
}
这里我想解释一下两个if的条件为什么要这么写
第一个if很好解释:now->right != tmp就是右子树还没访问 now->right==0就是右子树为空。这是中序遍历(波谷)的条件
第二个if:如果now->right==0,那么为了模拟递归,我至少要做三件事:1. 记忆当前结点,代表当前结点已经被访问; 2.弹栈; 3.让now==0,目的是确保执行下一轮主循环的时候,跳过深搜直接now=top
附: 二叉树的广度层遍历:
inline bool visit(Node* T)
{
if (T)
{
printf("%d ", T->data);
return true;
}
else return false;
}
void BinaryTree::queue_layer(Node* v)
{
Queue q;
Node* n = v;
if (visit(n))
{
q.push(n);//push root
}
while (!q.empty())
{
n = q.front();
q.pop(); //pop this layer
if (visit(n->left))
q.push(n->left); //push next layer
if (visit(n->right))
q.push(n->right); //push next layer
}
cout<<endl;
}