二叉树的C语言实现及遍历

前言

树是一种十分重要的基础数据结构。很多实际问题都会抽象成树来解决。而这篇文章要讲的二叉树则是树中最基本又很重要的一种特殊树。它只的是每个节点的度不大于2的树。
在这里插入图片描述
基于二叉树还有很多衍生的树。查找树,红黑树,平衡树等等等等。

基础思路

二叉树可以很形象的看做是一个递归的产物。所以二叉树的问题,基本都可以用递归来解决。无论一颗二叉树是什么样子的。他都可以看做三部分:本节点,左子树,右子树。
左子树也可以看成本节点,左子树,右子树。这样的结构。
在这里插入图片描述
就像图片上这样。所以一个树的节点需要三部分。保存本节点的数据。指向左节点(左子树),指向右节点(右子树)。当不存在左节点(左子树)或者右节点(右子树)时,则指向空就好。
于是我们就可以构造一个二叉树所需要的节点。

typedef struct TreeNode
{
	BTtype _val;
	struct TreeNode* _left;
	struct TreeNode* _right;
}BinaryTreeNode;

当然,根据需要一个节点也可以保存很多数据,比如一个学生的姓名学号生日电话等等等。

二叉树的建立

二叉树有很多种搭建方法。在不同的场景可以有用不同的方法。虽然二叉树是一种非线性的结构。我们依然可以使用数组来保存。
在这里插入图片描述

如果将根节点看做第零个节点的话,他的左孩子节点的下标等于父节点的下标的二倍加一,右孩子节点的下标等于父节点的下标的二倍加二。这样比较简单我就不贴代码了。
我们还是利用在上面建的节点。利用链式构成二叉树。我们确定一个根节点。然后让比根节点小的去左节点,比根节点大的去右节点。这样构成的树是一颗二叉查找树。当我们用中序遍历时,遍历结果就是升序的。


BinaryTreeNode* InitTree(BTtype *a,int nums)  //建树
{
	BinaryTreeNode* root;
	root = NULL;
	BinaryTreeNode* cur,*prev;
	for (int i = 0; i < nums; ++i)
	{
		BinaryTreeNode* newnode = (BinaryTreeNode*)malloc(sizeof(BinaryTreeNode));
		newnode->_val = a[i];
		newnode->_left = NULL;
		newnode->_right = NULL;
		if (NULL == root)   //说明这是树的第一个节点。
		{
			root = cur = newnode;
		}
		else     //执行else说明这棵树是有根节点的。
		{
			while (TRUE)
			{
				if (cur != NULL)   // 当前位置已经有节点了,所以要继续向下找位置
				{
					prev = cur;
					if (newnode->_val < cur->_val)  //向左子树移动
					{
						cur = cur->_left;
					}
					else if (newnode->_val > cur->_val)  //向右子树移动
					{				
						cur = cur->_right;
					}
				}
				else   //说明当前节点可以是空的 可以保存
				{
					if (prev->_val > newnode->_val)
						prev->_left = newnode;
					else
						prev->_right = newnode;
					break;
				}
			}
			cur = root;    //节点保存完毕, 需要返回root重新开始判断下一个节点往哪里保存。
		}
	}
	return root;
}

二叉树的遍历

我们提到了二叉树很形象的是递归的产物。 所以二叉树的问题用递归的思想会很方便处理。我们假设现在有一颗只有这样一颗只有三个节点的二叉树。
在这里插入图片描述

二叉树的遍历又有三种顺序:

  • 前序遍历: 根节点,左节点 ,右节点
  • 中序遍历:左节点,根节点,右节点
  • 后序遍历: 左节点,右节点,根节点

而我们每遍历一个节点时,使用的方法都是一样的,所以我们只需要利用递归的想法。例如前序遍历: 先用前序遍历左子树,然后输出根节点,在用前序遍历右子树。当程序进入“前序遍历左子树”时,依然会满足这个逻辑。这样就可以完成二叉树的递归遍历。

void InOrder(BinaryTreeNode *root)    //中序遍历
{
	if (root != NULL)
	{
		InOrder(root->_left);
		printf("%d\n",root->_val);
		InOrder(root->_right);
	}
}
void PrevOrder(BinaryTreeNode *root)   // 前序遍历
{
	if (NULL != root)
	{
		printf("%d\n",root->_val);
		PrevOrder(root->_left);
		PrevOrder(root->_right);
	}
}
void PostOrder(BinaryTreeNode* root)  // 后序遍历
{
	if (NULL != root)
	{
		PostOrder(root->_left);
		PostOrder(root->_right);
		printf("%d\n", root->_val);
	}
}

在解决其他树的问题的时候也是这样的,掌握递归的思想会很有效的解决一些树的问题。但是,虽然递归是一个很好地解决问题的途径。但是递归会带来一些问题。比如没递归一次就需要重新开辟一片内存来跑函数。所以如果不能确定的控制递归的结束条件。递归就会无穷尽的下去。或者当递归层数很多时,代码的效率会很低。也很浪费内存。所以一些地方甚至会要求请尽量不要递归。当然,递归依然是一个处理问题的有效手段。
像求一颗树的节点个数,叶子节点个数,翻转二叉树等等问题上,递归依然可以让我们很快速的写出代码。

nt TreeSize(BinaryTreeNode *root)  //求树大小
{
	if (root == NULL)
		return 0;
	return TreeSize(root->_right)+TreeSize(root->_left)+1;

}

int TreeLeafSize(BinaryTreeNode *root)   //叶子数
{
	if (root == NULL)
		return 0;
	if (root->_left == NULL && root->_right == NULL)
		return 1;
	return TreeLeafSize(root->_left) + TreeLeafSize(root->_right);
	
}
int maxDepth(struct TreeNode* root)   //  二叉树深度
{

	int left, right;
	if (!root)
		return 0;
	left = maxDepth(root->_left);
	right = maxDepth(root->_right);
	return left > right ? left + 1 : right + 1;
}
struct TreeNode* invertTree(struct TreeNode* root) {  //翻转二叉树

	if (root != NULL)
	{
		struct TreeNode* newnode;
		newnode = root->_left;
		root->_left = root->_right;
		root->_right = newnode;
		invertTree(root->_left);
		invertTree(root->_right);
	}
	return root;
}

版权声明:本文为sy1227081317原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。