用递归玩转简单二叉树

前言:

数据结构学到二叉树,就进入到了有难度的部分了,但难度对应着重要性,其重要性也不言而喻了。这节我会介绍用C语言实现递归方法的二叉树的一些重要基本功能,在二叉树中又属于基础知识,有需要的各位必须要好好掌握,我会详细地讲解,必要的递归步骤也会以图示展示出来,以便大家更好的理解。

注:会给出部分递归图示,但更多的是各位自己实践一下,二叉树用递归实现难度不大,但是一定要知道递归的原理,而理解原理最好的方法就是画递归图,尽管耗时较多,但一定要画一画!!!

目录

一:树的简单介绍

(1) 树的基本概念

(2) 树的结构及相关解释

(3) 树的表示方法及实际运用

二:二叉树的基本介绍

(1) 二叉树的基本概念及结构

(2) 两种特殊的二叉树

(3) 二叉树的重要性质

(4) 二叉树的两种存储结构

三:二叉树链式结构的实现

(1) 构建一颗二叉树

(2) 二叉树的简单遍历

1. 前序遍历 ( 根—左子树—右子树 )

2. 中序遍历—— ( 左子树—根—右子树 )

3. 后序遍历—— ( 左子树—右子树—根 )

(3) 二叉树的节点个数及深度的计算

1. 二叉树总结点个数

2. 二叉树叶节点个数

3. 二叉树第K层节点个数

4. 二叉树的深度

(4) 二叉树中节点的查找

代码及思路展示:

递归图解:

(5) 二叉树的销毁

四:二叉树与队列的综合应用

(1) 二叉树的层序遍历

(i) 实现思路

(ii) 过程图示

(iii) 代码实现

(2) 判断一棵二叉树是否为完全二叉树

(i) 实现思路

(ii) 过程图示

(iii) 代码展示

五:树、森林与二叉树的相互转换

六:相关代码的完整展示

(1) Test.c

(2) BinaryTree.h

(3) BinaryTree.c

(4) Queue.h

(5) Queue.c



一:树的简单介绍

(1) 树的基本概念

简述:

是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

树有一个特殊的结点,称为根结点,在整棵树的顶部。除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i<= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个父节点,可以有0个或多个子节点。

性质:

1. 树的特点是不相交,所以不可能有多个路径同时到达一个点
2. 树是递归定义的。(根+左子树+右子树)

3. n个节点的树的总边数为n-1条 (点 = 边+1,即任何一棵树中,结点个数比分支个数多一)

(2) 树的结构及相关解释

 节点的度:一个节点含有的子树的个数称为该节点的度;  如上图:A的为6
 叶节点:度为0的节点称为叶节点; 如上图:B、C、H、I...等节点为叶节点
 非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G、J为分支节点
 父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图: A是B的父节点
 子节点:一个节点含有的子树的根节点称为该节点的子节点;  如上图:P、Q是J的子节点

 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:K、L、M是兄弟节点

 堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为堂兄弟节点
 树的度:一棵树中,最大的节点的度称为树的度;  如上图:树的度为6
 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推

 树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
 节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图: 所有节点都是A的子   孙

(3) 树的表示方法及实际运用

树的表示方法:

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既然保存数据域,也要保存结点和结点之间的关系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的孩子兄弟表示法

图示:

树的实际运用:表示文件系统的目录树结构(文件目录)


二:二叉树的基本介绍

(1) 二叉树的基本概念及结构

基本概念:

一棵二叉树是结点的一个有限集合,该集合: 

1. 为空
2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成
特点:
1. 二叉树不存在度大于2的结点
2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

结构:

(2) 两种特殊的二叉树

一:满二叉树

一棵二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是
说,如果一个二叉树的层数为K,且结点总数是 2^k-1,则它就是满二叉树

二:完全二叉树

完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树引出来的。对于深度为K
的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树(各层节点连续)。 要注意的是满二叉树是一种特殊的完全二叉树。

结构:

(3) 二叉树的重要性质

