C语言--二叉树

目录

一、树的基本介绍

二、二叉树的定义

三、二叉树的实现

(一)二叉树的结构

(二)二叉树的创建

(三)二叉树的遍历

1.前序遍历(PreOrder)

2.中序遍历(InOrder)

3.后序遍历(PostOrder)

(四)二叉树的节点个数

(五)二叉树叶子节点的个数

(六)返回二叉树高度(深度)

(七)二叉树第k层节点个数

(八)查找值为x的节点

(九)层序遍历

(十)判断二叉树是否为完全二叉树

四、相关题目

(一)单值二叉树

(二)二叉树深度

(三)二叉树前序遍历

(四)判断是否为相同的树

(五)对称二叉树

(六)另一个树子树

(七)二叉树遍历

(八)翻转二叉树

(九)平衡二叉树

五、题目代码

(一)单值二叉树

(二)二叉树深度

(三)二叉树前序遍历

(四)判断是否为相同的树

(五)对称二叉树

(六)另一棵树子树

(七)二叉树遍历

(八)翻转二叉树

(九)平衡二叉树


一、树的基本介绍

树由节点和边组成,其中节点表示数据元素,边表示节点之间的关系。

二、二叉树的定义

二叉树(Binary tree)是n个有限元素的集合,该集合或者为空、或者由一个称为根(root)的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成,是有序树。

三、二叉树的实现

(一)二叉树的结构

包含一个值,两个二叉树节点指针分别指向左右节点。

typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

(二)二叉树的创建

二叉树的前序遍历是指先访问根节点,然后访问左子树,最后访问右子树。顺序是:根 -> 左 -> 右。

而创建一个二叉树使用了递归调用,值不为NULL(#)时,使用malloc函数开辟一块空间创建一个节点,然后该节点再次调用BinaryTreeCreate函数创建其左节点和右节点。

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
	if (*pi >= n || a[*pi] == '#')
	{
		(*pi)++;
		return NULL;//不是return
	}
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	newnode->_data = a[(*pi)++];
	newnode->_left = BinaryTreeCreate(a, n, pi);
	newnode->_right = BinaryTreeCreate(a, n, pi);
	return newnode;
}

(三)二叉树的遍历

二叉树遍历的代码恨简单,当root==NULL时,返回,注意这里的return是返回至上一层。

还有就是是打印以及调用,将打印根节点这一行代码放在不同位置就可以实现前序,中序以及后序。

1.前序遍历(PreOrder)

先访问根节点,然后访问左子树,最后访问右子树。顺序是:根 -> 左 -> 右。

// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	printf("%c", root->_data);
	BinaryTreePrevOrder(root->_left);
	BinaryTreePrevOrder(root->_right);
}

2.中序遍历(InOrder)

先访问左子树,然后访问根节点,最后访问右子树。顺序是:左 -> 根 -> 右。

// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreeInOrder(root->_left);
	printf("%c", root->_data);
	BinaryTreeInOrder(root->_right);
}

3.后序遍历(PostOrder)

先访问左子树,然后访问右子树,最后访问根节点。顺序是:左 -> 右 -> 根。

// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreePostOrder(root->_left);
	BinaryTreePostOrder(root->_right);
	printf("%c", root->_data);
}

(四)二叉树的节点个数

也是通过递归调用,返回左节点个数+右节点个数,每次递归回来就相当于是访问了对应的根节点

int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	
	return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;//+1是代表根节点
}

也可以简化为下面的写法

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

(五)二叉树叶子节点的个数

叶子节点的左右节点都为NULL,当遇到叶子节点时返回1,其余返回0

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

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

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

(六)返回二叉树高度(深度)

其实就是递归调用,返回左右更高的那个,以下代码也可以像二叉树节点个数代码一样进行简化

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

	int leftHeight = TreeHeight(root->left);
	int rightHeight = TreeHeight(root->right);

	return leftHeight > rightHeight ?
		leftHeight + 1 : rightHeight + 1;
}

(七)二叉树第k层节点个数

第一层为k,第二层k-1,直到第k层为1,那就当k==1的时候,这一层的节点个数

int TreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
		return 0;

	if (k == 1)
		return 1;

	// 子问题
	return TreeLevelKSize(root->left, k - 1)
		+ TreeLevelKSize(root->right, k - 1);
}

(八)查找值为x的节点

其实就是遍历每一个节点

BTNode* TreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;

	if (root->data == x)
		return root;

	BTNode* ret1 = TreeFind(root->left, x);
	if (ret1)
		return ret1;

	BTNode* ret2 = TreeFind(root->right, x);
	if (ret2)
		return ret2;

	return NULL;
}

(九)层序遍历

层序遍历要求输出二叉树,一层一层从左到右

我们选择使用队列(先进先出)

当队列为空时,进入左右节点

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

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);

		printf("%c ", front->_data);
		
		//左不为空,把左进去,右不为空把右进去
		if (front->_left)
			QueuePush(&q, front->_left);

		if (front->_right)
			QueuePush(&q, front->_right);
	}

	QueueDestroy(&q);
}

(十)判断二叉树是否为完全二叉树

一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同。

除了最后一层外,其他层是满二叉树,最后一层的节点要求从左到右连续。

解决办法很简单,利用层序遍历,但是当遇到第一个NULL的时候,如果之后全为空则是完全二叉树,否则不是。

bool TreeComplete(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)
		{
			QueueDestroy(&q);
			return false;
		}
	}

	QueueDestroy(&q);
	return true;
}

四、相关题目

(一)单值二叉树

判断是否存在,在判断根的左节点是否存在,以及值是否相等,接着判断右节点是否存在和值是否相等,返回递归将左右节点分别作为根节点。

965. 单值二叉树 - 力扣(LeetCode)

(二)二叉树深度

返回二叉树高度

104. 二叉树的最大深度 - 力扣(LeetCode)

(三)二叉树前序遍历

这个题需要注意的是

使用数组存放,但是数组下标不能使用局部变量,也就是要求传地址

144. 二叉树的前序遍历 - 力扣(LeetCode)

(四)判断是否为相同的树

100. 相同的树 - 力扣(LeetCode)

(五)对称二叉树

101. 对称二叉树 - 力扣(LeetCode)

(六)另一个树子树

572. 另一棵树的子树 - 力扣(LeetCode)

我是构造一个判断相同的函数,然后遍历每个节点

注意在对节点判断时,先看是否存在,不然会报错

(七)二叉树遍历

跟前序遍历那个题类似

二叉树遍历_牛客题霸_牛客网 (nowcoder.com)

(八)翻转二叉树

226. 翻转二叉树 - 力扣(LeetCode)

(九)平衡二叉树

110. 平衡二叉树 - 力扣(LeetCode)

五、题目代码

(一)单值二叉树

bool isUnivalTree(struct TreeNode* root) {
    if (root == NULL)
    {
        return true;
    }
    if((root->left && root->left->val != root->val) || (root->right && root->right->val != root->val))
        return false;

    return isUnivalTree(root->right)&&isUnivalTree(root->left);;
}

(二)二叉树深度

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

(三)二叉树前序遍历

 //要求数组需要malloc,假定使用的人会free
 //1.扩容的方式  2.先遍历一遍
 int TreeSize(struct TreeNode* root)
 {
    return root == NULL? 0 : TreeSize(root->left)+TreeSize(root->right)+1;
 }
void prevOrder(struct TreeNode* root, int* a, int* pi)//int i 是局部变量,每个递归调用中不同了!!!
{
    if(root == NULL)
    {
        return;
    }

    a[(*pi)++] = root->val;
    prevOrder(root->left, a, pi);
    prevOrder(root->right, a, pi);
}
 //输出型参数 -- returnSize
int* preorderTraversal(struct TreeNode* root, int* returnSize) {
    //returnSize 是leetcode的规范,如果返回数组,需要返回数组大小
    *returnSize = TreeSize(root);
    int* a = (int*)malloc(sizeof(int)*(*returnSize));
    int *i = 0;
    prevOrder(root, a, &i);
    return a;
}

(四)判断是否为相同的树

bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
        if(p == NULL && q == NULL)
        return true;
    
    //其中一个不为空
    if(p == NULL || q == NULL)
        return false;

    if(p->val != q->val)
        return false;

    return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

(五)对称二叉树

bool _isSymmetric(struct TreeNode* p, struct TreeNode* q) {
    if(!p && !q)//pq都为空
    {
        return true;
    }
    if((p && q) && (p->val == q->val))//pq都不为空
        return _isSymmetric(p->right, q->left)&&_isSymmetric(p->left, q->right);
    else
    {
        return false;
    }
    return true;
}
bool isSymmetric(struct TreeNode* root) {
    if(_isSymmetric(root->right, root->left))
        return true;
    else
        return false;
}

(六)另一棵树子树

 //遍历所有子树,看是否相同(isSame 递归返回左跟左,右跟右)
 //在isSun中返回左与subRoot,右与subRoot
bool isSameTree(struct TreeNode* p, struct TreeNode* q)
{
    if(p == NULL && q == NULL)
        return true;
    
    //其中一个不为空
    if(p == NULL || q == NULL)
        return false;

    if(p->val != q->val)
        return false;

    return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
    if(root == NULL)
    {
        return false;
    }
    if(root->val == subRoot->val && isSameTree(root, subRoot))
        return true;

    return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);

}

(七)二叉树遍历

#include <stdio.h>
typedef struct TreeNode
{
    char val;
    struct TreeNode* left;
    struct TreeNode* right;
}TreeNode;
TreeNode* MakeTree(char* a, int* pi)
{
    if(a[*pi] == '#' || a[*pi] == '\0')//不能在这(*pi)++,放这==,如果不是#也会++
    {
        (*pi)++;
        return NULL;
    }
    TreeNode* newnode = (TreeNode*)malloc(sizeof(TreeNode));
    newnode->val = a[(*pi)++];
    newnode->left = MakeTree(a, pi);
    newnode->right = MakeTree(a, pi);
    return newnode;
}
// TreeNode* MakeTree(char* a, int* pi)
// {
//     if(a[*pi] == '#' || a[*pi] == '\0')
//     {
//         return NULL;
//     }
//     TreeNode* newnode = (TreeNode*)malloc(sizeof(TreeNode));
//     newnode->val = a[(*pi)++];
    
//     newnode->left = MakeTree(a, pi);
//     (*pi)++;
//     newnode->right = MakeTree(a, pi);
//     return newnode;
// }
void Inorder(TreeNode* root)
{
    if(root==NULL)
    {
        return;
    }
    Inorder(root->left);
    printf("%c ",root->val);
    Inorder(root->right);
}
int main() {
    char a[101];
    scanf("%s", a);
    int i = 0;
    TreeNode* newtree = MakeTree(a, &i);
    Inorder(newtree);
    return 0;
}

(八)翻转二叉树

struct TreeNode* invertTree(struct TreeNode* root) {
    if(root == NULL)
    {
        return NULL;
    }
    //invertTree返回值是交换之后的节点
    struct TreeNode* left = invertTree(root->left);
    struct TreeNode* right = invertTree(root->right);
    root->left = right;
    root->right = left;
    return root;
}

(九)平衡二叉树

// int getTreeHeight(struct TreeNode* root)//这个太慢,超出时间限制
// {
//     if (root == NULL)
//         return 0;
//     return getTreeHeight(root->left) > getTreeHeight(root->right)?getTreeHeight(root->left)+1:getTreeHeight(root->right)+1;
// }
int getTreeHeight(struct TreeNode* root)
{
    if (root == NULL)
        return 0;
    int leftHeight = getTreeHeight(root->left);
    int rightHeight = getTreeHeight(root->right);
    return  (leftHeight>rightHeight?leftHeight:rightHeight)+1;
}
bool isBalanced(struct TreeNode* root) {
    if(root == NULL)
        return true;
    //左右子树高度差小于等于1
    return abs(getTreeHeight(root->left) - getTreeHeight(root->right)) <= 1 &&
           isBalanced(root->left) && isBalanced(root->right);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值