二叉树(0)——二叉树的实现与二叉树的遍历

0.二叉树的实现(C++)

未完,待补充

#include <iostream>
#include<iostream>
#include<queue>
#include<stack>
using namespace std;

//二叉树结点的定义
template <typename T>
struct BinTreeNode
{
	T value;
	BinTreeNode* left; //左孩子
	BinTreeNode* right;   //右孩子
	BinTreeNode(const T &value):    //为什么此处声明为const?
		//因为声明的是模板,不确定传入参数的大小,声明为数据的常引用,保证头不会太大,
		//并且该函数不改变,传入参数的值,在进行列表初始化时,将形参value进行拷贝赋值给属性value
		value(value),right(NULL),left(NULL)
	//此处列表初始化的赋值顺序,不是按照在此处的书写顺序,
	//而是按照前面成员变量的定义顺序,即value,left,right
		{}
};


//二叉树类的实现
template <typename T>
class BinTree
{
//私有属性
private:
	BinTreeNode* root;

//为用户提供的接口
public:
	
	//默认构造函数
	BinTree()
        :root(nullptr)
    {
    }
	//有参构造
	BinTree(BinTreeNode* root)
	{

	}
	//析构函数
	~BinTree()
	{
		destroyBinTree(root);
	}
	//赋值运算符重载
	BinTree& operator=(const BinTree& binTree)
	{
		if(this!=binTree)
		{
			destroyBinTree(root);
			copyBinTree(binTree->root);
		}
	}
	
	
	//拷贝构造函数
	BinTree(const BinTree &binTree)
	{
		root=copyBinTree(binTree->root);
	}
//四种遍历(具体实现见后文)
	//中序遍历
	//前序遍历
	//后序遍历
	//层序遍历
	
//节点个数
	
//叶节点个数
	
//树的高度
	
//第k层结点的个数
	
//查找函数:在当前树中查询数据值为data的节点
	
//查找某个结点的父亲结点

//查找某个节点的左孩子节点

//查找某个节点的右孩子节点
	
	
}

1.中序遍历

(1)递归实现

//中序遍历递归实现
void inOrderTraverse(BinTreeNode *root)
{
    if(root)
    {
	inOrderTraverse(root->left);
	visit(root);
	inOrderTraverse(root->right);
    }
}

(2)非递归实现(栈实现)

思路:由于二叉树的结点只能通过根结点向下访问,从根结点开始入栈,然后不断迭代让左孩子结点入栈,直到左孩子结点为null时,开始访问栈顶元素(即在出栈前夕开始访问该节点),然后出栈,然后再访问出栈结点的右结点,让右结点入栈

对于每一组父亲结点和两个孩子

                第一波入栈顺序:根结点->左孩子,

                         则出栈顺序:左孩子->根结点

                第二波入栈:右孩子

                            出栈:右孩子

                总是在出栈前夕的时候访问就保证了访问与出栈相同的顺序:左孩子->根结点->右孩子

时间复杂度:O(n)

空间复杂度:O(n)

代码实现:

//中序遍历非递归实现
void inOderTraverse(BinTreeNode *root)
{
	stack<BinTreeNode *> s;
	BinTreeNode *p=root;    //定义遍历变量指针p

	while(p || !s.empty())   //中序遍历的最终遍历变量指针p指向为空,并且弹出了所有的入栈的结点
	{
		if(p)    //如果指针p指向的结点存在,就将它入栈
		{
			s.push(p);    //选择的每一层都先让根结点先入栈,然后让它的左结点入栈,这样利用栈的
							//先进后出的特点,出栈的时候左孩子结点会先出栈,然后根结点再出栈,
	   					   //然后根结点出栈之后访问其右结点,所以整个过程正好符合左,根,右
			p=p->left;
		}else
		{
			p=s.top();
			visit(p);       //这样就访问了当前层的根结点
			s.pop(p);       //从栈中弹出访问过的结点
			p=p->right;
		}
	}

}

(3)Morris方法

//待补充

2.前序遍历

(1)递归实现

//前序遍历递归实现
void preOrderTraverse(BinTreeNode* root)
{
    if(root)
    {
	visit(root);
	preOrderTraverse(root->left);
	preOrderTraverse(root->right);
    }

}