1. 若规定根节点的层数为1,则一棵非空二叉树的第k层最多2^(k-1)个结点.
2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^k-1(满二叉树).
3. 对任何一棵二叉树, 如果度为0的叶结点个数为n0, 度为2的分支结点个数为n2 ,则有

n0=n2 +1
4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度h= log(n+1).

(注:即log以2为底,n+1为对数)(由n=2^h-1推得)

一个小结论:一个拥有n个结点的二叉树的深度一定在[log(n + 1),n]之间
5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号(数组下标)为i的结点有:

(1) 若i>0,i位置节点的父节点序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
(2) 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
(3) 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子

注:第3,5点十分重要,涉及了二叉树结构的计算问题。第5点更是与后面的堆结构息息相关

(4) 二叉树的两种存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。
1. 顺序存储
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
2. 链式存储
二叉树的链式存储结构是指用链表来表示一棵二叉树,即用链表的指针域来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针域分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。

图示说明:


三:二叉树链式结构的实现

由于顺序结构只适合存储完全二叉树,所以这里只介绍链式结构的实现。

(1) 构建一颗二叉树

要执行二叉树的一些功能,首先构建出一棵二叉树,这里会介绍如何简单快速构建一棵二叉树以及如何根据递归的思路正规的构建一棵二叉树。

1. 快速构建一棵二叉树

首先要生成各个节点:

BTNode* BuyBinaryTreeNode(BTDataType x)//生成节点
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	node->data = x;
	node->left = node->right = NULL;
	return node;
}

然后就可以开始构建二叉树:

//构建的第一颗二叉树,不是完全二叉树
BTNode* CreatBinaryTree()//构建一颗二叉树,并返回根节点
{
	BTNode* nodeA = BuyBinaryTreeNode('A');
	BTNode* nodeB = BuyBinaryTreeNode('B');
	BTNode* nodeC = BuyBinaryTreeNode('C');
	BTNode* nodeD = BuyBinaryTreeNode('D');
	BTNode* nodeE = BuyBinaryTreeNode('E');
	BTNode* nodeF = BuyBinaryTreeNode('F');

	nodeA->left = nodeB;
	nodeA->right = nodeC;
	nodeB->left = nodeD;
	nodeC->left = nodeE;
	nodeC->right = nodeF;
	return nodeA;
}

//构建的第二颗二叉树,是完全二叉树
BTNode* CreatBinaryTree()//构建一颗二叉树,并返回根节点
{
	BTNode* nodeA = BuyBinaryTreeNode('A');
	BTNode* nodeB = BuyBinaryTreeNode('B');
	BTNode* nodeC = BuyBinaryTreeNode('C');
	BTNode* nodeD = BuyBinaryTreeNode('D');
	BTNode* nodeE = BuyBinaryTreeNode('E');
	BTNode* nodeF = BuyBinaryTreeNode('F');
	BTNode* nodeG = BuyBinaryTreeNode('G');
	BTNode* nodeH = BuyBinaryTreeNode('H');

	nodeA->left = nodeB;
	nodeA->right = nodeC;
	nodeB->left = nodeD;
	nodeB->right = nodeG;
	nodeC->left = nodeE;
	nodeC->right = nodeF;
	nodeD->left = nodeH;
	return nodeA;
}

构建好的两棵二叉树:

2:利用递归思想构建一棵二叉树 (以上左图为例)

这里会提前涉及到遍历的思想,介绍就在下面,需要先理解好遍历思想再看这里。

构建二叉树的递归介绍:

先给出想要构建的二叉树的先序遍历(eg)序列(NULL也要算上),然后利用该字符串序列通过递归的思路构建二叉树。

eg: 上左图先序遍历得到的字符串序列是ABD###CE##F##其中“#”表示的是NULL,以此先序遍历序列为例构建二叉树的方法如下:

