数据结构-线索二叉树(后序线索二叉树及遍历)

  • 后序线索二叉树


  • 线索化的概念及相关图解

在上一篇中详细介绍了中序线索二叉树线索化图解相关概念都放在那篇博客啦,放个传送门

线索二叉树详细解析(含图解):传送门

包括还有先序线索二叉树:传送门

 

  • 后序线索二叉树

(1)后序线索二叉树的构造

             ①思路:在这里说下构造的原因,与先序中序线索二叉树不同,在遍历时后序时先把孩子全部遍历了再回到父母节点,  而回寻父母节点的方法只能在定义的节点结构中添加一指针,指向该节点的父母节点

template<class T>
struct BT_Thread_Node
{
	T Data;
	BT_Thread_Node* Left_Child;
	BT_Thread_Node* Right_Child;
	BT_Thread_Node* Parent;	       //指向该节点的父母节点
	int Ltag;
	int Rtag;
};

             ②先序递归构造二叉树

                 就一点不同:传参多了一个父母节点,构建完当前节点后,该节点得指向父母节点,并且该节点作为其左右孩子的                                           父母节点作为参数传至下一构造函数。

template<class T>
int Thread_Binary_tree<T>::Create_Thread_BTree(BT_Thread_Node<T>* &Tree,BT_Thread_Node<T>* Parent_Node)
{
	int Data;
	cin >> Data;
	if (Data == -1)
		Tree = NULL;
	else
	{
		Tree = new BT_Thread_Node<T>;
		Tree->Parent = Parent_Node;        //指向父母节点
		Tree->Data = Data;
		Tree->Ltag = Link;
		Tree->Rtag = Link;
		Create_Thread_BTree(Tree->Left_Child,Tree);  //Tree是孩子的父母节点
		Create_Thread_BTree(Tree->Right_Child,Tree);
	}
	return 1;
}

 

(2)后序线索化

             ①思路:

                        a.先线索化左右孩子节点

                        b.根节点:若左孩子节点空,左孩子节点指向前驱节点(前一次访问的节点Pre_Node),标记该孩子为                                                       线索模式(Thread)

                                         若前一次访问的节点不为空并且它的右孩子为空,则该右孩子指向当前节点(后继节点),                                                         标记该孩子为线索模式(Thread)

                        c.保存当前节点,作为下一次的Pre_Node (记录历史的意思)

             ②实现代码:

#define Link	0	//表示该节点有非空孩子节点
#define Thread	1	//表示该节点有后续节点(对于右子树来说)
template<class T>
void Thread_Binary_tree<T>::PostOrder_Thread_Op(BT_Thread_Node<T>* &Tree)
{
	if (Tree == NULL)
		return;
	
	PostOrder_Thread_Op(Tree->Left_Child);		//左
	PostOrder_Thread_Op(Tree->Right_Child);		//右

	if (Tree->Left_Child == NULL)		//根
	{
		Tree->Left_Child = Pre_Node;
		Tree->Ltag = Thread;
	}
	if (Pre_Node != NULL && Pre_Node->Right_Child == NULL)
	{
		Pre_Node->Right_Child = Tree;
		Pre_Node->Rtag = Thread;
	}

	Pre_Node = Tree;
}

注意!!!!!Pre_Node 是作为类内私有数据,相当于全局变量,对每个节点的线索都会使它改变!

 

(3)中序线索遍历

             ①思路:

                        a.靠左访问:找到最左边的节点,也就是后序遍历顺序中最先访问的那个节点

                        b.按后继线索访问:当前节点的右孩子节点指向的是后继,则访问该节点并指向下一右孩子直到该孩子不为后继节点。记得保存每次访问的节点哦,便于后面判断袖珍树(子树)是否访问完成。

                        c.回到根部:后面会放出一个例子,在ab两部分操作后,会出现两种情况

                                  1:该节点为根节点,访问最后这一节点后,整个后序遍历完成啦,跳出。                                                                                   2:是节点是子树的根节点,访问这一节点后完成的是子树的后序遍历,这时肯定是回到该子树根节         点的父母节点,然后指向该父母节点的右孩子进行下一次的遍历。

             ②实现代码:

template<class T>
void Thread_Binary_tree<T>::_PostOrder_Op(BT_Thread_Node<T>* &Tree)
{
	if (Tree == NULL)
		return;
	
	BT_Thread_Node<T> *Cur_Node = Tree;
	Pre_Node = NULL;
	while (Cur_Node != NULL)
	{
		//个人觉得,Cur_Node->Ltag == Link会影响找到最左节点,但就测试数据来看,两个都ok
		//while (Cur_Node->Left_Child != Pre_Node && Cur_Node->Ltag == Link)
		while (Cur_Node->Left_Child != Pre_Node)//change,找到起始节点(在左树的最左边)
		{
			Cur_Node = Cur_Node->Left_Child;
		}

		while (Cur_Node != NULL && Cur_Node->Rtag == Thread)//按线索找到次树节点
		{
			BT_Node_Stack[++Top] = Cur_Node;
			cout << Cur_Node->Data << " ";
			Pre_Node = Cur_Node;			//每次访问节点后,PreNode更新
			Cur_Node = Cur_Node->Right_Child;
		}

		if (Cur_Node == Tree)		//如果当前节点为根节点,说明遍历完成
		{
			BT_Node_Stack[++Top] = Cur_Node;
			cout << Cur_Node->Data << " ";
			return;
		}

		while (Cur_Node != NULL && Cur_Node->Right_Child == Pre_Node)//当前节点的右孩子节点刚好上次遍历,说明该袖珍树只差根就遍历完成
		{
			BT_Node_Stack[++Top] = Cur_Node;
			cout << Cur_Node->Data << " ";
			Pre_Node = Cur_Node;
			Cur_Node = Cur_Node->Parent;		//访问袖珍树后回到上一层
		}

		if (Cur_Node != NULL && Cur_Node->Rtag == Link)		//回到上一层后,先访问右孩子
		{
			Cur_Node = Cur_Node->Right_Child;
		}
	}
}

把要访问的节点全部入栈???答:为了方便析构。

             ③图解举栗

                     a.靠左访问:最左边的节点是D(先找,不访问)

                     b.按后继访问:看箭头! D -> G -> E-> BB的右子树不是线索啦,跳出,此时B还没有访问

                     c.回到根部:显然回到的根部是B,并且是上面说的第二种情况! 子树的根节点

                                          怎么判断?还记得我说的要保存上一访问节点吗? 上一访问节点是E,B的右子树指向E

                                 判定的语句对于程序中的:while (Cur_Node != NULL && Cur_Node->Right_Child == Pre_Node)

                                         访问B后完成以B为根节点的子树(BDEG)的后序遍历,然后回到B的父母节点A

                                          回到A(不访问A),直接指向它的右孩子C    

 

                      大家注意!!对于a操作,不仅可以在链模式(左孩子存在的情况下)找,也可以寻找该节点的前驱!!

                      我的锅这个图其实C的左孩子是指向F,为了易懂我还没画出来。

                      回到遍历:到了C之后,又是重复abc操作啦,靠左孩子访问到了F(C的左孩子节点指向它的前驱节点F),再后继访问到C,最后回到根部A,遍历完成。

 

(4)析构线索树

              在写中序线索树的时候我是比较傻,又写了一遍线索遍历,这次干脆在遍历的时候就把节点入栈,便于删除。

             ①实现代码:

template<class T>
Thread_Binary_tree<T>::~Thread_Binary_tree()
{
	while (Top != -1)
	{
		delete BT_Node_Stack[Top];
		Top--;
	}
}

 

到这再bb两句,重要的! 和先序还有中序线索不同的是

一 节点的定义:多了个指向父母节点的指针

二 保存上一访问节点:为了判断子树是否遍历完成,需记录

 

到这基础就讲完啦,看看类实现代码吧~

  • 后序线索二叉树类实现

(1)类定义包括常量定义

                         老思路,实现与接口分开~实现函数全部私有化

#include<iostream>
using namespace std;
#define Link	0	//表示该节点有非空孩子节点
#define Thread	1	//表示该节点有后续节点(对于右子树来说)
#define MAXSIZE  100

template<class T>
struct BT_Thread_Node
{
	T Data;
	BT_Thread_Node* Left_Child;
	BT_Thread_Node* Right_Child;
	BT_Thread_Node* Parent;	       //指向该节点的父母节点
	int Ltag;
	int Rtag;
};

template<class T>
class Thread_Binary_tree
{
private:
	BT_Thread_Node<T>* Tree;
	BT_Thread_Node<T>* Pre_Node;
	BT_Thread_Node<T>* BT_Node_Stack[MAXSIZE];
	int Top;
	int Create_Thread_BTree(BT_Thread_Node<T>* &Tree,BT_Thread_Node<T>* Parent);
	void PostOrder_Thread_Op(BT_Thread_Node<T>* &Tree);
	void _PostOrder_Op(BT_Thread_Node<T>* &Tree);
public:
	Thread_Binary_tree();
	~Thread_Binary_tree();
	void PostOrder_Thread();
	void _PostOrder();
};

(2)完整代码

#include<iostream>
using namespace std;
#define Link	0	//表示该节点有非空孩子节点
#define Thread	1	//表示该节点有后续节点(对于右子树来说)
#define MAXSIZE  100

template<class T>
struct BT_Thread_Node
{
	T Data;
	BT_Thread_Node* Left_Child;
	BT_Thread_Node* Right_Child;
	BT_Thread_Node* Parent;	       //指向该节点的父母节点
	int Ltag;
	int Rtag;
};

