二叉树遍历(递归与迭代)

二叉树遍历算法分为前序(PreOredr),中序(InOrder),后序(PostOrder)遍历。并且可以设计递归型或者迭代型算法。

        本文二叉树定义为:

struct BinaryTreeNode
{
	int m_nValue;
	BinaryTreeNode* m_pLeft;
	BinaryTreeNode* m_pRight;
};

1.二叉树的递归遍历算法

1.1前序遍历

void PreOrder(BinaryTreeNode* pRoot)
{
	if (pRoot!=NULL)
	{
		visit(*pRoot);//visit只是代表处理,关键的是结构
		PreOrder(pRoot->m_pLeft);
		PreOrder(pRoot->m_pRight);
	}	
}

1.2中序遍历

void InOrder(BinaryTreeNode* pRoot)
{
	if (pRoot!=NULL)
	{
		InOrder(pRoot->m_pLeft);
		visit(*pRoot); //visit只是代表处理,关键的是结构
		InOrder(pRoot->m_pRight);
	}	
}

1.3后续遍历

void PostOrder(BinaryTreeNode* pRoot)
{
	if (pRoot!=NULL)
	{
		PostOrder(pRoot->m_pLeft);
		PostOrder(pRoot->m_pRight);
		visit(*pRoot);//visit只是代表处理,关键的是结构
	}	
}

二叉树的三种遍历代码都很简洁,但是这里只是描述结构。根据相应的应用进行设计的时候按照这个结构写递归算法就可以了。关于递归算法顺便提一句很浅显但是设计算法的时候易忽略的一点。二叉树的递归肯定是有底层的叶节点向根节点进行的。

1.4习题举例

       输入一棵二叉搜索树,将该二叉搜索树转换为一个排序的双向链表。要求不能创建任何新的结点,只能调整树中指针的指向。

《二叉树遍历(递归与迭代)》

分析:因为是二叉搜索树所以转变的过程就是节点的左指针指向左子树的最大值,又指针指向右子树的最小值。可以用中序遍历的算法结构来编程。

《二叉树遍历(递归与迭代)》

错误解法:

BinaryTreeNode* findMin(BinaryTreeNode* pNode)
{
	if (pNode==NULL)
		return NULL;
	BinaryTreeNode* min=pNode;
	while (min->m_pLeft!=NULL)
		min=min->m_pLeft;
	return min;
}
BinaryTreeNode* findMax(BinaryTreeNode* pNode)
{
	if (pNode==NULL)
		return NULL;
	BinaryTreeNode* max=pNode;
	while (max->m_pRight!=NULL)
		max=max->m_pRight;
	return max;
}
//从本质上看,这个根本就不是一个二叉树递归算法
void ConvertNode(BinaryTreeNode* pNode)
{
	if (pNode==NULL)
		return;
	BinaryTreeNode* leftNode=pNode->m_pLeft;
	BinaryTreeNode* rightNode=pNode->m_pRight;//保存下次迭代的值
	pNode->m_pLeft=findMax(leftNode);
	//反向的指针如何处理?如果对叶节点也设置反向的指针的话,那么就是一个无限循环
	pNode->m_pRight=findMin(rightNode);
	ConvertNode(leftNode);
	ConvertNode(rightNode);
}
BinaryTreeNode* Convert(BinaryTreeNode* pRootOfTree)
{
	if(pRootOfTree==NULL)
		return NULL;
	BinaryTreeNode* pFirstOfList=findMin(pRootOfTree);//直接找到最小值
	ConvertNode(pRootOfTree);
	return pFirstOfList;
}

正确解法:

BinaryTreeNode* Convert(BinaryTreeNode* pRootOfTree)
{
if(pRootOfTree==NULL)
	return NULL;
	BinaryTreeNode *pLastNodeInList = NULL;
    ConvertNode(pRootOfTree, &pLastNodeInList);
    // pLastNodeInList指向双向链表的尾结点,
    // 我们需要返回头结点
    BinaryTreeNode *pHeadOfList = pLastNodeInList;
    while(pHeadOfList != NULL && pHeadOfList->m_pLeft != NULL)
        pHeadOfList = pHeadOfList->m_pLeft;
    return pHeadOfList;
}