struct TreeNode //结构
{
    struct TreeNode* left;
    struct TreeNode* right;
    char val;
};
void PreOrder(struct TreeNode* root) //先序遍历
{
    if (root == NULL)
    {
        return;
    }
    printf("%c ", root->val);
    PreOrder(root->left);
    PreOrder(root->right);
}
void InOrder(struct TreeNode* root) //中序遍历
{
    if (root == NULL)
    {
        return;
    }
    InOrder(root->left);
    printf("%c ", root->val);
    InOrder(root->right);
}
void PostOrder(struct TreeNode* root) //后序遍历
{
    if (root == NULL)
    {
        return;
    }
    PostOrder(root->left);
    PostOrder(root->right);
    printf("%c ", root->val);
}
struct TreeNode* CreatBinaryTree(char* arr, int* pi) //用字符串构建二叉树!!!
{
    if (arr[*pi] == '#')//NULL就跳过
    {
        (*pi)++;
        return NULL;
    }
    //下面是二叉树的正式构建过程,先开辟父节点,再创建该父节点的左右子树
    struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    if (root == NULL)
    {
        printf("malloc fail\n");
        exit(-1);
    }
    root->val = arr[*pi];
    (*pi)++;
    root->left = CreatBinaryTree(arr, pi);//构建root节点的左子树
    root->right = CreatBinaryTree(arr, pi);//构建root节点的右子树
    return root;
}
int main()
{
    char arr[100] = { 0 };
    gets(arr);
    int i = 0;  //i是字符串中控制字符位置的一个标识,在函数递归调用的过程中为了保证i的变化,需要进行传址调用
    struct TreeNode* root = CreatBinaryTree(arr, &i);//构建一棵二叉树,并返回其根节点
    //用三种遍历进行测试:
    PreOrder(root);
    printf("\n");
    InOrder(root);
    printf("\n");
    PostOrder(root);
    printf("\n");
    return 0;
}

(2) 二叉树的简单遍历

这里主要介绍递归的方式进行二叉树的遍历,以构建的第一棵非完全二叉树为例,需要各位好好画递归图理解一下。(递归图以前序遍历为例,中序遍历与后序遍历需要各位自己实践)

我认为比较好的二叉树遍历方法文章,需要先学习这个知识二叉树的遍历方法

1. 前序遍历 ( 根—左子树—右子树 )

拓展:深度优先遍历是先遍历完一条完整的路径(从根到叶子的完整路径),才会向上层折返,再去遍历下一个路径,前序遍历就是一种深度优先遍历

代码展示:

void BinaryTreePreOrder(BTNode* root)//先序遍历————(根左右)
{
	if (root == NULL)//树为空或达到了最小规模子问题
	{
		return;
	}
	printf("%c ", root->data);
	BinaryTreePreOrder(root->left);
	BinaryTreePreOrder(root->right);
}

递归图解:

第一棵树的先序遍历结果为:ABDCEF

2. 中序遍历—— ( 左子树—根—右子树 )

代码展示:

void BinaryTreeInOrder(BTNode* root)//中序遍历———— (左根右)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreeInOrder(root->left);
	printf("%c ", root->data);
	BinaryTreeInOrder(root->right);
}

第一棵树的中序遍历结果为:DBAECF

3. 后序遍历—— ( 左子树—右子树—根 )

代码展示:

void BinaryTreePostOrder(BTNode* root)//后序遍历———— (左右根)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	printf("%c ", root->data);
}

第一棵树的后序遍历结果为:DBEFCA


(3) 二叉树的节点个数及深度的计算

1. 二叉树总结点个数

int BinaryTreeNodeSize(BTNode* root)//获取二叉树节点个数
{
	if (root == NULL)
	{
		return 0;
	}
	//总结点数 = 左子树节点数 + 右子树节点数 + 根节点
	return BinaryTreeNodeSize(root->left) + BinaryTreeNodeSize(root->right) + 1;
}

递归图解:

2. 二叉树叶节点个数

int BinaryTreeLeafSize(BTNode* root)//获取二叉树叶子节点个数
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)//左右子树都为NULL,表示为叶节点
	{
		return 1;
	}
	//二叉树叶子数=左子树叶子树+右子树叶子数
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

3. 二叉树第K层节点个数

