【数据结构】二叉树及二叉树的基础oj练习


二叉树

首先,我们先了解一下树的概念:

树的概念

树,其实是由有限个数构成的一个具有层次关系的集合,它是一种非线性的数据结构。
在这里插入图片描述
我们之所以叫它树,是因为它像倒挂着的树,由根向下递归展开。

树的相关概念

节点:相当于这个树中每一个数字,而最上面那个节点叫根节点,由根节点延申下来的数叫子节点或者叫子树。
节点的度:一个节点含有的子树的个数称为该节点的度,例如上图中1的度为2,2的度为2,4的度为0。
叶节点或终端节点:度为0的节点称为叶节点,例如上图中的4,5,6,7都是叶节点。
非终端节点或分支节点:度不为0的节点,例如上图中2和3。
双亲结点或父节点:如果一个节点含有子节点,则这个节点称为其子节点的双亲结点,如上图1是2的父节点,也是3的父节点。
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点,如上图2是1的子节点。
兄弟节点:具有相同父节点的子节点互称为兄弟节点,如上图中2,3都是1的子节点,2和3互为兄弟节点。
树的度:一棵树中,节点度数最大的那个就是树的度,上图树的度为2。
节点的层次:由根节点开始定义,根节点为第一层,根的子节点为第二层,以此类推。
树的高度或深度:树中节点最大的层次,上图树的高度为3。
堂兄弟节点:双亲在同一层的节点互为堂兄弟,上图中5和6就互为堂兄弟节点。
节点的祖先:从根到该节点所经分支上的所有节点,上图1就是所有节点的祖先。
子孙:以某节点为根的子树中任一节点都称为该节点的子孙,上图所有节点都是1的子孙。
森林:这些互不相交的树的集合称为森林。

注意事项:
子树之间不能有交集,子树之间不能相交
除了根节点外,每个节点有且仅有一个父节点

二叉树的概念

简单来说,二叉树就是树的度数为2的。
任何一个二叉树都是由以下几种情况复合而成:
在这里插入图片描述

特殊的二叉树

满二叉树:一个二叉树,如果每一层的节点数都是最大值,那么这个二叉树就是满二叉树。假设这个满二叉树有x层,那么利用等比公式,可以算出它的节点总数就是2x -1个。
完全二叉树:简单来说就是一个二叉树按照层序遍历(层序遍历后面会说)从左到右都是连续的,如果一个子树,它没有左子树,但是有右子树,那么它就不是完全二叉树。满二叉树是一种特殊的完全二叉树。

二叉树的性质

1、若根节点的层数为1,则一个非空的二叉树它第x层最多有2x-1个节点,如果深度为h的二叉树,它的节点总数不超过2x -1。
2、对于满二叉树而言,若根节点的层数为1,如果总节点数为n,那么高度h=log2(n+1)。
3、对任何一个二叉树,设度为0的子树有n0个,度为2的有n2个,则n2+1=n0

二叉树的存储结构

在这里插入图片描述

顺序存储

也就是二叉树用层序遍历在数组中依次存储,但是考虑到数据存储问题,一般是存储完全二叉树的数据才用数组的形式存储,因为非完全二叉树可能会有左子树为空,右子树有数据的问题导致数组有些位置空缺,造成空间的浪费。所以一般使用数组存储适合完全二叉树。

在数组存储中我们发现:假设一个父节点在数组中的下标为i,并且有左右子树的情况下(左右子树都不为空),那么它的左子树下标为2i+1,右子树下标为2i+2,如果2i+1>=n(n为总结点数),那么该节点没有左子树,同理,如果2i+2>=n,则该节点没有右子树。

在这里插入图片描述

链式存储

用链表来表示一个二叉树,用结构体创建3个结构体变量,分别表示当前节点的数据和左右指针。

typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
	struct BinTreeNode* Left; // 指向当前节点左孩子
	struct BinTreeNode* Right; // 指向当前节点右孩子
	BTDataType data; // 当前节点值域
}

二叉树的遍历

前序遍历

在这里插入图片描述
前序遍历就是先遍历根节点,再遍历左子树,再遍历右子树。
我们来试试这道题:二叉树的前序遍历

