二叉树的建立和深度优先遍历

本文详细介绍了二叉树的数据结构定义及其实现,并深入探讨了二叉树的深度优先遍历(包括递归和非递归实现)与广度优先遍历的方法,通过解析函数帧揭示了遍历算法背后的数学规律。

一、二叉树的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;
}


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值