int BinaryTreeKLevelSize(BTNode* root, int k)//获取二叉树第K层节点个数
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)//类比与当k=1时第一层的节点个数就是1
	{
		return 1;
	}
	//第K层节点个数 = 左子树的第k-1层节点个数 + 右子树的第k-1层节点个数
	return BinaryTreeKLevelSize(root->left, k - 1)
		+ BinaryTreeKLevelSize(root->right, k - 1);
}

4. 二叉树的深度

int BinaryTreeDepth(BTNode* root)//求二叉树的深度
{
	if (root == NULL)
	{
		return 0;
	}
	//二叉树的深度 = 左子树与右子树中较大的一个深度 + 1(根节点)
	//先记录,再直接比较得到深度,防止重复递归
	int leftdepth = BinaryTreeDepth(root->left);
	int rightdepth = BinaryTreeDepth(root->right);
	return leftdepth > rightdepth ? leftdepth + 1 : rightdepth + 1;
}

(4) 二叉树中节点的查找

代码及思路展示:

BTNode* BinaryTreeNodeFind(BTNode* root, BTDataType x)//在二叉树中查找值为x的节点
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)//对应的根节点
	{
		return root;
	}
	//在左子树中查找
	BTNode* leftnode = BinaryTreeNodeFind(root->left, x);
	if (leftnode)//查找完为空,就往右子树查找,找到了就直接返回,不要继续查找
	{
		return leftnode;
	}
	//在右子树中查找
	BTNode* rightnode = BinaryTreeNodeFind(root->right, x);
	if (rightnode)//查找完为空,就表示整棵树都没有想要查找的节点
	{
		return rightnode;
	}
	//整棵树中都没有
	return NULL;
}

递归图解:


(5) 二叉树的销毁

//销毁二叉树————类似与后序遍历的思想,将节点从下向上释放
void BinaryTreeDestroy(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreeDestroy(root->left);
	BinaryTreeDestroy(root->right);
	free(root);
}

四:二叉树与队列的综合应用

实现链式二叉树的一些简单功能后,还可以搭配队列实现一些特殊功能。这里要注意,使用C语言实现时,需要用到队列,这里直接使用以前实现好的队列。完成以下两个简单的功能函数,不仅可以增强我们对二叉树结构的理解,还可以对队列的知识进行进一步掌握。

(1) 二叉树的层序遍历

层序遍历:除了前序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1,层序遍历就是从二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

拓展:广度优先遍历需要把下一步所有可能的位置全部遍历完,才会进行更深层次的遍历,层序遍历就是一种广度优先遍历

(i) 实现思路

1. 将根节点push入队列(作为队列中链表节点的数据域)

2. 记录队首节点的数据域(二叉树中的某个节点)

3. pop掉队首节点(free掉队首节点,但是队首节点的数据域不会被free掉)

4. 通过记录的队首节点数据域,将其在二叉树中的非空左右子节点push入队列

5. 循环执行2,3,4步,直至队列为空结束

(ii) 过程图示

(iii) 代码实现

//层序遍历————用队列实现 (二叉树的各个节点在push,pop队列时,是作为队列中链表节点的数据域)
void BinaryTreeLevelOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	//思路:利用队列,将二叉树的根节点先push入队列,获取并记录完后再pop出队列,
	//以记录的节点(队首节点的数据域,并且会不断变化)为根节点,再将其的左右非NULL子节点push入队列,
	//随着队首元素的不断pop,队首元素会不断发生变化,而队首元素可以以层序遍历的方式将二叉树的所有节点带入队列中,从而达到层序遍历的效果
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);//1.祖先节点入列
	while (!QueueEmpty(&q))//队列为空就结束
	{
		BTNode* front = QueueFront(&q);//front表示队首节点的数据域,也表示二叉树中的节点
		QueuePop(&q);//pop
		printf("%c ", front->data);
		//将记录的队首元素数据域的左右非空子节点入列
		if (front->left)
		{
			QueuePush(&q, front->left);
		}
		if (front->right)
		{
			QueuePush(&q, front->right);
		}
	}
	QueueDestroy(&q);
}

(2) 判断一棵二叉树是否为完全二叉树

