二叉树的构建;求叶子节点个数;最小栈的三种解法;判断一棵树是完全二叉树;二叉树的前序,后续,中序遍历,不使用递归实现;

二叉树的构建
二叉树如果是以 1 2 3 4 5 6 7 8 9 这种形式给出的话那么只是一个前序 后续,或者中序遍历是无法拿到准确的树形结构的,但是当给出标志位之后,例如1 2 4 6 # # # 5 # # 3 # #之后便可以通过一个遍历还原出树形结构,首先先讲一下,通过标志位来构建树

问题:编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。

#include<iostream>
#include<string.h>
#include<string>
using namespace std;
class TreeNode   //这个是二叉树的结点
{
public:
 	TreeNode* left;
 	TreeNode* right;
 	char c;
 	TreeNode(char x)
  		:c(x)
  		, left(nullptr)
  		, right(nullptr)
 		{}
};
struct ret  //以及返回值的封装,我们要拿到两个返回值就封装了一种类型
{
 	TreeNode* root; //当前构建的节点
 	int used;  //构建过程中用掉的val个数
};
ret CreateTree(char * s, int size)   //主干函数
{
 	if (size == 0)   //特殊情况,当size减为0的时候
 	{
  		ret result;
  		result.root = nullptr;
  		result.used = 0;
  		return result;
 	}
 	char rootVal = s[0];
 	if (rootVal == '#')  //特殊情况,对标志位进行判断
 	{
  		ret result;
  		result.root = nullptr;
  		result.used = 1;
  		return result;
 	}
 	//根
 	TreeNode* tree = new TreeNode(s[0]); //新建结点并进行构造
 	ret left_result = CreateTree(s + 1, size - 1);  //然后进行左子树的构造,传的参数-1是因为根节点已经用掉了一个
 	ret right_result = CreateTree(s + 1 + left_result.used, size - 1 - left_result.used); //右子树的构建要 减去根节点的长度,以及左子树的长度
 	tree->left = left_result.root;   //然后让根节点的左
 	tree->right = right_result.root;  //根节点的右
 	ret p;     返回值
 	p.root = tree;    //包含本层所建立的根然后返回让上一级结点指向这个结点
 	p.used = 1 + left_result.used + right_result.used;
 	return p;
}
void InorderTraversal(TreeNode* p)   //中序遍历最后打印出树
{
 	if (p == nullptr)
 	 	return;
 	InorderTraversal(p->left);
 	cout << p->c << " " ;
 	InorderTraversal(p->right);
}
void Test()
{
 	char s[200];
 	cin>>s;
    	int size = strlen(s);
 	ret result = CreateTree(s, size);
 	InorderTraversal(result.root);
}
int main()
{
 	Test();
 	return 0;
}

求叶子结点个数:

//方法1利用递推
int _1(TreeNode * tree)
{
 	if (tree == nullptr)
  		return 0;
 	if (tree->left == nullptr && tree->right == nullptr)
  		return 1;
 	return _1(tree->left) + _1(tree->right);
}


//方法二利用累加
int _2(TreeNode * tree,int& c)
{
 	if (tree->left == nullptr && tree->right == nullptr)
  		c++;
 	_2(tree->left);
 	_2(tree->right);
 	return c;
}

最小栈的三种解法
1.利用双栈实现
2.利用一个栈实现,压得时候压两个,弹的时候弹两个,访问栈顶的话需要四步,先弹出并保存,然后再访问出栈中的有效元素的栈顶元素,然后再将最小值重新压入栈中
3.捆绑,自己定义一个类型,然后里面还有两个值一个是压入的元素,一个是最小的元素,类似于第二种方法,但是将第二种方法进行了封装,然后用->访问你所想要访问的元素


层序遍历:

void _1(TreeNode* tree)
{
 	queue<TreeNode*> que;
 	if (tree == nullptr)
 		 return;
 	que.push(tree);
 	while (!que.empty())
 	{
 		 cout << que.front()->val << endl;
 		 if (que.front()->left)
 		 {
 			  que.push(que.front()->left);
 		 }
 		 if (que.front()->right)
 		 {
 			  que.push(que.front()->right);
 		 }
		 que.pop();
 	}
}

