面试算法 二叉树的遍历,方法 :线索二叉树 ( morris ) ,前序遍历: 中序遍历: 后序遍历

1.题目:二叉树遍历
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
层序遍历:从上往下、从左往右

 算法: morris遍历


2.算法:

morris算法:


3.算法思想: (这东西讲不清    bibi  看视频!!!)

前序遍历 
总结: 1.首先遇到节点左边是 NULL  打印自己 ,遍历自己的右节点  
     2.遇到右节点为空 ,打印数据 节点数据    还有 就是建立线索二叉树,继续遍历自己的左节点, 
         3.遇到线索二叉树,消除线索二叉树 ,遍历自己的 右节点 
 

中序遍历 
总结: 1.首先到达最左边的时候 需要打印数据
       2.断开线索连接的时候 打印数据 
       3.节点的最左边为 NULL  打印当前节点的数据 
 

后序遍历
总结: 断开线索二叉树的时候,链表反转,打印数据 


4.代码:

/*************************************************
作者:She001
时间:2022/9/31
题目:二叉树遍历
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根

算法 : morris遍历(线索二叉树) 
***************************************************/

#include<bits/stdc++.h>
using namespace std;


typedef struct student
{
	int a;
	struct student * left;
	struct student * right;
	int shendu;//树的深度 
}node;  //指针类型的 
/*//二叉树模型 
						
					a1
			a2              a3
		a4	  a5        a6     a7
		            a8
                       a9
*/
///

//遍历的方法实现 
/*
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
层序遍历:从上往下、从左往右
*/

// 手写方法1 的 前序遍历  过程   
/*
1.开始  cur== a1  ,定义了 mostright=NULL   , 
2.mostright = cur->left =a1->lift  = a2 ;||   mostright 进入循环 ->  一直到  mostright= a5 ,
3. 退出 while 循环 进入判断  判读条件成功的为  mostright == NULL   ,所以我们建立 线索二叉树     ====|||a5 ->right =a1;||||====  
打印数据  输出数据    cur ->a =  打印 a1->a   ; cur=cur->left= a2; continue;
4.这次从 a2 开始  我们开始   
5. mostright = cur->lift =  a2->lift =a4;
6.不能进入while 循环 ,因为 s4->right =NULL ;   
7. 进入if  判断  所以      =====||||a4 -> right = a2;|||||=====    
输出数据 cur->a   =  输出数据 a2-> a  ;     cur= cur->left  =a4   continue;
8.这次从 a4 开始  
9.开始  mostright= cur->left =a4 ->left =NULL
10. 进入 mostright ==NULL  的判断  判断不成立 所以进入 else 的里面 输出数据   cur ->a   =  a4->a    然后我们的出来最后有一个  cur =cur -> right =  a4->right ; cur=a2 
11. cur=a2    mostright = a2->left = a4 ;  进入 while  循环   退出循环,, 因为 mostright ->right=cur  ;  删除 线索二叉树 cur  = cur->right = a2->right= a5; 
12. 这时候 从 cur =  a5 开始 
13. mostright =cur ->left  =a5->left =NULL 
14.mostright =NULL   if 判断  ,  mostright =NULL  所以我们进入 else  空间 ,所以执行  打印数据,cur ->a  =  a5->a  , 
15.cur=cur->right  = a5->  right    = a1; 
16.mostright= cur->left =a2 ;  进入循环 , 退出循环因为  mostright->right = a2-> right=a5 , mostright->right=a5->right =a1  ,  mostright ==cur 这个原因退出循环
17.进入判断  因为 mostright == cur 所以 删除 线索二叉树的连接!  cur = cur -> right  = a1 -> a3;
18. mostright =cur->left  =a3->left  = a6   进入 循环出来   判断   mostright -> right =NULL       ========||||||||  a6  ->right  = a3;  
   打印 cur-> a  = a3->a ;   cur=cur ->left = a3->left =a6; 
19. mostright = cur-> left = a6->left = a8 ;      mostright进入循环 ,直到  a9 的时候停止。 ======||||||||||||a9->right =a6;
	打印cur ->a  =a6 ->a    ;  cur= cur->left = a6->left = a8 ; 
20.mostright =cur ->left  =NULL   打印 a8->a   cur=cur->right  = a9; 
21. mostright= a9->left = NULL;  所以  停止,打印 cur-> a = a9->a;  cur =a9 ->right  = a6 ;
21 mostright= cur-> left  =a8 ; a8->right = a9  a9 ->right =a6   mostright== cur  循环停止  所以消除线索二叉树  , cur = cur->right = a6-> right = a3; 
22.mostright = cur->left = a3->left=a6;   mostright->right ===cur 循环结束   所以消除线索二叉树 , cur = cur -> right = a7; 
23. mostright = a7->right =NULL  ;   建立线索二叉树, a7->right = a7;  , 打印 a7  ;  cur= cur->left = NULL  ,
24.if   判断   cur  == NULl  退出外部的大循环! 
*/
/*
前序遍历 
总结: 1.首先遇到节点左边是 NULL  打印自己 ,遍历自己的右节点  
 		2.遇到右节点为空 ,打印数据 节点数据    还有 就是建立线索二叉树,继续遍历自己的左节点, 
		 3.遇到线索二叉树,消除线索二叉树 ,遍历自己的 右节点 
*/
void fangfa_1(node * cur)//前序遍历
{
	if(cur == NULL)
	{
		return ;
	} 
	node * mostright = NULL; //寻找最左边子树的 最靠右的叶子节点   标记位
	while(cur !=NULL)
	{		
		mostright=cur->left;//寻找开始,从当前节点的下一个左边节点 ,开始寻找,寻找当前节点左子树的最靠右的叶子节点   (假如没有就 没有)
		if(mostright !=NULL)
		{
			while(mostright->right!=NULL && mostright->right!=cur)//一直找,停止的条件,第一个是当前节点的右节点为 NULL  , 第二个是 当前线索二叉树已经建立, 
			{
				mostright=mostright->right;//往右走 
			}
			if(mostright->right==NULL)//找到了 cur 节点的子树的最右边节点 ,建立线索二叉树 
			{
				mostright->right=cur;  //我们建立线索二叉树  实现子树 最右边的节点 连接 当前的节点 
				//因为 这个是 前序的遍历,所以我们到了 节点 就打印
				cout<<cur->a<<"   ";
				cur=cur->left;//寻找当前节点的左边节点的线索二叉树
				continue; //结束,再次循环 //防止与下一步冲突 
			}
			else//已经含有线索二叉树,我们需要删除节点, 
			{
				mostright->right=NULL;//删除含有线索的叶子节点 的连接 
			} 		
		}
		else//当我们的  mostright==NULL  的时候我们需要打印数值 // 因为此时是二叉树的最左边的节点 ,打印当前的节点的值 
		{
			cout<<cur->a<<"   ";	//打印数值		
		}
		cur=cur->right; 
	} 
} 