判断一棵二叉树是否为完全二叉树同样需要用到队列的思想,较上面的层序遍历而言,相似而又复杂一些。我们需要知道完全二叉树是各层节点连续的二叉树,所以可以利用其第一个叶节点后面的节点全部都是叶节点这个特点进行判断。

(i) 实现思路

1. 创建一个队列,将祖先节点作为队列中链表的第一个节点的数据域,将该节点push入队列

2. 不断pop掉队首节点,并记录队首节点的数据域

3. 判断记录的队首节点的数据域是否为NULL,如果不是NULL,就将该数据域在二叉树中对应根节点的左右两个子节点带入队列(注意:这里与层序遍历不同的是,空节点也要入列)

4. 随着队首节点的变化,将二叉树节点作为数据域不断push入队列

5. 当上述操作执行到队列为空或者遇到遇到第一个队首节点数据域为空时(二叉树的第一个叶节点),不再入列

6. 开始从队首节点的数据域进行判断,如果为NULL删除队首节点后继续判断直至队列为空,如果进行判断时出现数据域为非NULL的节点,就表示不是完全二叉树,否则就是。

(ii) 过程图示

(iii) 代码展示

//判断一颗二叉树是否为完全二叉树(二叉树的各层的节点是连续的)————用队列实现 
//二叉树的各个节点在push,pop队列时,是作为队列中链表节点的数据域!!!
bool BinaryTreeIsComplete(BTNode* root)
{
	if (root == NULL)
	{
		return false;
	}
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);//root作为队列链表的数据域进入队列

	while (!QueueEmpty(&q))//队列为空就结束
	{
		BTNode* front = QueueFront(&q);//front表示队首节点的数据域,也表示二叉树中的节点
		QueuePop(&q);
		if (front == NULL)
		{
			break;//遇到第一个数据域为NULL的节点就结束!!!
		}
		else//队首节点数据域不为空,就将其在二叉树中对应的左右节点入队列(二叉树中的NULL节点也入列)
		{
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
	}
//开始判断是否为完全二叉树————上面的while循环出来后,此时队列为空或遇到第一个数据域为NULL的节点
//如果进入下面的while循环,表示队列不为空,那么当此时队列中剩余节点的数据域都为NULL时就是完全二叉树
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);//front表示队首节点的数据域,也表示二叉树中的节点
		QueuePop(&q);
		if (front)
		{
			QueueDestroy(&q);
			return false;
		}
	}
	QueueDestroy(&q);
	return true;//表示是队列为空时离开了第二个while循环,满足后面全为NULL的条件,是完全二叉树
}

五:树、森林与二叉树的相互转换

这里是我在后续学习过程中遇到的一个问题,看到了一篇与之相关的写的很好的博客,在这里分享给各位:树、森林与二叉树的相互转换

六:相关代码的完整展示

(1) Test.c

#include "BinaryTree.h"

void Test1()
{
	BTNode* root = CreatBinaryTree();//构建一颗二叉树
	//四种遍历
	printf("先序遍历:");
	BinaryTreePreOrder(root);
	printf("\n");
	printf("中序遍历:");
	BinaryTreeInOrder(root);
	printf("\n");
	printf("后序遍历:");
	BinaryTreePostOrder(root);
	printf("\n");
	printf("层序遍历:");
	BinaryTreeLevelOrder(root);
	printf("\n");
	//计算各个类型的节点数目
	printf("BinaryTreeNodeSize:%d\n", BinaryTreeNodeSize(root));
	printf("BinaryTreeLeafSize:%d\n", BinaryTreeLeafSize(root));
	printf("BinaryTree2LevelSize:%d\n", BinaryTreeKLevelSize(root, 2));
	printf("BinaryTree3LevelSize:%d\n", BinaryTreeKLevelSize(root, 3));
	printf("BinaryTreeDepth:%d\n", BinaryTreeDepth(root));//二叉树的深度
	//在二叉树中查找某个节点
	if (BinaryTreeNodeFind(root, 'F'))
	{
		printf("有节点‘F’\n");
	}
	else
	{
		printf("无节点‘F’\n");
	}
	if (BinaryTreeNodeFind(root, 'G'))
	{
		printf("有节点‘G’\n");
	}
	else
	{
		printf("无节点‘G’\n");
	}
}

