文章主要介绍了二叉树,包含其构造与一些简单应用,文章代码使用c语言实现,对初学者友好。如有错误,还望各位不吝赐教。

一、基本概念

1、定义

考虑到平台都是大佬,此处就简单介绍下。😀😀

a、结点

  • 根节点、分支节点与叶子节点;
  • 双亲节点与孩子节点;

b、度

一个节点包含子树的数量,也就是它的孩子节点数目,就是它的度;

  • 简单应用: 一棵树中,所有节点度的和+1,即为节点的数量。
  • 例题:在一颗度为3的树中,度为3的节点有2个,度为2的节点有1个,度为1的节点有2个,则叶子节点有多少个? 叶子节点数: 2×3+1×2+2×1+1 -(2+1+2) = 6

c、深度(高度)

  • 固定节点数有: 最大深度=节点数,即每一层一个节点; 最小深度$n=\log_{q}{\left ( S \times \left ( q-1 \right )+1 \right ) }$,其中S为总节点数,q为深度。其实就是等比求和公式的变体:$S=\frac{a_{1} \left ( 1-q^{n} \right ) }{1-q}$。在其中,由于根节点只有1个,所以$a_{1} =1$。
  • 例题:一颗拥有1000个节点的树度为4,则它的最小深度是? image.png 5.7>5,所以最小深度为6。

二、二叉树

每个节点的度最多为2,每个节点都有且只有左右子树(左右子树可能为空)。

1、常用性质

  • 一棵二叉树中,叶子节点的数目等于度为2的节点数目+1,即$n_{0} =n_{2} +1$。 证明: 二叉树中只有度为0、1、2的结点,故:$S=n_{2} +n_{1}+n_{0}$; 又,二叉树中除根节点外,每个节点都有枝干,故:$S=2 \times n_{2}+n_{1}+1$;所以有:$n_{2} +n_{1}+n_{0} = 2 \times n_{2}+n_{1}+1$ 即$n_{0} = n_{2}+1$。
  • 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是$2^{h} -1$; 证明:每一层结点数最多为$2^{h-1}$, 有等比数列求和公式即得;
  • 规定根节点的层数为1,具有n个结点的满二叉树的深度为$h=\left \lceil log_{2} \left ( n+1 \right ) \right \rceil$(向上取整); 证明:n个节点,则有 $2^{h-1}-1< n\le 2^{h}-1$; 故 $h-1<log_{2} \left ( n+1 \right ) \le h$ ,从而得证。

2、构造

数组中元素按前序排列,其中以‘#’号代表空,以此数组构造二叉树。 例如:ABD##E#H##CF##G##,此时树的形状如下。 image.png

构造难点

  • 参数选择: 选择元素时,一个元素使用1次,因此,下标i应当随着调用而逐渐变化,故i应传址调用;
  • 递归顺序: 元素按前序排列,因此同样按前序构造二叉树;

代码

// 申请节点
BTNode* BuyNode(BTDataType x)
{
	BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
	if (!tmp)
	{
		perror("malloc fail");
		exit(-1);
	}
	// 修改值
	tmp->data = x;
	tmp->left = tmp->right = NULL;
	return tmp;
}

BTNode* TreeCreate(BTNode* root, BTDataType* a, int* pi, int n)
{
	// #号返回NULL
	if (a[(*pi)] == '#')
	{
		(*pi)++;
		return NULL;
	}
	root = BuyNode(a[(* pi)++]);
	root->left = TreeCreate(root->left, a, pi, n);
	root->right = TreeCreate(root->right, a, pi, n);
	return root;
}

BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
	// 创建根节点,随后构造二叉树
	BTNode* ret = NULL;
	ret = TreeCreate(ret, a, pi, n);
	return ret;
}

三、简单应用

在解决树的相关问题时,常常需要考虑分解问题的做法。借由二叉树的逻辑,一般考虑三个小问题:

  • 终止条件
  • 问题分解
  • 特例 除了以上几个基本问题,根据不同的需求,有时需要考虑不同的的特例。

1、简单版

a、求二叉树的深度

  • 终止条件:当节点为空时,返回0;
  • 问题分解:树的高度等于最深节点的层数,也就是说,可以分别求左右子树高度的较大值,+1即为树的总高度。