/*
中序遍历 
总结: 1.首先到达最左边的时候 需要打印数据
       2.断开线索连接的时候 打印数据 
       3.节点的最左边为 NULL  打印当前节点的数据 
*/


void fangfa_2(node * cur)//中序遍历
{
	if(cur == NULL)
	{
		return ;
	} 
	node * mostright = NULL; //寻找最左边子树的 最靠右的叶子节点   标记位
	while(cur !=NULL)
	{		
		mostright=cur->left;//寻找开始,从当前节点的下一个左边节点 ,开始寻找,寻找当前节点左子树的最靠右的叶子节点   (假如没有就 没有)
		if(mostright !=NULL)
		{
			while(mostright->right!=NULL && mostright->right!=cur)//一直找,停止的条件,第一个是当前节点的右节点为 NULL  , 第二个是 当前线索二叉树已经建立, 
			{
				mostright=mostright->right;//往右走 
			}
			if(mostright->right==NULL)//找到了 cur 节点的子树的最右边节点 ,建立线索二叉树 
			{
				mostright->right=cur;  //我们建立线索二叉树  实现子树 最右边的节点 连接 当前的节点 
			
				cur=cur->left;//寻找当前节点的左边节点的线索二叉树
				continue; //结束,再次循环 //防止与下一步冲突 
			}
			else//已经含有线索二叉树,我们需要删除节点, 
			{
				mostright->right=NULL;//删除含有线索的叶子节点 的连接 
			} 		
		}
		else//当我们的  mostright==NULL  的时候我们需要打印数值 // 因为此时是二叉树的最左边的节点 ,打印当前的节点的值 
		{
			
		}
		cout<<cur->a<<"   ";	//打印数值		
		cur=cur->right; 
	} 
} 

/*
后序遍历
总结: 断开线索二叉树的时候,链表反转,打印数据 


*/

node* reverse(node* head)//链表反转 
{
	node* prev =NULL;
	node* curr;
	node* next;
	curr=head;
	while(curr!=NULL)
	{
		next=curr->right;
		curr->right=prev;
		prev=curr;
		curr=next;
	}
	return prev;
	
}


void printnode(node *head)//打印数据  ,步骤,链表反转,打印数据,还原链表,保持二叉树的完整性
{
	node *hh = reverse(head);//第一反过来 
	node * ww=hh;//保存开头的节点 
	while(hh!=NULL)//打印数据 
	{
		cout<<hh->a<<"   ";
		hh=hh->right;
	}
	reverse(ww);	//还原 
} 


