二叉树--C语言实现数据结构

在这里插入图片描述

本期带大家一起用C语言实现二叉树🌈🌈🌈

1、二叉树的定义

二叉树是一种特殊的树状数据结构,它由节点组成,每个节点最多有两个子节点,分别称为左子节点和右子节点

二叉树的链式存储结构是指用 链表 来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三

个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址

2、二叉树 的结构

在这里插入图片描述

3、二叉树的实现

3.1 结构设计

typedef int BTreeDataType;

typedef struct BTreeNode
{
	BTreeDataType val;

	struct BTreeNode* right;
	struct BTreeNode* left;
}BTNode;

3.2 手动构建二叉树

在这里插入图片描述

BTNode* CreatNode(BTreeDataType val)
{
	BTNode* root = (BTNode*)malloc(sizeof(BTNode));
	if (root == NULL)
	{
		perror("root malloc fail");
		return;
	}

	root->val = val;
	root->left = NULL;
	root->right = NULL;

	return root;

}
BTNode* CreatBinaryTree()
{
	
	BTNode* root1 = CreatNode(1);
	BTNode* root2 = CreatNode(2);
	BTNode* root3 = CreatNode(3);
	BTNode* root4 = CreatNode(4);
	BTNode* root5 = CreatNode(5);
	BTNode* root6 = CreatNode(6);
	BTNode* root7 = CreatNode(7);

	root1->left = root2;
	root1->right = root3;
	root2->left = root4;
	root2->right = root5;
	root3->left = root6;
	root3->right = root7;
	return root1;

}

3.3 前序遍历

  1. 首先进行条件判断,如果当前节点root为空,即遍历到了空节点,输出"N "(表示Null)之后返回。这是因为在先序遍历中,空节点也需要被遍历到。

  2. 若当前节点root不为空,则输出当前节点的值root->val

  3. 继续递归遍历当前节点的左子树,即调用PrevOrder(root->left)

  4. 最后,递归遍历当前节点的右子树,即调用PrevOrder(root->right)

通过递归的方式,先序遍历会按照先根节点、左子树、右子树的顺序遍历整个二叉树。在代码中,先打印当前节点的值,然后分别遍历左子树和右子树。

void PrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	else
		printf("%d ", root->val);
	PrevOrder(root->left);
	PrevOrder(root->right);

}

在这里插入图片描述

3.4 中序遍历

  1. 首先进行条件判断,如果当前节点root为空,即遍历到了空节点,输出"N "(表示Null)之后返回。这是因为在中序遍历中,空节点也需要被遍历到。

  2. 若当前节点root不为空,则递归遍历当前节点的左子树,即调用InOrder(root->left)

  3. 输出当前节点的值root->val

  4. 最后,递归遍历当前节点的右子树,即调用InOrder(root->right)

通过递归的方式,中序遍历会按照左子树、根节点、右子树的顺序遍历整个二叉树。在代码中,先遍历左子树,然后打印当前节点的值,最后遍历右子树。这样可以保证中序遍历的顺序性。

void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	InOrder(root->left);
	printf("%d ", root->val);
	InOrder(root->right);

}

3.5 后序遍历

后序遍历的访问次序是 先访问左子树,再访问右子树,再访问根节点

逻辑同前序遍历和中序遍历差不多

void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->val);

}

3.4 层序遍历

层序遍历就是从第一层开始从左向右逐个遍历

那么当前结果就是1 2 3 4 5 6 7

层序遍历的话,需要一个队列来进行辅助

首先我们将root根节点放进去,然后判断队列当中是否为空,为空就跳出循环

不为空的话就将节点不为NULL的放进去,直到队列为空

void LevalOrder(BTNode* root)
{
	if (root == NULL)
		return;
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		printf("%d ", front->val);
		if(front->left)
			QueuePush(&q, front->left);
		if(front->right)
			QueuePush(&q, front->right);

	}

	QueueDestroy(&q);

}

核心思想就是出上一层带下一层

3.5 计算二叉树的节点数

这里我们可以有两种思路:
1、给定一个 size ,遍历二叉树,分别遍历左右子树,遍历过程中遍历到非空节点 size++,遍历到空,则返回 0。
2、二叉树的大小 = 根节点 + 左子树节点 + 右子树节点,将其分成多个子问题,递归求解。

思路1 size计数

