Bootstrap

二叉树遍历及其应用

二叉树的三种遍历算法的递归实现

特别说明:函数形参中的 void(*visit)(BinTreeNode *p) 代表:采用一个visit的指针指向某个函数,该函数的形参为BinTreeNode *p,该函数的实现体中可以对结点进行处理,传参时将外部函数名传入即可
前、中、后的差别仅在于 visit(subTree) 该语句的位置不同

1、前序遍历

template<class T>
void PreOrder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p)) {
	if (subTree != nullptr) {
		visit(subTree);
		PreOrder(subTree->leftChild, visit);
		PreOrder(subTree->rightChild, visit);
	}
}

2、中序遍历

template<class T>
void InOrder(BinTreeNode<T> *subTree, void (*visit)(BinTreeNode<T> *p)) {
	if (subTree != nullptr) {
		InOrder(subTree->leftChild, visit);
		visit(subTree);
		InOrder(subTree->rightChild, visit);
	}
}

3、后序遍历

template<class T>
void PostOrder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p)) {
	if (subTree != nullptr) {
		PostOrder(subTree->leftChild, visit);
		PostOrder(subTree->rightChild, visit);
		visit(subTree);
	}
}

二叉树的三种遍历算法的非递归实现

把递归过程改为非递归过程(亦称迭代),一般需要一个工作栈,记录遍历时的回退路径。

1、两种前序遍历的非递归写法

第一种:利用栈存储当前结点的右结点,接着就进入当前结点的左结点进行处理,处理结束后取栈中对应的右结点

template<class T>
void PreOrder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p)) {
	stack <BinTreeNode<T>*> s;
	s.push(nullptr);                    		//栈底存放空指针作为结束标志
	BinTreeNode<T> *t = subTree;
	while (t != nullptr) {
		visit(t);							//访问结点
		if (t->rightChild != nullptr) {         //右结点入栈
			s.push(t->rightChild);
		}
		if (t->leftChild != nullptr) {			//处理左结点
			t = t->leftChild;
		}
		else {
			t = s.top();						//左结点为空
			s.pop();
		}
	}
}

第二种:利用栈先存储右结点,后存储左结点,实现左、右的出栈顺序

template<class T>
void PreOrder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p)) {
	stack <BinTreeNode<T>*> s;
	s.push(subTree);
	BinTreeNode<T> *t;

	while (!s.empty()) {
		t = s.top();    
		s.pop();
		visit(t);				//退栈并访问
		
		if (t->rightChild != nullptr) {    			//右结点进栈
			s.push(t->rightChild);
		}
		if (t->leftChild != nullptr) { 				//左结点进栈
			s.push(t->leftChild);
		}
	}
}

2、二叉树的层序遍历

利用队列实现层序遍历

template<class T>
void LevelOrder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p)) {
	queue<BinTreeNode<T>*> q;
	BinTreeNode<T> *t;
	q.push(subTree);				//根结点入栈

	while (!q.empty()) {
		t = q.front();
		q.pop();
		visit(t);
		if (t->leftChild != nullptr) {
			q.push(t->leftChild);
		}
		if (t->rightChild != nullptr) {
			q.push(t->rightChild);
		}
	}
}

3、中序遍历的非递归算法

与前序遍历类似,同样使用一个栈来记录回退的路径。首先从根结点开始走到最左下角的结点,该结点的左子树为NULL,然后访问该结点的数据,再访问该结点的右子树。在右子树中重复上述流程,直到该子树遍历完。

template<class T>
void InOrder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p)) {
	stack<BinTreeNode<T>*> s;
	BinTreeNode<T> *t = subTree;             //遍历指针t
	do
	{
		//第一步:遍历指针t指向最左下角的空结点
		while (t != nullptr) {
			s.push(t);
			t = t->leftChild;
		}

		//到达空指针后,回退上一层结点,并行进到右结点
		if (!s.empty()) {
			t = s.top();
			s.pop();
			visit(t);
			t = t->rightChild;
		}
	} while (p != nullptr || !s.empty());
}

4、后序遍历的非递归算法

后序遍历情况较为复杂,在遍历完左子树时不能访问根结点,需要再遍历右子树,待访问完毕后,才访问根结点。所以在栈工作记录中必须注明刚才是在左(L)还是右(R)中,故结点需要包含方向信息

遍历使用的结点定义

typedef enum {L, R} Tag;

template<class T>
struct stkNode
{
	BinTreeNode<T> *ptr;
	Tag tag;
	stkNode(BinTreeNode<T> *p = nullptr): ptr(p),tag(L){}
};

后序遍历主函数

template<class T>
void PostOrder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p)) {
	stack<stkNode<T>> s;
	stkNode<T> w;
	BinTreeNode<T> *t = subTree;
	do
	{
		//前进到最左下角
		while (t != nullptr)     
		{
			w.ptr = t;
			w.tag = L;
			s.push(w);
			t = t->leftChild;
		}

		//是否继续while循环的标志,从左出来,不继续读结点,从右结点出来继续从栈中读出结点
		int continuel = 1; 
		
		while (continuel && !s.empty()) {
			w = s.top();
			s.pop();
			t = w.ptr;
			switch (w.tag)
			{
			case L: w.tag = R;
				s.push(w);
				continuel = 0;
				t = t->rightChild;
				break;
			case R:visit(t);
				break;
			}
		}

	} while (!s.empty());
}

;