二叉树(Binary_Tree)(C语言)

目录

一、二叉树基本概念

1.二叉树概念

2.二叉树的遍历

(1)递归的重要性

(2)前、中、后序遍历

(3)层序遍历

二、代码实现

1.Binary_Tree.h

2.Binary_Tree.c

1.树的建立(TreeCreate)

(1)手搓轮子

(2)造车

2.树的销毁(TreeDestroy)

3.计算节点个数(TreeSize)

4,计算叶节点个数(TreeLeafSize)

5.计算深度为k的节点数(TreeLevelKSize)

6.查找节点(TreeFind)

7.前序遍历打印树元素(PrevOrder)

8.中序遍历打印树元素(InOrder)

9.后序遍历打印树元素(PostOrder)

10.层序遍历打印二叉树(TreeLevelOrder)

11.判断树是否为完全二叉树(TreeComplete)

12.计算树的高度

三、总结


一、二叉树基本概念

1.二叉树概念

二叉树是典型的属性存储结构,和堆的结构相同,堆是一个完全二叉树

下图就是一个典型的二叉树。

2.二叉树的遍历

(1)递归的重要性

二叉树的学习,最重要的就是二叉树的遍历,由于二叉树的根节点含有左右节点,每个节点单部分的结构完全相同,可以运用经典的分治思想,将左右节点分别视为新的根节点,进行递归操作,二叉树的许多函数实现都运用了递归,二叉树的遍历也不例外

(2)前、中、后序遍历

对于上图展示的二叉树结构,我们分别使用前、中、后序遍历。

前序遍历:根、左、右。先走根节点,然后是左节点、最后右节点

也被称为深度优先遍历

中序遍历:左、根、右。

后序遍历:左、右、根。

上图的二叉树的三种遍历方式的示意图如下,红色方框括起来的可以互相看作彼此的左、右、根节点。

(3)层序遍历

除了上述三种递归实现的遍历之外,还有层序遍历,其方法就是一层一层向下遍历二叉树,可以用来判断二叉树是否为完全二叉树,其实现我们会在后面给出。

层序遍历也被称为广度优先遍历

二、代码实现

1.Binary_Tree.h

我们使用链式存储来实现二叉树,当然后面讲到广度优先遍历的时候我们会使用顺序存储结构。

这里的TreeNode包含了节点数据以及左右孩子,这也是我们说的孩子表示法。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <math.h>
typedef int BTDataType;
typedef struct BinaryTreeNode {
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BianryTreeNode* right;
}TreeNode;
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
TreeNode* TreeCreate(BTDataType* a, int n, int* pi);
// 二叉树销毁
void TreeDestory(TreeNode* root);
// 二叉树节点个数
int TreeSize(TreeNode* root);
// 二叉树叶子节点个数
int TreeLeafSize(TreeNode* root);
// 二叉树第k层节点个数
int TreeLevelKSize(TreeNode* root, int k);
// 二叉树查找值为x的节点
TreeNode* TreeFind(TreeNode* root, BTDataType x);
// 二叉树前序遍历 
void PrevOrder(TreeNode* root);
// 二叉树中序遍历
void InOrder(TreeNode* root);
// 二叉树后序遍历
void PostOrder(TreeNode* root);
// 层序遍历
void TreeLevelOrder(TreeNode* root);
// 判断二叉树是否是完全二叉树
bool TreeComplete(TreeNode* root);

2.Binary_Tree.c

1.树的建立(TreeCreate)

(1)手搓轮子

在我们还没有对递归有足够理解,手法尚未娴熟的时候,我们只能用最笨的方法手搓轮子,一个节点一个节点的创建。

TreeNode* BuyTreeNode(int x)
{
	TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
	assert(node);

	node->data = x;
	node->left = NULL;
	node->right = NULL;
	return node;
}
(2)造车

造车部分大家可以先参考后面的前序遍历,我们使用前序遍历,使用一个数组的数据创建一个指定形状的二叉树,你只需在数组中写入空数据,创建的时候便会创建空节点。

