数据结构篇:二叉树(二:二叉树的非递归遍历)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_15020543/article/details/83449007

         二叉树是一种非常重要的数据结构,很多其它数据结构都是基于二叉树的基础演变而来的。对于二叉树,有前序、中序以及后序三种遍历方法。因为树的定义本身就 是递归定义,因此采用递归的方法去实现树的三种遍历不仅容易理解而且代码很简洁。

递归遍历:https://blog.csdn.net/qq_15020543/article/details/83216722

而对于树的遍历若采用非递归的方法,就要采用栈去模拟实现。在三种遍历 中,前序和中序遍历的非递归算法都很容易实现,非递归后序遍历实现起来相对来说要难一点。

前言:

  1. 示例代码建议大家学习思想,而不是生搬硬套。
  2. 程序中 类似  stack<结构体*> s 这种形式

    为利用

    #include<stack> 
    

    所实现的,如果自己写栈的话,Pop,Push,Top方法都要自己写。而且会麻烦许多。这里建议大家直接以这种形式写。

  3. 对于后序非递归遍历,三种结构体有着这样的关系。(以我文末的程序为例(栈自己编写))

  4. 对于先序和中序,则没有中间那个带标识的结构体,直接就是栈。这同时也是建议大家使用stack<结构体*> s形式的理由,因为数据类型的不同,势必要改写Pop,Push,GetTop函数,很麻烦。

  5. 参考自https://www.cnblogs.com/SHERO-Vae/p/5800363.html

一.前序遍历

   前序遍历按照“根结点-左孩子-右孩子”的顺序进行访问。

  根据前序遍历访问的顺序,优先访问根结点,然后再分别访问左孩子和右孩子。即对于任一结点,其可看做是根结点,因此可以直接访问,访问完之后,若其左孩子不为空,按相同规则访问它的左子树;当访问其左子树时,再访问它的右子树。因此其处理过程如下:

  对于任一结点P:

  1. 访问结点P,并将结点P入栈;
  2. 判断结点P的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点P,循环至1;若不为空,则将P的左孩子置为当前的结点P;
  3. 直到P为NULL并且栈为空,则遍历结束。
void preOrder2(BinTree *root)     //非递归前序遍历 
{
    stack<BinTree*> s;
    BinTree *p=root;
    while(p!=NULL||!s.empty())
    {
        while(p!=NULL)
        {
            cout<<p->data<<" ";
            s.push(p);
            p=p->lchild;
        }
        if(!s.empty())
        {
            p=s.top();
            s.pop();
            p=p->rchild;
        }
    }
}

二.中序遍历

  中序遍历按照“左孩子-根结点-右孩子”的顺序进行访问。

  根据中序遍历的顺序,对于任一结点,优先访问其左孩子,而左孩子结点又可以看做一根结点,然后继续访问其左孩子结点,直到遇到左孩子结点为空的结点才进行访问,然后按相同的规则访问其右子树。因此其处理过程如下:

  对于任一结点P,

  1. 若其左孩子不为空,则将P入栈并将P的左孩子置为当前的P,然后对当前结点P再进行相同的处理;
  2. 若其左孩子为空,则取栈顶元素并进行出栈操作,访问该栈顶结点,然后将当前的P置为栈顶结点的右孩子;
  3. 直到P为NULL并且栈为空则遍历结束。
void inOrder2(BinTree *root)      //非递归中序遍历
{
    stack<BinTree*> s;
    BinTree *p=root;
    while(p!=NULL||!s.empty())
    {
        while(p!=NULL)
        {
            s.push(p);
            p=p->lchild;
        }
        if(!s.empty())
        {
            p=s.top();
            cout<<p->data<<" ";
            s.pop();
            p=p->rchild;
        }
    }    
}

三.后序遍历

       后序遍历按照“左孩子-右孩子-根结点”的顺序进行访问。

  后序遍历的非递归实现是三种遍历方式中最难的一种。因为在后序遍历中,要保证左孩子和右孩子都已被访问并且左孩子在右孩子前访问才能访问根结点,这就为流程的控制带来了难题。下面介绍两种思路。

      第一种思路:对于任一结点P,将其入栈,然后沿其左子树一直往下搜索,直到搜索到没有左孩子的结点,此时该结点出现在栈顶,但是此时不能将其出栈并访问, 因此其右孩子还为被访问。所以接下来按照相同的规则对其右子树进行相同的处理,当访问完其右孩子时,该结点又出现在栈顶,此时可以将其出栈并访问。这样就 保证了正确的访问顺序。可以看出,在这个过程中,每个结点都两次出现在栈顶,只有在第二次出现在栈顶时,才能访问它。因此需要多设置一个变量标识该结点是 否是第一次出现在栈顶。是为1,否为2.