(2)非递归实现

思路:

思路与上述中序遍历相似,入栈和出栈顺序相同,更改的是访问的时机,即在入栈前夕访问结点

对于每一组父亲结点和两个孩子

                第一波入栈顺序:根结点->左孩子,

                         则出栈顺序:左孩子->根结点

                第二波入栈:右孩子

                            出栈:右孩子

                总是在入栈前夕的时候访问就保证了访问与入栈相同的顺序:左孩子->根结点->右孩子

时间复杂度:O(n)

空间复杂度:O(n)

代码实现:

//前序遍历非递归实现(栈实现)
void preOrderTraverse(BinTreeNode *root)
{
	stack<BinTreeNode *> s;
	BinTreeNode *p=root;    //定义遍历变量指针p

	while(p || !s.empty())   //中序遍历的最终遍历变量指针p指向为空,并且弹出了所有的入栈的结点
	{
		if(p)    //如果指针p指向的结点存在,就将它入栈
		{
			visit(p);     //先访问根结点,再将该结点入栈,直到左孩子为空开始返回
							//每次先访问根结点,然后指向本次的左孩子时,对于下一层又是根结点
			s.push(p);
			p=p->left;
		}else
		{
			p=s.top();
			s.pop(p);       //在弹出本层根结点时,再去访问右结点
			p=p->right;
		}
	}

}

(3)Morris方法

//待补充

3.后序遍历

(1)递归实现

//后序遍历递归实现
void postOrderTraverse(BinTreeNode* root)
{
    if(root)
    {
	
	postOrderTraverse(root->left);
	postOrderTraverse(root->right);
        visit(root);
    }

}

(2)非递归实现

思路:

中序遍历和前序遍历的入栈顺序为

                第一波入栈顺序:根结点->左孩子,

                         则出栈顺序:左孩子->根结点

我们没办法在入栈的时候先入栈左孩子,右孩子

但是在出栈的时候可以在左孩子出栈后,插入右孩子入栈,先判断右孩子是否已经被访问,没有被访问的话,就先将右孩子入栈,然后入栈的顺序就存储了同一组的根结点->右孩子

然后出栈顺序就为右孩子->根结点

所以整体思路:

                第一波入栈顺序:根结点->左孩子,

                         则出栈顺序:左孩子

                判断根结点是否有右孩子或者有孩子是否已经被访问,

                没有被访问的话,第二波入栈:右孩子

                         则出栈顺序:右孩子->根结点

                 所以增加了判断就保证了出栈顺序为左孩子->右孩子->根结点

                所以我们需要增加一个变量保存刚才已经访问过的结点,如果该结点保存的是左孩子,那么就会进行入栈,如果保存的时右孩子,表明右孩子已经被访问,可以访问根结点了

经验:当我们无法改变入栈的顺序时,我们可以通过判断,先让部分出栈,然后又让其他元素入栈,这样就改变了出栈的顺序

时间复杂度:O(n)

空间复杂度:O(n)

代码实现:

//后序遍历非递归实现(栈实现)
void postOrderTraverse(BinTreeNode *root)
{
	BinTreeNode* visited=nullptr;//保存被访问的右结点,用于判断当前结点的右结点是否已被访问
	BinTreeNode* p=root;
	stack<BinTreeNode*> s;

	while(p || !s.empty())
	{
		//从头结点开始,不断迭代让左孩子入栈
		while(p)
		{
			s.push(p);
			p=p->left;
		}
		//当左孩子全部入栈时,让p栈顶,开始准备出栈
		p=s.top();
		//当前结点要出栈,要进行访问,先必须保证它的右结点不存在或已经被访问,
		//否则,先让它的右结点先入栈
		if(p->right && p->right != visited)
		{
			p=p->right;
		}
	    //当前结点的右结点不存在或已经被访问,就可以访问当前的结点,并且让它出栈
		else
		{
			visit(p);
			s.pop();
			visited=p;//访问过后就将判断结点设置为刚才访问过的结点,
			p=nullptr;//让p为空,是因为对于当前层来说访问完当前结点就结束了
		}
	}

}

(3)Morris方法

//待补充

4.层次遍历

//待补充

猜你喜欢

转载自blog.csdn.net/qq_34805255/article/details/84001051
今日推荐