template<class T>
class Thread_Binary_tree
{
private:
	BT_Thread_Node<T>* Tree;
	BT_Thread_Node<T>* Pre_Node;
	BT_Thread_Node<T>* BT_Node_Stack[MAXSIZE];
	int Top;
	int Create_Thread_BTree(BT_Thread_Node<T>* &Tree,BT_Thread_Node<T>* Parent);
	void PostOrder_Thread_Op(BT_Thread_Node<T>* &Tree);
	void _PostOrder_Op(BT_Thread_Node<T>* &Tree);
public:
	Thread_Binary_tree();
	~Thread_Binary_tree();
	void PostOrder_Thread();
	void _PostOrder();
};

template<class T>
int Thread_Binary_tree<T>::Create_Thread_BTree(BT_Thread_Node<T>* &Tree,BT_Thread_Node<T>* Parent_Node)
{
	int Data;
	cin >> Data;
	if (Data == -1)
		Tree = NULL;
	else
	{
		Tree = new BT_Thread_Node<T>;
		Tree->Parent = Parent_Node;        //指向父母节点
		Tree->Data = Data;
		Tree->Ltag = Link;
		Tree->Rtag = Link;
		Create_Thread_BTree(Tree->Left_Child,Tree);  //Tree是孩子的父母节点
		Create_Thread_BTree(Tree->Right_Child,Tree);
	}
	return 1;
}

template<class T>
void Thread_Binary_tree<T>::PostOrder_Thread_Op(BT_Thread_Node<T>* &Tree)
{
	if (Tree == NULL)
		return;
	
	PostOrder_Thread_Op(Tree->Left_Child);		//左
	PostOrder_Thread_Op(Tree->Right_Child);		//右

	if (Tree->Left_Child == NULL)		//根
	{
		Tree->Left_Child = Pre_Node;
		Tree->Ltag = Thread;
	}
	if (Pre_Node != NULL && Pre_Node->Right_Child == NULL)
	{
		Pre_Node->Right_Child = Tree;
		Pre_Node->Rtag = Thread;
	}

	Pre_Node = Tree;
}

template<class T>
void Thread_Binary_tree<T>::_PostOrder_Op(BT_Thread_Node<T>* &Tree)
{
	if (Tree == NULL)
		return;
	
	BT_Thread_Node<T> *Cur_Node = Tree;
	Pre_Node = NULL;
	while (Cur_Node != NULL)
	{
		//个人觉得,Cur_Node->Ltag == Link会影响找到最左节点,但就测试数据来看,两个都ok
		//while (Cur_Node->Left_Child != Pre_Node && Cur_Node->Ltag == Link)
		while (Cur_Node->Left_Child != Pre_Node)//change,找到起始节点(在左树的最左边)
		{
			Cur_Node = Cur_Node->Left_Child;
		}

		while (Cur_Node != NULL && Cur_Node->Rtag == Thread)//按线索找到次树节点
		{
			BT_Node_Stack[++Top] = Cur_Node;
			cout << Cur_Node->Data << " ";
			Pre_Node = Cur_Node;			//每次访问节点后,PreNode更新
			Cur_Node = Cur_Node->Right_Child;
		}

		if (Cur_Node == Tree)		//如果当前节点为根节点,说明遍历完成
		{
			BT_Node_Stack[++Top] = Cur_Node;
			cout << Cur_Node->Data << " ";
			return;
		}

		while (Cur_Node != NULL && Cur_Node->Right_Child == Pre_Node)//当前节点的右孩子节点刚好上次遍历,说明该袖珍树只差根就遍历完成
		{
			BT_Node_Stack[++Top] = Cur_Node;
			cout << Cur_Node->Data << " ";
			Pre_Node = Cur_Node;
			Cur_Node = Cur_Node->Parent;		//访问袖珍树后回到上一层
		}

		if (Cur_Node != NULL && Cur_Node->Rtag == Link)		//回到上一层后,先访问右孩子
		{
			Cur_Node = Cur_Node->Right_Child;
		}
	}
}

template<class T>
Thread_Binary_tree<T>::Thread_Binary_tree():
	Pre_Node(NULL)
	,Top(-1)
{
	Create_Thread_BTree(Tree, NULL);
}

template<class T>
Thread_Binary_tree<T>::~Thread_Binary_tree()
{
	while (Top != -1)
	{
		delete BT_Node_Stack[Top];
		Top--;
	}
}

template<class T>
void Thread_Binary_tree<T>::PostOrder_Thread()
{
	PostOrder_Thread_Op(Tree);
}

template<class T>
void Thread_Binary_tree<T>::_PostOrder()
{
	_PostOrder_Op(Tree);
}


int main()
{
	Thread_Binary_tree<int> MyTree;
	MyTree.PostOrder_Thread();
	MyTree._PostOrder();
	return 0;
}
  • 60
    点赞
  • 192
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值