void ConvertNode(BinaryTreeNode* pNode, BinaryTreeNode** pLastNodeInList)
{
    if(pNode == NULL)
        return;
    BinaryTreeNode *pCurrent = pNode;
	//visit_begin
    if (pCurrent->m_pLeft != NULL)
        ConvertNode(pCurrent->m_pLeft, pLastNodeInList);
    pCurrent->m_pLeft = *pLastNodeInList; 
    if(*pLastNodeInList != NULL)
        (*pLastNodeInList)->m_pRight = pCurrent;
    *pLastNodeInList = pCurrent;
    //visit_end
	if (pCurrent->m_pRight != NULL)
        ConvertNode(pCurrent->m_pRight, pLastNodeInList);
}

2.二叉树的迭代遍历算法

为了把一个递归过程改为非递归过程,就需要自己维护一个辅助栈结构,记录遍历时的回退路径。非递归的快速排序的设计依据也是这个。

2.1前序遍历的非递归算法

#include<stack>
void PreOrder(BinaryTreeNode* pRoot)
{
	if (pRoot==NULL)
		return;
	std::stack<BinaryTreeNode*> S;
	BinaryTreeNode *p=pRoot;   //二叉树分左右,所以光有栈不行,合理的运用遍历指针是关键之一
	while(p!=NULL)
	{
		visit(p);
		if (p->m_pRight!=NULL)
			S.push(p->m_pRight);
		if (p->m_pLeft!=NULL)
			p=p->m_pLeft;
		else
		{
			if (S.empty())
				break;
			p=S.top();
			S.pop();
		}
	}
}

2.2中序遍历的非递归算法

#include<stack>
void InOrder(BinaryTreeNode* pRoot)
{
	if (pRoot==NULL)
		return;
	std::stack<BinaryTreeNode*> S;
	BinaryTreeNode *p=pRoot;
	do 
	{
		while(p!=NULL)
		{
			S.push(p);
			p->m_pLeft;
		}
		//若进行到这里左子树为空
		if (!S.empty())//Stack不空时退栈,然后访问该元素
		{
			p=S.top();
			S.pop();
			visit(p);
			p=p->m_pRight;
		}
	} while (p!=NULL||!S.empty());
	//这里的p==NULL表示右子树为空,然后堆栈如果也空的话,才是处理完毕
}

2.3后序遍历的非递归算法

        迭代后序遍历比较复杂。在遍历完左子树时还不能访问根节点,需要再遍历又子树。待右子树遍历完后才访问根节点。所以在辅助栈工作记录中必须注明节点是在左子树还是在右子树。

//记住二叉树后序遍历的非递归调用需要用pair类型,因为需要记录是左进还是右进,所以每个节点必然会进栈两次!!!
void PostOrder(BinaryTreeNode* pRoot)
{
	if (pRoot==NULL)
		return;
	std::pair<BinaryTreeNode*,char> w;
	std::stack<std::pair<BinaryTreeNode*,char> > S;
	BinaryTreeNode *p=pRoot;      
	do 
	{
		while(p!=NULL)           //左子树经过节点加L进栈
		{
			w.first=p;
			w.second='L';
			S.push(w);
			p=p->m_pLeft;
		}
		bool continuel=true;     //继续循环标志,用于L改为R的时候就开始向右遍历
		while (continuel && !S.empty()) //用一个break语句也能实现循环标志continuel的功能
		{
			w=S.top();
			S.pop();
			p=w.first;
			if (w.second=='L')  //标记为L表示左子树遍历完
			{
				w.second=='R';
				S.push(w);
				continuel=false;
				p=p->m_pRight;
			}
			else
				visit(p);      //如果标记为R,表示右子树遍历完
		}
	}while (!S.empty());
}

    原文作者:递归与分治算法
    原文地址: https://blog.csdn.net/sicofield/article/details/9066987
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