void Test2()
{
	BTNode* root = CreatBinaryTree();//构建一颗二叉树

	printf("层序遍历:");
	BinaryTreeLevelOrder(root);
	printf("\n");

	if (BinaryTreeIsComplete(root))
	{
		printf("YES\n");
	}
	else
	{
		printf("NO\n");
	}
}

int main()
{
	Test1();
	//Test2();
	return 0;
}

(2) BinaryTree.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef char BTDataType;

//定义二叉树的节点结构
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
} BTNode;

BTNode* BuyBinaryTreeNode(BTDataType x);//获取节点
BTNode* CreatBinaryTree();//构建一颗二叉树并返回其根节点

void BinaryTreePreOrder(BTNode* root);//先序遍历
void BinaryTreeInOrder(BTNode* root);//中序遍历
void BinaryTreePostOrder(BTNode* root);//后序遍历
void BinaryTreeLevelOrder(BTNode* root);//层序遍历

int BinaryTreeNodeSize(BTNode* root);//获取二叉树节点个数
int BinaryTreeLeafSize(BTNode* root);//获取二叉树叶子节点个数
int BinaryTreeKLevelSize(BTNode* root,int k);//获取二叉树第K层节点个数
int BinaryTreeDepth(BTNode* root);//求二叉树的深度
BTNode* BinaryTreeNodeFind(BTNode* root, BTDataType x);//在二叉树中查找值为x的节点

bool BinaryTreeIsComplete(BTNode* root);//判断一颗二叉树是否为完全二叉树
void BinaryTreeDestroy(BTNode* root);//销毁二叉树

(3) BinaryTree.c

#include "BinaryTree.h"
#include "Queue.h"

BTNode* BuyBinaryTreeNode(BTDataType x)//生成节点
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	node->data = x;
	node->left = node->right = NULL;
	return node;
}

//构建的第一颗二叉树,不是完全二叉树
//BTNode* CreatBinaryTree()//构建一颗二叉树,并返回根节点
//{
//	BTNode* nodeA = BuyBinaryTreeNode('A');
//	BTNode* nodeB = BuyBinaryTreeNode('B');
//	BTNode* nodeC = BuyBinaryTreeNode('C');
//	BTNode* nodeD = BuyBinaryTreeNode('D');
//	BTNode* nodeE = BuyBinaryTreeNode('E');
//	BTNode* nodeF = BuyBinaryTreeNode('F');
//
//	nodeA->left = nodeB;
//	nodeA->right = nodeC;
//	nodeB->left = nodeD;
//	nodeC->left = nodeE;
//	nodeC->right = nodeF;
//	return nodeA;
//}

//构建的第二颗二叉树,是完全二叉树
BTNode* CreatBinaryTree()//构建一颗二叉树,并返回根节点
{
	BTNode* nodeA = BuyBinaryTreeNode('A');
	BTNode* nodeB = BuyBinaryTreeNode('B');
	BTNode* nodeC = BuyBinaryTreeNode('C');
	BTNode* nodeD = BuyBinaryTreeNode('D');
	BTNode* nodeE = BuyBinaryTreeNode('E');
	BTNode* nodeF = BuyBinaryTreeNode('F');
	BTNode* nodeG = BuyBinaryTreeNode('G');
	BTNode* nodeH = BuyBinaryTreeNode('H');

	nodeA->left = nodeB;
	nodeA->right = nodeC;
	nodeB->left = nodeD;
	nodeB->right = nodeG;
	nodeC->left = nodeE;
	nodeC->right = nodeF;
	nodeD->left = nodeH;
	return nodeA;
}

void BinaryTreePreOrder(BTNode* root)//先序遍历————(根左右)
{
	if (root == NULL)//树为空或达到了最小规模子问题
	{
		return;
	}
	printf("%c ", root->data);
	BinaryTreePreOrder(root->left);
	BinaryTreePreOrder(root->right);
}