使用局部变量size来记录节点数量是不可行的。因为在递归调用的过程中,每一次递归都会创建一个新的函数栈帧,这意味着每个递归调用都有自己独立的size变量,并且初始值都是0,无法准确计算出二叉树的大小。

为了解决这个问题,可以使用全局变量size来记录节点数量。全局变量位于全局数据区,在整个工程内都有效。但需要注意的是,由于全局变量不会被销毁,如果多次调用计算节点数量的函数,size会累加,可能导致错误的结果。因此,在每次调用之前,需要将size重置为0,以确保准确计算二叉树的大小。

总结来说,局部变量不能满足统计二叉树节点数量的在这里插入代码片需求,需要使用全局变量,并在每次调用之前将其重置为0。

int size = 0;

int BTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	size++;
	BTreeSize(root->left);
	BTreeSize(root->right);
	return size;
}

思路2 递归实现
计算二叉树的节点数

如果root==NULL的话,那我们就返回0

不然的话我们就返回1+左子树的节点树+右子树的节点数

int BTreeSize(BTNode* root)
{

	//return root == NULL ? 0 : 1 + BTreeSize(root->left) + BTreeSize(root->right);

	if (root == NULL)
		return 0;

	return 1 + BTreeSize(root->left) + BTreeSize(root->right);
}

3.6 计算二叉树的高度

计算二叉树的高度

如果我们的root==NULL的话,那我们返回0

不然的话计算左子树的高度和右子树的高度

让最高的子树高度+1就是我们的二叉树高度

不过需要注意的是需要拿left_height和right_height来计数,防止重复递归调用

不然到时候还得重复计算好多次,尤其是会重复计算下面的高度

可不是简单的二倍关系

nt BTreeHeight(BTNode* root)
{
	if (root == NULL)

		return 0;
	int left_hight = BTreeHeight(root->left);
	int right_hight = BTreeHeight(root->right);

	return left_hight > right_hight ?  1 + left_hight : 1 + right_hight;
}

3.7 计算叶子节点的个数

计算叶子节点的个数

如果root==NULL,返回0

如果root->left == NULL && root->right == NULL 那就返回1

如果还不满足以上的条件话,那就返回左子树的叶子节点个数+右子树叶子节点个数

int BTreeLeafSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	if (root->left == NULL && root->right == NULL)
		return 1;
	return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);


}

3.8 计算第K层节点数

计算第K层节点数

对于第一层,需要计算的是 第 k 层的节点个数;
对于第二层,需要计算的是 第 k - 1 层的节点个数;
对于第 k 层,计算的就是 第 1 层( 当前层数 ) 的节点个数。

如果我的root==NULL的话,那就返回0

如果我的root!=NULL而且k==1的话那就返回1

不然的话就返回左子树第k-1层节点的数目+右子树第k-1层节点的数目

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

}

3.9 查找某个值对应的节点

查找某个值对应的节点

如果root==NULL,返回NULL

如果root->val==val,返回root

既然root->val!=val,那么就去左右子树分别查找

如果在左子树查走到了,那么就返回左子树当中找到的那个节点

如果在右子树查走到了,那么就返回右子树当中找到的那个节点

至于为什么要判断左右子树查找到结果不为NULL呢

因为左子树如果结果是NULL,不然直接返回NULL

还有右子树没有查找

右子树的结果的话可以不用判NULL,因为这个时候根节点和左子树当中都没有找到val,那么就剩下右子树了

如果右子树还没有的话,那就没有,返回NULL,有的话就返回节点

BTNode* BTreeFind(BTNode* root, BTreeDataType val)
{
	if (root == NULL)
		return NULL;
	if (root->val == val)
		return root;
	BTNode* leftNode = BTreeFind(root->left,val);
	if (leftNode != NULL)
		return leftNode;
	BTNode* rightNode = BTreeFind(root->right, val);
	if (rightNode != NULL)
		return rightNode;
	return NULL;

}

3.10 判断是否为满二叉树

和层序遍历的思想一样,需要一个队列来进行辅助

首先将根节点root放到队列当中
如果在中途遇到了NULL的话,那就跳出循环
如果没遇到NULL的话,那就带下一层子树进来

遇到NULL结束入队列,并且检查队列当中是否还有有效元素(NULL除外)

如果全是NULL,那就代表这个二叉树完全二叉树

不然的话就不是