'#'表示数组元素为空。

//通过前序遍历创建树
TreeNode* TreeCreate(char*a,int *pi) {
	if (a[*pi] == '#') {        //给一个数组进行二叉树插入
		(*pi)++;
		return NULL;
	}
	TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
	if (root == NULL) {
		perror("malloc fail");
		exit(-1);
	}
	root->data = a[*(pi)++];
	root->left = TreeCreate(a, pi);
	root->right = TreeCreate(a, pi);
	return root;
}

2.树的销毁(TreeDestroy)

直接前序遍历销毁即可

void TreeDestory(TreeNode* root) {
	if (root == NULL) {
		return;
	}
	TreeDestory(root->left);
	TreeDestory(root->right);
	free(root);
}

3.计算节点个数(TreeSize)

在我们面临递归解题的时候,需要设置好递归结束条件,可以借助递归展开图来帮助我们理清递归思路

int TreeSize(TreeNode* root)
{
	return root == NULL ? 0 :
		TreeSize(root->left) +
		TreeSize(root->right) + 1;
}

4,计算叶节点个数(TreeLeafSize)

这个问题最重要的就是如何判断为叶节点,即是root!=NULL,但是left、right==NULL。

// 叶子节点的个数
int TreeLeafSize(TreeNode* root)
{
	// 空 返回0
	if (root == NULL)
		return 0;
	// 不是空,是叶子 返回1
	if (root->left == NULL
		&& root->right == NULL)
		return 1;
	// 不是空 也不是叶子  分治=左右子树叶子之和
	return TreeLeafSize(root->left) +
		TreeLeafSize(root->right);
}

5.计算深度为k的节点数(TreeLevelKSize)

计数,每次向下走一层k-1,最后当k=1的时候,判断为到达k层。

int TreeLevelKSize(TreeNode* root, int k)
{
	assert(k > 0);
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;
	return TreeLevelKSize(root->left, k - 1)
		+ TreeLevelKSize(root->right, k - 1);
}

6.查找节点(TreeFind)

找到了节点大小为k的节点,就返回节点指针。为空返回NULL,否则继续向下遍历

TreeNode* TreeFind(TreeNode* root, BTDataType x) {
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;
	TreeNode* ret1 = TreeFind(root->left, x);
	if (ret1)
		return ret1;
	TreeNode* ret2 = TreeFind(root->right, x);
	if (ret2)
		return ret2;
}

7.前序遍历打印树元素(PrevOrder)

void PrevOrder(TreeNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

	printf("%d ", root->data);
	PrevOrder(root->left);
	PrevOrder(root->right);
}

8.中序遍历打印树元素(InOrder)

void InOrder(TreeNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}

9.后序遍历打印树元素(PostOrder)

void PostOrder(TreeNode* root) {
	if (root == NULL)
		return;
	post_order(root->left);
	post_order(root->right);
	printf("%d ", root->data);
}

10.层序遍历打印二叉树(TreeLevelOrder)

根据层序遍历的原理,从前面的节点一层层往下,这个过程可以看作队列(Queue)的尾进头出的过程,于是我们可以再包含一个Queue.h的头文件。

首先将二叉树顶的节点插入到初始化的队列中,每一节点遍历的时候就将该节点的左右节点(如果非空)插入到队列中,然后同时打印出该节点(root)

手机用一个LevelSize记录该层节点数,方便确认该层遍历的终点。

特别注意的是,这个队列的QueueDataType需要改成TreeNode*,用来在队列中存储二叉树节点,在提取队头节点的时候,QueuePop不会影响数据存储,我们提前用front来记录二叉树节点。

在单层循环结束后printf("\n");可以打印出树的结构。

