手写二叉树(c++)

手写实现二叉树

今天来试着今天试着自己写一个二叉树。包括各种遍历,求树高,节点数的接口的实现。

(1)二叉树节点的定义

下面是二叉树的节点定义

typedef struct Node
{
	int val;
	struct Node* left;
	struct Node* right;
	Node(int a = 0, Node* b = nullptr, Node* c = nullptr) :val(a), left(b), right(c) {}
}Node;

(2)二叉树类及其接口

定义相关接口和构造函数

class binary_tree
{
public:
	Node* root;
	binary_tree():root(nullptr){}
	//还原数组存储的二叉树
	binary_tree(int arr[],int size);
	//前序遍历
	void prePrint();
	//中序遍历
	void inPrint();
	//后序遍历
	void posPrint();
	//层序遍历
	void print();
	//判断树是否为空
	bool isEmpty() { return root==nullptr;}
	//树的节点个数
	int size();
	//树的高度
	int height();
	//查找节点
	Node findkey(int tmp);
	//非递归前序遍历
	void prePrintStack();
	//非递归中序遍历
	void inPrintStack();
	//非递归后序遍历
	void posPrintStack();

(3)接口和其构造函数

1.构造函数的实现

空构造就不必说了,我们知道数组也是可以来存储二叉树的。

1.1数组存储二叉树的原理

对于完全二叉树,我们以 i 表示节点序号,如上图,i 从上至下,从左至右编号。那么这个完全二叉树满足以下性质:

1.如果i > 1,那么它的父节点为 i/2 (整除)。
2.与上一点相反,如果一个节点存在分支节点,那么它的左子节点为 2i,右子节点为2i + 1。
3.上一点继续推导可以得出,如果2i > n 那么这个结点没有分支节点,它是一个叶子点。
4.如果 2i + 1 > n 那么这个结点没有右分支节点。
5.如果2i == n,这个二叉树就不是完全二叉树。

有了这些性质我们可以把一棵二叉树装到数组中存储,方法如下:
我们可以假定任意一棵树为完全二叉树,根节点下标为1,不存在的节点我们可以存特殊值(这里我用的-1),如此我们可以根据以上性质,在一个数组中存储二叉树,以节点编号为数组下标即可!
在这里插入图片描述

如上图的二叉树,我们可以把它存在数组中,如下图所示

在这里插入图片描述
下面就可以来写通过数组来还原构造二叉树了,通过递归的方式还原出这棵树,代码如下:

//递归函数的实现
Node* create_tree(int arr[],int i, int size)
{
	//判断是否越界或不存在节点
	if (i >= size||arr[i]==-1)
	{
		return nullptr;
	}

	//开始递归构造二叉树
    Node*tmp= new Node(arr[i]);
    tmp->left = create_tree(arr, 2 * i, size);
    tmp->right = create_tree(arr, 2 * i + 1, size);
	return tmp;
}
binary_tree::binary_tree(int arr[],int size)
{
	int i = 1;
	root=create_tree(arr,i,size);
}

2.各种遍历的实现

2.1前序遍历两种写法(递归与非递归)

这里提供两种写法(其实还可以通过Morris遍历)

传统的递归写法:

//前序遍历
void preprint(Node* root)
{
	if (root == nullptr)return;
    //根左右的顺序
	cout << root->val;
	preprint(root->left);
	preprint(root->right);
}
void binary_tree::prePrint()
{
	preprint(root);
}

通过栈来实现的迭代写法:

//非递归的先序遍历
//根左右,由于栈先进后出,所以我们push完根,要先push右,再push左
void binary_tree::prePrintStack()
{
	stack<Node*>st;
	if(root)st.push(root);

	while (!st.empty())
	{
		Node* t = st.top();
		st.pop();
		cout << t->val;

		if(t->right)st.push(t->right);
		if(t->left)st.push(t->left);
	}
}

这里由于栈是先进后出,我们如果把根出栈后,然后先push左子树,再push右子树,这样的话,出栈的顺序就是右左,显然不行。我们要想实现根左右,那我们根出栈以后,因该先push右子树,再push左子树

2.2中序的两种写法

传统的递归写法

//中序遍历
void inprint(Node* root)
{
	if (root == nullptr)return;

	inprint(root->left);
	cout << root->val;
	inprint(root->right);
}
void binary_tree::inPrint()
{
	inprint(root);
}

通过栈来实现的迭代写法:

void binary_tree::inPrintStack()
{
	stack<Node*>st;
	Node* cur = root;
	//定义一个指针从root开始
	while (cur||!st.empty())
	{
		if (cur != NULL)
		{
			//如果不为空,一直往左边走,并入栈
			st.push(cur);
			cur = cur->left;
		}
		else
		{
			//如果走到空了,开始输出根节点。
			cur = st.top();
			st.pop();
			cout << cur->val;
			//开始处理右子树
			cur = cur->right;
		}
	}
}

中序遍历的顺序是左根右,我们想通过迭代来模拟递归。那么开始处理根节点的时机,就是处理完左子树,才开始输出根节点,再开始去处理右子树。那么迭代可以这样去模仿:我们定义一个cur变量,它的是左子树不为空时,就不断把cur入栈,当它左子树为空时,我们输出根节点,并把cur移动到右子树并入栈,当栈为空且cur为NULL时结束。

2.3后序遍历的两种写法

后序的递归写法

void posprint(Node* root)
{
	if (root == NULL) return;

	posprint(root->left);
	posprint(root->right);
	cout << root->val;
}
void binary_tree:: posPrint()
{
	posprint(root);
}

后序的非递归写法

oid binary_tree::posPrintStack()
{
	stack<Node*> st;
	Node* node = root;
	Node* mark = NULL;
	while (!st.empty() || node != NULL)
	{
		if (node != NULL)
		{
			//存在左子树,向左子树走
			st.push(node);
			node = node->left;
		}
		else
		{
			//不存在左子树,开始回溯到根
			Node* t = st.top();
			//判断右子树存在,且被没被mark标记(说明这是第一次走到父节点,并不是回溯到父节点)
			if (t->right != NULL && mark !=t->right)
			{
				node = t->right;
			}
			else
			{
			//如果不存在右子树或该右子树被mark标记(说明这是已经处理的右子树,回溯到父节点)
				cout << t->val;
				mark = t;
				st.pop();
			}
		}
	}
}

后序遍历就比较复杂了,一般不会涉及。想学习的可以看下面这个视频,讲的挺清楚的。

讲解视频(二叉树非递归后序遍历)

2.4 层序遍历

利用队列进行层序遍历

void binary_tree::print()
{
	//利用队列实现层序遍历
	queue<Node*>q;
	if (root != NULL) q.push(root);
	while (!q.empty())
	{
		//记录上一层结点的个数
		int size = q.size();
		for (int i = 0; i < size; i++)
		{
			//取出队头元素
			Node* t = q.front();
			q.pop();

			cout << t->val;
			if (t->left) q.push(t->left);
			if (t->right) q.push(t->right);
		}
		//输出完一层
		cout << endl;
	}
}

3. 其他接口的实现

3.1 获取节点个数

采用递归向下搜索,回溯带回节点个数。
树的节点数=左子树节点数+右子树节点数+1(根节点)

//树的节点个数
int getsize(Node*root)
{
	if (root == nullptr) return 0;
	//如果是树叶
	if (root->left == nullptr && root->right ==nullptr) return 1;

	return getsize(root->left) + getsize(root->right)+1;
}
int binary_tree::size()
{
	return getsize(root);
}

3.2 获取树高

树高的定义:二叉树结点层次的最大值,也就是其左右子树的最大高度+1
height=max(heightmax左子树+heightmax右子树)+1;
这样我们就可以递归处理左右子树,最后求出树高。

//树的高度
int getheight(Node* root)
{
	if (root == nullptr) return 0;
	return max(getheight(root->left), getheight(root->right)) + 1;
}
int binary_tree::height()
{
	return getheight(root);
}

3.3查找节点
//查找结点
void dfs(Node* root,Node&t,int tmp)
{
	if (root == nullptr) return;
	if (root->val = tmp)
	{
		t = *root;
		return;
	}
	dfs(root->left,t,tmp);
	dfs(root->right,t,tmp);
}
Node binary_tree::findkey(int tmp)
{
	Node t;
	dfs(root,t,tmp);
	return t;
}

(4)总结

二叉树的大部分问题可以通过递归解决。当树的节点多时,递归会出现,函数大量压栈,占用栈空间出现爆栈,所以出现了非递归的前中后序遍历。但本质上时间复杂度和空间复杂度都是O(N),没有变化,仅解决了爆栈的问题。那有没有更好的算法呢?
答案是有的,Morris遍历可以实现前中后序的遍历,且时间复杂度为O(N),空间复杂度为O(1),优化了空间复杂度。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

<Augenstern>

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值