void postOrder2(BinTree *root)    //非递归后序遍历
{
    stack<BTNode*> s;
    BinTree *p=root;
    BTNode *temp;
    while(p!=NULL||!s.empty())
    {
        while(p!=NULL)              //沿左子树一直往下搜索,直至出现没有左子树的结点 
        {
            BTNode *btn=(BTNode *)malloc(sizeof(BTNode));
            btn->btnode=p;
            btn->isFirst=true;
            s.push(btn);
            p=p->lchild;
        }
        if(!s.empty())
        {
            temp=s.top();
            s.pop();
            if(temp->isFirst==true)     //表示是第一次出现在栈顶 
             {
                temp->isFirst=false;
                s.push(temp);
                p=temp->btnode->rchild;    
            }
            else                        //第二次出现在栈顶 
             {
                cout<<temp->btnode->data<<" ";
                p=NULL;
            }
        }
    }    
}

部分示例代码

#include <iostream>
#include <string.h>
using namespace std;

typedef struct BiTree { //二叉树
	char data;
	BiTree *lchild,*rchild;
} Tree;


typedef struct TempNode {//后序非递归遍历需要为每个结点设置标识,所以新建一个结构体
	Tree *tempNode;
	int tag;
} TempTreeNode;


struct LastStack { //用来储存结点和tag的栈(链式储存结构)
	TempTreeNode *tempTreeNode;
	LastStack *next;
};

class TraTree {
	private :
		Tree *root;
		LastStack *top;
	public:
		TraTree() {
			root=NULL;
			top=NULL;
		}
		Tree * PreCreateBiTree();//先序创立二叉树
		void Push(TempTreeNode *e);//入栈
		int Pop();//出栈
		TempTreeNode * GetTop();//得到栈顶元素
		bool TestEmpty();//判断栈是否为空 
		void PreOrderTraverse(Tree *T);//先序遍历 
		void InOrderTraverse(Tree *T);//中序遍历 
		void LastOrderTraverse(Tree *T);//后序遍历 
};


Tree * TraTree :: PreCreateBiTree() {
	Tree *T;
	char c;
	cin>>c;
	if(c=='#') {
		T=NULL;
	} else {
		T=new Tree;
		if(!T) {
			cout<<"请求内存失败。"<<endl;
		} else {
			T->data=c;
		}
		T->lchild=PreCreateBiTree();
		T->rchild=PreCreateBiTree();
	}
	return T;
}
int TraTree::Pop() {
	if(!top) {
		cout<<"溢出"<<endl;
		return -1;
	}
	LastStack *p=new LastStack;
	p=top;
	top=top->next;
	delete p;
	return 1;
}
void TraTree::Push(TempTreeNode *e) {
	LastStack *s=new LastStack;
	if(!s) {
		cout<<"内存分配失败";
		return ;
	}
	s->tempTreeNode=e;
	s->next=top;
	top=s;
}
TempTreeNode * TraTree::GetTop() {
	if(top) {
		return top->tempTreeNode;
	}
}
bool TraTree:: TestEmpty() {
	if(top) {
		return false;
	} else {
		return true;
	}
}

void TraTree::PreOrderTraverse(Tree *T)
{

}

void TraTree::InOrderTraverse(Tree *T)
{
	
}

void TraTree::LastOrderTraverse(Tree *T)
{
	Tree *p=T;
    TempTreeNode *temp;
    while(p!=NULL||TestEmpty()==false)
    {
        while(p!=NULL)              //沿左子树一直往下搜索,直至出现没有左子树的结点 
        {
            temp = new TempTreeNode;
            temp->tempNode=p;
            temp->tag=1;
            Push(temp);
            p=p->lchild;
        }
        if(TestEmpty()==false)
        {
            temp = GetTop();
            Pop();
            if(temp->tag==1)     //表示是第一次出现在栈顶 
             {
                temp->tag=2;
                Push(temp);
                p=temp->tempNode->rchild;    
            }
            else                        //第二次出现在栈顶 
             {
                cout<<temp->tempNode->data<<" ";
                p=NULL;
            }
        }
    }    
}
int main() {
	TraTree traTree; 
	Tree *tree;
	cout<<"请以先序顺序输入数元素,以'#'代表虚空元素"<<endl;
    tree = traTree.PreCreateBiTree();
	traTree.LastOrderTraverse(tree);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_15020543/article/details/83449007