bool IsComplete(BTNode* root)
{
	Queue q;

	QueueInit(&q);
	
	if(root)
	QueuePush(&q, root);
	
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front == NULL)
			break;
		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
	}
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front != NULL)
		{
			QueueDestroy(&q);
			return false;
		}

	}
	QueueDestroy(&q);
	return true;
}

3.11 二叉树的销毁

二叉树的销毁逻辑是和后序遍历一样的

void BTreeDestroy(BTNode* root)
{

	if (root == NULL)
		return;
	BTreeDestroy(root->left);
	
	BTreeDestroy(root->right);

	free(root);

}

4、感谢与交流✅

🌹🌹🌹如果大家通过本篇博客收获了,对二叉树有了新的了解的话
那么希望支持一下哦如果还有不明白的,疑惑的话,或者什么比较好的建议的话,可以发到评论区,
我们一起解决,共同进步 ❗️❗️❗️
最后谢谢大家❗️❗️❗️💯💯💯

在这里插入图片描述

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
平衡二叉树是一种特殊的二叉树,它的左右子树的高度差不超过1。AVL树是一种自平衡的二叉搜索树,它的高度始终保持在O(log n)。 下面是C语言实现平衡二叉树(AVL树)的代码: ``` #include <stdio.h> #include <stdlib.h> /* 定义平衡二叉树节点结构体 */ struct AVLNode { int data; // 存储的数据 int height; // 节点高度 struct AVLNode *leftChild; // 左子树 struct AVLNode *rightChild; // 右子树 }; /* 获取节点高度 */ int getHeight(struct AVLNode *node) { if (node == NULL) { return -1; } else { return node->height; } } /* 获取节点平衡因子 */ int getBalanceFactor(struct AVLNode *node) { if (node == NULL) { return 0; } else { return getHeight(node->leftChild) - getHeight(node->rightChild); } } /* 更新节点高度 */ void updateHeight(struct AVLNode *node) { node->height = 1 + (getHeight(node->leftChild) > getHeight(node->rightChild) ? getHeight(node->leftChild) : getHeight(node->rightChild)); } /* 右旋操作 */ struct AVLNode *rotateRight(struct AVLNode *node) { struct AVLNode *newRoot = node->leftChild; node->leftChild = newRoot->rightChild; newRoot->rightChild = node; updateHeight(node); updateHeight(newRoot); return newRoot; } /* 左旋操作 */ struct AVLNode *rotateLeft(struct AVLNode *node) { struct AVLNode *newRoot = node->rightChild; node->rightChild = newRoot->leftChild; newRoot->leftChild = node; updateHeight(node); updateHeight(newRoot); return newRoot; } /* 插入操作 */ struct AVLNode *insert(struct AVLNode *root, int data) { if (root == NULL) { root = (struct AVLNode *) malloc(sizeof(struct AVLNode)); root->data = data; root->height = 0; root->leftChild = NULL; root->rightChild = NULL; } else if (data < root->data) { root->leftChild = insert(root->leftChild, data); if (getHeight(root->leftChild) - getHeight(root->rightChild) == 2) { if (data < root->leftChild->data) { root = rotateRight(root); } else { root->leftChild = rotateLeft(root->leftChild); root = rotateRight(root); } } } else if (data > root->data) { root->rightChild = insert(root->rightChild, data); if (getHeight(root->rightChild) - getHeight(root->leftChild) == 2) { if (data > root->rightChild->data) { root = rotateLeft(root); } else { root->rightChild = rotateRight(root->rightChild); root = rotateLeft(root); } } } updateHeight(root); return root; } /* 中序遍历 */ void inOrderTraversal(struct AVLNode *root) { if (root != NULL) { inOrderTraversal(root->leftChild); printf("%d ", root->data); inOrderTraversal(root->rightChild); } } int main() { struct AVLNode *root = NULL; int data[] = {5, 2, 8, 1, 3, 6, 9}; int len = sizeof(data) / sizeof(data[0]); int i; for (i = 0; i < len; i++) { root = insert(root, data[i]); } inOrderTraversal(root); return 0; } ``` 以上代码实现了平衡二叉树的插入和中序遍历操作。在插入操作中,根据插入节点的值和当前节点的值的大小关系,不断递归向左或向右子树进行插入操作,并在递归返回时更新节点高度和进行平衡操作。在平衡操作中,根据节点的平衡因子进行旋转操作,使树重新平衡。在中序遍历操作中,按照左子树、根节点、右子树的顺序遍历树中的节点,输出节点的值。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值