void fangfa_3(node * cur)//中序遍历
{
	if(cur == NULL)
	{
		return ;
	} 
	node * mostright = NULL; //寻找最左边子树的 最靠右的叶子节点   标记位
	node * jj=cur;
	while(cur !=NULL)
	{		
		mostright=cur->left;//寻找开始,从当前节点的下一个左边节点 ,开始寻找,寻找当前节点左子树的最靠右的叶子节点   (假如没有就 没有)
		if(mostright !=NULL)
		{
			while(mostright->right!=NULL && mostright->right!=cur)//一直找,停止的条件,第一个是当前节点的右节点为 NULL  , 第二个是 当前线索二叉树已经建立, 
			{
				mostright=mostright->right;//往右走 
			}
			if(mostright->right==NULL)//找到了 cur 节点的子树的最右边节点 ,建立线索二叉树 
			{
				mostright->right=cur;  //我们建立线索二叉树  实现子树 最右边的节点 连接 当前的节点 
				
				cur=cur->left;//寻找当前节点的左边节点的线索二叉树
				continue; //结束,再次循环 //防止与下一步冲突 
			}
			else//已经含有线索二叉树,我们需要删除节点, 
			{
				
				mostright->right=NULL;//删除含有线索的叶子节点 的连接 
				printnode(cur->left);
			} 		
		}
		cur=cur->right; 
	} 
	printnode(jj);
} 







int main()
{
	//数据初始化,建立树 
	struct student *a1 =new struct student; 
	struct student *a2 =new struct student; 
	struct student *a3 =new struct student; 
	struct student *a4 =new struct student; 
	struct student *a5 =new struct student; 
	struct student *a6 =new struct student; 
	struct student *a7 =new struct student; 
	struct student *a8 =new struct student; 
	struct student *a9 =new struct student; 
	//数值的赋值 
	a1->a=1; 
	a2->a=2; 
	a3->a=3; 
	a4->a=4; 
	a5->a=5; 
	a6->a=6; 
	a7->a=7; 
	a8->a=8; 
	a9->a=9; 
	//节点的连接 
	a1->left=a2;
	a1->right=a3;
	a2->left=a4;
	a2->right=a5;
	a3->left=a6;
	a3->right=a7;
	a6->left=a8;
	a8->right=a9;
	//节点为空的设置
	a4->left=NULL;
	a4->right=NULL;
	a5->left=NULL;
	a5->right=NULL;
	a8->left=NULL;
	a9->left=NULL;
	a9->right=NULL;
	a6->right=NULL;
	a7->left=NULL;
	a7->right=NULL;
	
	
		//打印前序遍历
	cout<<"前序遍历:  "; 
	fangfa_1(a1); 
	cout<<endl; 
		//打印中序遍历
	cout<<"中序遍历:  "; 
	fangfa_2(a1); 
	cout<<endl; 
	
		
		//打印后序遍历 
	cout<<"后序遍历 :  "; 
	fangfa_3(a1); 
	cout<<endl; 
	
	//释放空间 
	delete a1;
	delete a2;
	delete a3;
	delete a4;
	delete a5;
	delete a6;
	delete a7;
	delete a8;
	delete a9;	

	return 	0;
} 

 5.线索二叉树的解释

线索二叉树是链表表示的树,它是利用了二叉树未被使用的 n + 1个闲置的指针构成的树;
根据二叉树的三种遍历方式构成了三种不同的线索二叉树;
二叉树的遍历只能从根结点开始依次遍历,而构建了线索二叉树后,就可以从二叉树中任何一个结点进行遍历,这就是线索化最大的意义了;
实际上线索二叉树的应用面是很窄的,但是学习它最重要的意义还是理解它的这种思想;
就是将闲置的空间充分利用起来,这应该是最重要的意义了;

本质:

二叉树的遍历本质上是将一个复杂的非线性结构转换为线性结构,使每个结点都有了唯一前驱和后继(第一个结点无前驱,最后一个结点无后继)。对于二叉树的一个结点,查找其左右子女是方便的,其前驱后继只有在遍历中得到。为了容易找到前驱和后继,有两种方法。一是在结点结构中增加向前和向后的指针,这种方法增加了存储开销,不可取;二是利用二叉树的空链指针。

优势

(1)利用线索二叉树进行中序遍历时,不必采用堆栈处理,速度较一般二叉树的遍历速度快,且节约存储空间。

(2)任意一个结点都能直接找到它的前驱和后继结点。 

不足

(1)结点的插入和删除麻烦,且速度也较慢。

(2)线索子树不能共用。 

密语:

其实无论是什么样的序列化,只需要记得下面这三句话:

  • 如果结点有左孩子:那么Lchild指向他的左孩子,否则指向遍历序列中他的前驱结点。
  • 如果结点有右孩子:那么Rchild依然指向他的左孩子,否则指向遍历序列的后继节点。
  • 前驱或者后驱没有,就为NULL。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值