//层序遍历打印二叉树  可以用来判断完全二叉树
void TreeLevelOrder(TreeNode* root) {
	Queue q;
	QueueInit(&q);
	if (root) {
		QueuePush(&q, root);
	}
	int levelSize = 1;
	while (!QueueEmpty(&q)){
		while(levelSize--){
			TreeNode* front = QueueFront(&q);
			QueuePop(&q);//这里删去的是QueueNode*类型指针,同时将其free掉,但是我们的front已经记录了TreeNode类型指针,不受影响
			printf("%d", front->data);
			if (front->left) {
				QueuePush(&q, front->left);
			}
			if (front->right) {
				QueuePush(&q, front->right);
			}
		}
		printf("\n");
		levelSize = QueueSize(&q);
	}
	printf("\n");
	QueueDestroy(&q);
}

11.判断树是否为完全二叉树(TreeComplete)

我们先走一遍层序遍历,根据层序遍历的原理将二叉树中的节点删去,再将root节点的左右儿子依次插入,循环的结束条件就是遇到空节点。

这样走完一趟循环,假如二叉树是完全二叉树,那么队列里面一定是空的,所以我们只需再往后遍历一遍队列,假如说碰见了非空节点,则返回false,全为空则返回true。

//判断是否是完全二叉树
bool TreeComplete(TreeNode* root) {
	Queue q;
	QueueInit(&q);
	if (root) {
		QueuePush(&q, root);
	}
	int levelSize = 1;
	while (!QueueEmpty(&q)) {
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front == NULL) {
			break;
		}
		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
	}
	// 前面遇到空以后,后面还有非空就不是完全二叉树
	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front)
		{
			QueueDestroy(&q);
			return false;
		}
	}

	QueueDestroy(&q);
	return true;
}

12.计算树的高度

在这个函数中,同样使用递归函数,比较左右高度,取最高的+1就是树的高度

我们可以使用数学函数fmax,包含<math.h>,可以比较两个数中较大的那个。

int TreeHeight(TreeNode* root) {
	if (root == NULL) {
		return 0;
	}
	//int leftHeight = TreeHeight(root->left);
	//int rightHeight = TreeHeight(root->right);
	//return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	return fmax(TreeHeight(root->left), TreeHeight(root->right)) + 1;
}

三、总结

二叉树可谓是人类智慧的结晶,也是我们系统接触递归的第一课,随着学习的深入,我们对递归的了解会越来越清晰,往后我们也会用C++非递归遍历二叉树。大家加油。

  • 42
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是二叉树的创建代码: ``` #include <stdio.h> #include <stdlib.h> struct Node { int data; struct Node *left; struct Node *right; }; struct Node *create_node(int data) { struct Node *new_node = (struct Node*) malloc(sizeof(struct Node)); new_node->data = data; new_node->left = NULL; new_node->right = NULL; return new_node; } struct Node *create_tree() { int data; printf("Enter data, -1 for no node: "); scanf("%d", &data); if (data == -1) { return NULL; } struct Node *root = create_node(data); printf("Enter left child of %d\n", data); root->left = create_tree(); printf("Enter right child of %d\n", data); root->right = create_tree(); return root; } void inorder(struct Node *root) { if (root == NULL) { return; } inorder(root->left); printf("%d ", root->data); inorder(root->right); } int main() { struct Node *root = create_tree(); printf("Inorder traversal of binary tree: "); inorder(root); printf("\n"); return 0; } ``` 该代码中,我们首先定义了一个 `Node` 结构体,它包含三个成员,分别是节点的值 `data`,左子树指针 `left` 和右子树指针 `right`。 接着,我们定义了一个 `create_node` 函数,用于创建一个新的节点,并返回指向该节点的指针。 然后,我们定义了一个 `create_tree` 函数,用于递归创建二叉树。该函数首先提示用户输入节点的值,如果输入的值为 -1,则表示该节点不存在,返回 NULL。否则,就创建一个新的节点,并递归创建该节点的左子树和右子树。 最后,我们定义了一个 `inorder` 函数,用于中序遍历二叉树,并打印每个节点的值。 在 `main` 函数中,我们首先调用 `create_tree` 函数创建二叉树,然后调用 `inorder` 函数中序遍历并打印该二叉树的节点值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值