前序,后序,中序遍历,是深度优先,需要栈
层序遍历是广度,优先利用的是队列




判断一棵树是完全二叉树
其实际上和层序遍历是差不多的,只是发生了一些改进
假如保留叶子结点的左右空结点,那么普通二叉树在全部进栈之后肯定中间断断续续有空的存在,也就是说空的后面还有有效元素。但是完全二叉树就是在为遇到第一个空结点之后后面就不会再有有效元素了。

bool _1(TreeNode* tree)
{
 	queue<TreeNode*> que;
 	if (tree == nullptr)
 		 return true;
 	que.push(tree);
 	while (1) //首先在队列中找到第一个空
 	{
 		if(que.front() == nullptr)  //遇到空结点则跳出
 		{
 			break;
		}
 		que.push(que.front()->left);  //无论是否是空直接压入
 		//这里一个注意的地方就是,你可以画图看看,哪怕不是完全二叉树,也不可能将一个空结点的左右压入,因为是空的话就跳了出去
  		que.push(que.front()->right);
  		que.pop();
 	}
 	//接着再看队列中空结点后面是否还存在有效元素就可以了
 	while(!que.empty())
 	{
 		if(que.front()!=nullptr)
 		{
 			return false;
 		}
 		que.pop();
	}
	return true;	
}




二叉树的前序,后续,中序遍历,不使用递归实现:

在这里插入图片描述

首先我们来剖析一下这张图片
首先我们要前中后序遍历二叉树,那么我们非递归的写法就是将一个节点分为三份,就是说会三次路过同一个结点,然后遍历的过程主要是利用栈,来进行节点的压栈,主要难点就是每次循环的条件

void Preorder(TreeNode* tree)
{
 	TreeNode* cur = tree;   //用来遍历整个链表
 	TreeNode* last = nullptr;  //记录的是上一个被三次访问结束的结点  
 	TreeNode* top = tree;     //用来记录栈的顶端元素
 	stack<TreeNode*> sta;
 	while (cur != nullptr || sta.empty() != true)
 	{
 		 while (cur != nullptr)
  		{
   			sta.push(cur);
   			//cout<<cur->val<<endl;    //前序遍历的打印位置
   			cur = cur->left;
  		}
  		top = sta.top();
  		if (cur->right != nullptr&&top->right != last)  //top->right == last 的意思就是现在栈顶 元素的右边是已经三次访问结束的元素 
  		{
  		//进入这次循环一般是第二次访问
  		//循环的判断条件有两个一个是当前节点的右结点不为空,第二个判断条件就是见下图
  			 cur = top->right;
  		}
  		else
  		{
  		//进入这次循环一般是第三次访问
  			//cout<<top->val<<endl;    //中序遍历的打印位置
  			 last = top;
  			 sta.pop();
  		}
 	}
}
//中序遍历我么们待会再讲

在这里插入图片描述

当便利到D的时候,栈中已经压入了,A,B,D,然后进入D的右边,再进行压栈,G,H,等到遍历完毕出来的时候G这边,是已经遍历结束了的,所以last就标记着G 点,虽然D的右子树不为空,但是是已经遍历过的结点了,所以也不进入。

//后序遍历打印的时候会存在一个问题

中序遍历:

void Preorder(TreeNode* tree)
{
 	TreeNode* cur = tree;   //用来遍历整个链表
 	TreeNode* last = nullptr;  //记录的是上一个被三次访问结束的结点  
 	TreeNode* top = tree;     //用来记录栈的顶端元素
 	stack<TreeNode*> sta;
 	while (cur != nullptr || sta.empty() != true)
 	{
 		 while (cur != nullptr)
 		 {
 			sta.push(cur);
 			cur = cur->left;
  		}
  		top = sta.top();
  		if(cur->right == nullptr)   //中序遍历的话要在这里加上一个条件打印
  		{ 
  			cout<<top->val<<endl;
  		}
  		if (cur->right != nullptr&&top->right != last)   
  		{
  			 cout<<top->val<<endl;   //并且这个里面也是需要打印的
  			 cur = top->right;
  		}
  		else
  		{
  			 last = top;
  			 sta.pop();
  		}
 	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值