void BinaryTreeInOrder(BTNode* root)//中序遍历———— (左根右)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreeInOrder(root->left);
	printf("%c ", root->data);
	BinaryTreeInOrder(root->right);
}

void BinaryTreePostOrder(BTNode* root)//后序遍历———— (左右根)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	printf("%c ", root->data);
}

int BinaryTreeNodeSize(BTNode* root)//获取二叉树节点个数
{
	if (root == NULL)
	{
		return 0;
	}
	//总结点数 = 左子树节点数 + 右子树节点数 + 根节点
	return BinaryTreeNodeSize(root->left) + BinaryTreeNodeSize(root->right) + 1;
}

int BinaryTreeLeafSize(BTNode* root)//获取二叉树叶子节点个数
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)//左右子树都为NULL,表示为叶节点
	{
		return 1;
	}
	//二叉树叶子数=左子树叶子树+右子树叶子数
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

int BinaryTreeKLevelSize(BTNode* root, int k)//获取二叉树第K层节点个数
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)//类比与当k=1时第一层的节点个数就是1
	{
		return 1;
	}
	//第K层节点个数 = 左子树的第k-1层节点个数 + 右子树的第k-1层节点个数
	return BinaryTreeKLevelSize(root->left, k - 1)
		+ BinaryTreeKLevelSize(root->right, k - 1);
}

int BinaryTreeDepth(BTNode* root)//求二叉树的深度
{
	if (root == NULL)
	{
		return 0;
	}
	//二叉树的深度 = 左子树与右子树中较大的一个深度 + 1(根节点)
	//先记录,再直接比较得到深度,防止重复递归
	int leftdepth = BinaryTreeDepth(root->left);
	int rightdepth = BinaryTreeDepth(root->right);
	return leftdepth > rightdepth ? leftdepth + 1 : rightdepth + 1;
}

BTNode* BinaryTreeNodeFind(BTNode* root, BTDataType x)//在二叉树中查找值为x的节点
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)//对应的根节点
	{
		return root;
	}
	//在左子树中查找
	BTNode* leftnode = BinaryTreeNodeFind(root->left, x);
	if (leftnode)//查找完为空,就往右子树查找,找到了就直接返回,不要继续查找
	{
		return leftnode;
	}
	//在右子树中查找
	BTNode* rightnode = BinaryTreeNodeFind(root->right, x);
	if (rightnode)//查找完为空,就表示整棵树都没有想要查找的节点
	{
		return rightnode;
	}
	//整棵树中都没有
	return NULL;
}

//层序遍历————用队列实现 (二叉树的各个节点在push,pop队列时,是作为队列中链表节点的数据域)
void BinaryTreeLevelOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	//思路:利用队列,将二叉树的根节点先push入队列,获取并记录完后再pop出队列,
	//以记录的节点(队首节点,并且会不断变化)为根节点,再将其的左右非NULL子节点push入队列,
	//随着队首元素的不断pop,队首元素会不断发生变化,而队首元素可以以层序遍历的方式将二叉树的所有节点带入队列中,从而达到层序遍历的效果
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);//1.祖先节点入列
	while (!QueueEmpty(&q))//队列为空就结束
	{
		BTNode* front = QueueFront(&q);//front表示队首节点的数据域,也表示二叉树中的节点
		QueuePop(&q);//pop
		printf("%c ", front->data);
		//将队首元素的左右非空子节点入列
		if (front->left)
		{
			QueuePush(&q, front->left);
		}
		if (front->right)
		{
			QueuePush(&q, front->right);
		}
	}
	QueueDestroy(&q);
}