void Traversal(struct TreeNode* root, int* node, int* returnSize)
{
    if(root == NULL)
    {
        return;
    }
    
    node[(*returnSize)++] = root->val;
    Traversal(root->left,node,returnSize);
    Traversal(root->right,node,returnSize);
}

int* preorderTraversal(struct TreeNode* root, int* returnSize){
    int* node = (int*)malloc(sizeof(int) * 100);
    *returnSize = 0;
    Traversal(root, node,returnSize);
    return node;
}

中序遍历

在这里插入图片描述
中序遍历就是先遍历左子树,再遍历根节点,再遍历右子树。
试题:二叉树的中序遍历

void Traversal(struct TreeNode* root,int* returnSize,int* arr)
{
    if(root == NULL)
    {
        return;
    }
    Traversal(root->left,returnSize,arr);
    arr[(*returnSize)++] = root->val;
    Traversal(root->right,returnSize,arr);
}

int* inorderTraversal(struct TreeNode* root, int* returnSize){
    int* arr = (int*)malloc(sizeof(int)*100);
    *returnSize = 0;
    Traversal(root,returnSize,arr);
    return arr;
}

后序遍历

在这里插入图片描述
后序遍历就是先遍历左子树,再遍历右子树,再遍历根节点。
试题:二叉树的后序遍历

void preorder(int* arr,int* returnSize, struct TreeNode* root)
{
    if(root == NULL)
    {
        return;
    }
    
    preorder(arr,returnSize,root->left);
    
    preorder(arr,returnSize,root->right);
    arr[(*returnSize)++] = root->val;

}

int* postorderTraversal(struct TreeNode* root, int* returnSize){
    int* arr = (int*)malloc(sizeof(int) * 200);
    *returnSize = 0;
    preorder(arr,returnSize,root);
    return arr;
}

如果容易忘记,你就这么理解:把左右子树看成一个整体,把根节点看成要插队的人
前序遍历就是插在前面,变成根,左子树,右子树
中序遍历就是插在中间,变成左子树,根,右子树
后序遍历就是插在后面,变成左子树,右子树,根。

层序遍历

在这里插入图片描述
顾名思义,就是一层一层遍历。

关于二叉树的函数接口

// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

二叉树节点个数

int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;//进入if语句,表示是空节点
	}
 
	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;//走到这里表示不是空节点,再判断左右子树是不是空节点,再+1.
}

二叉树叶子节点个数

int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;//空节点返回
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;//必须是左右子树都为空,用&&
	}
 
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

二叉树第k层节点个数

int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1 && root != NULL)
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

二叉树查找值为x的节点

BTNode* BinaryTreeFind(BTNode* root, BTdatatype x)
{
	if (root == NULL)
	{
		return 0;                      
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode* ret1 = BinaryTreeFind(root->left, x);
	if (ret1)
	{ 
		return ret1;
	}
 
	BTNode* ret2 = BinaryTreeFind(root->right, x);
	if(ret2)
	{
		return ret2;
	}
}

二叉树的基础oj练习

单值二叉树

传送门:点击这里

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

相同的树

传送门:点击这里

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 Symmetric(struct TreeNode* left, struct TreeNode* right)
{
    if(left == NULL && right == NULL)
    {
        return true;
    }
    if(left == NULL || right == NULL)
    {
        return false;
    }
    if(left->val != right->val)
    {
        return false;
    }
    return Symmetric(left->left,right->right) && Symmetric(left->right,right->left);
}

bool isSymmetric(struct TreeNode* root){
    if(root == NULL)
    {
        return true;
    }
    return Symmetric(root->left,root->right);
}

另一棵树的子树

传送门:点击这里

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


bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
    if(root == NULL)
    {
        return false;
    }
    if(Subtree(root,subRoot))
    {
        return root;
    }
    else
    {
        return isSubtree(root->left,subRoot) || isSubtree(root->right,subRoot);
    }

}

今天的oj练习就练到这里,如果觉得这篇文对你有帮助的话,就点个赞吧,如有错误,望指出!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值