代码如下:

int maxDepth(struct TreeNode* root) {
	//终止条件
	if(root == NULL)
	{
	   return 0;
	} 
	// 问题分解
	return fmax(maxDepth(root->left), maxDepth(root->right)) + 1;
}

b、二叉树节点个数

节点为空时返回0,节点不为空时,返回左树节点个数+右树节点个数+1,递归求解;

  • 终止条件:空节点;
  • 问题分解:左树数量+右树数量+1;
int BinaryTreeSize(BTNode* root)
{
	return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

c、二叉树叶子节点个数

思路同上,但增加判断条件,节点为空时返回0;节点的左右子树都为空时,表明此时为叶子节点,返回1;同样,叶子节点总数等于左子树中叶子的数量+右树中叶子的数量;

  • 终止条件:1、节点为空;2、节点为叶子节点;
  • 问题分解:左树叶子数量+右树叶子数量;
int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	if (!root->left && !root->right)
		return 1;

	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

d、二叉树第k层节点数量

改变判断条件,当到达第k层且节点不为空时,也就是k=1时,返回1;

  • 终止条件:1、空结点;2、到达第k层,结点不为空;
  • 问题分解:左树k层结点数量+右树k层结点数量;
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (k == 1 && root)
	{
		return 1;
	}
	if (root == NULL)
	{
		return 0;
	}

	return  BinaryTreeLevelKSize(root->left, k - 1) 
		+ BinaryTreeLevelKSize(root->right, k - 1);
}

e、二叉树中查找值为x的节点

先找根节点,随后找左右子树,若找到,则返回对应节点的地址,未找到则继续找,遇到空返回;

  • 终止条件:找到节点;空节点;
  • 问题分解:先找自身,随后找左右子树;
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;
	BTNode* ret = BinaryTreeFind(root->left, x);
	// 在左边找到了
	if (ret)
		return ret;
	ret = BinaryTreeFind(root->right, x);
	// 在右边找到或没找到都可直接返回
	return ret;
}

f、普通遍历(前、中、后)

根据遍历的不同修改访问节点语句的位置,从而达到不同遍历的效果;

  • 终止条件:空节点;
  • 问题分解:访问左右子树的不同顺序;
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
		return;
	printf("%c ", root->data);
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
}
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
		return;
	BinaryTreePrevOrder(root->left);
	printf("%c ", root->data);
	BinaryTreePrevOrder(root->right);
}

void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
		return;
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
	printf("%c ", root->data);
}

g、层序遍历

层序遍历的实现需要借助队列,当队列为空时,将树中节点入队,不为空时出队,同时将其左右孩子入队。

void LevelOrder(Queue* q, BTNode* root)
{
	// 访问到空节点之后返回
	if (root == NULL)
		return;
	QueuePush(q, root);
	// 否则出队
	while (!QueueEmpty(q))
	{
		BTNode* r = QueueFront(q);
		QueuePop(q);
		printf("%c ", r->data);
		if (r->left)
			QueuePush(q, r->left);
		if (r->right)
			QueuePush(q, r->right);
	}
}

void BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);

	LevelOrder(&q, root);

	QueueDestroy(&q);
}

h、判断二叉树是否为完全二叉树

采用与层序遍历相同的原理,先将树中的节点依次入队,当发现队头为空时,停止入队;此时,若队列中还有非空数据,则表明二叉树不是完全二叉树;若二叉树为完全二叉树,则空节点必定是队列中最后一个节点;

int BinaryTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	// 根入队
	if (root == NULL)
	{
		return true;
	}
	QueuePush(&q, root);
	// 将树中节点依次入队,遇到空则停止入队
	while (!QueueEmpty(&q))
	{
		BTNode* f = QueueFront(&q);
		if (f == NULL)
		{
			break;
		}
		QueuePop(&q);
		if(root->left)
			QueuePush(&q, f->left);
		if(root->right)
			QueuePush(&q, f->right);
	}
	// 判断剩余的元素中是否非空元素
	while (!QueueEmpty(&q))	
	{
		BTNode* f = QueueFront(&q);
		QueuePop(&q);
		// 存在非空节点
		if (f != NULL)
		{
			QueueDestroy(&q);
			return false;
		}
	}
	QueueDestroy(&q);
	return true;
}