//判断一颗二叉树是否为完全二叉树(二叉树的各层的子节点是连续的)————用队列实现 
//二叉树的各个节点在push,pop队列时,是作为队列中链表节点的数据域!!!
bool BinaryTreeIsComplete(BTNode* root)
{
	if (root == NULL)
	{
		return false;
	}
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);//root作为队列链表的数据域进入队列

	while (!QueueEmpty(&q))//队列为空就结束
	{
		BTNode* front = QueueFront(&q);//front表示队首节点的数据域,也表示二叉树中的节点
		QueuePop(&q);
		if (front == NULL)
		{
			break;//遇到第一个数据域为NULL的节点就结束!!!
		}
		else//队首节点数据域不为空,就将其在二叉树中对应的左右节点入队列(二叉树中的NULL节点也入列)
		{
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
	}
	//开始判断是否为完全二叉树————上面的while循环出来后,此时队列为空或遇到第一个数据域为NULL的节点
	//如果进入下面的while循环,表示队列不为空,那么当此时队列中剩余节点的数据域都为NULL时就是完全二叉树
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);//front表示队首节点的数据域,也表示二叉树中的节点
		QueuePop(&q);
		if (front)
		{
			QueueDestroy(&q);
			return false;
		}
	}
	QueueDestroy(&q);
	return true;//表示是队列为空时离开了第二个while循环,满足后面全为NULL的条件,是完全二叉树
}

//销毁二叉树————类似与后序遍历的思想,将节点从下向上释放
void BinaryTreeDestroy(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreeDestroy(root->left);
	BinaryTreeDestroy(root->right);
	free(root);
}

(4) Queue.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

//这里队列内的元素类型需要改成 struct BinaryTreeNode*,
//要用到前置声明,声明类型(struct BinaryTreeNode)
struct BinaryTreeNode;//前置声明要用到类型全名
typedef struct BinaryTreeNode* QDataType;

//用单链表实现队列(定义队列中的链表结构)
typedef struct QueueListNode
{
	struct QueueListNode* next;//指针域
	QDataType data;//数据域
} QLNode;

//为了更好地实现单链表的头删(出队列)与尾插(入队列),在队列的链表结构中在定义一个头节点与尾节点,可以表示队列的结构
typedef struct Queue
{
	QLNode* head;
	QLNode* tail;
} Queue;

//队列相关接口函数的定义
void QueueInit(Queue* pq);//初始化队列
void QueuePush(Queue* pq, QDataType x);//入队列
void QueuePop(Queue* pq);//出队列
bool QueueEmpty(Queue* pq);//判断队列是否为空
QDataType QueueFront(Queue* pq);//获取队头数据
void QueueDestroy(Queue* pq);//销毁队列中动态开辟节点的链表

(5) Queue.c

#define _CRT_SECURE_NO_WARNINGS
#include "Queue.h"

void QueueInit(Queue* pq)//队列初始化
{
	assert(pq);
	pq->head = NULL;
	pq->tail = NULL;
}

void QueuePush(Queue* pq, QDataType x)//入队列(单链表的尾插)
{
	QLNode* newnode = (QLNode*)malloc(sizeof(QLNode));//开辟新节点
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->next = NULL;
	newnode->data = x;          //此时,二叉树的节点是作为队列中链表节点的数据域!!!
	//节点入列的两种情况
	if (pq->head == NULL)//1.原队列为空
	{
		pq->head = pq->tail = newnode;
		newnode->next = NULL;
	}
	else//原队列不为空
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
		pq->tail->next = NULL;
	}
}

void QueuePop(Queue* pq)//出队列,单链表的头删
{
	assert(pq);
	assert(!QueueEmpty(pq));//删除时队列不能为空
	QLNode* next = pq->head->next;
	free(pq->head);                   //free掉链表节点,对应节点的数据域不会被销毁
	pq->head = next;
}

bool QueueEmpty(Queue* pq)//判断队列是否为空
{
	assert(pq);
	return pq->head == NULL;
}

QDataType QueueFront(Queue* pq)//获取队头数据
{
	assert(pq);
	assert(!QueueEmpty(pq));//队列不能为空
	return pq->head->data;
}

void QueueDestroy(Queue* pq)//销毁队列中动态开辟节点的链表
{
	assert(pq);
	while (pq->head)
	{
		QLNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
}

总结:

我这次的分享就到这里结束,由这次的内容更可以看出,数据结构的学习过程中画图是十分重要的,也许耗时,但一定有很大的帮助。希望我的分享可以帮助到各位,再见。


  • 7
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值