C语言实现二叉树的链式结构,二叉树Leetcode几道经典OJ题

二叉树链式结构的实现

        在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。

//二叉树的结点结构体类型
typedef struct binaytreenode
{
	int val;
	struct binaytreenode* left;
	struct binaytreenode* right;

}btnode;

结点类型中有个存放整型数据的 val,以及左右儿子的结构体指针,可以用来找到左右儿子;

创建二叉树结点:

                1.先开辟结点空间;

                2. 将x存入val中;

                3.暂时将其左右儿子指针置为空;
 

btnode* buynode(int x)
{
	btnode* tmp = NULL;
	tmp = (btnode*)malloc(sizeof(btnode));
	if (tmp == NULL)
	{
		exit(-1);
	}
	tmp->val = x;
	tmp->left = tmp->right = NULL;

	return tmp;
}

        

手动构建一颗二叉树:如图

BTNode* CreatBinaryTree()
{

     BTNode* node1 = BuyNode(1);
     BTNode* node2 = BuyNode(2);
     BTNode* node3 = BuyNode(3);
     BTNode* node4 = BuyNode(4);
     BTNode* node5 = BuyNode(5);
     BTNode* node6 = BuyNode(6);
 
     node1->_left = node2;
     node1->_right = node4;
     node2->_left = node3;
     node4->_left = node5;
     node4->_right = node6;
     return node1;
}

                1.创建6个结点,再分别用指针将他们的左右儿子串联起来;

                2.返回二叉树的根结点指针;

二叉树的遍历

        前序、中序以及后序遍历

学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依
次对二叉 树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具
体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
按照规则,二叉树的遍历有: 前序 / 中序 / 后序的递归结构遍历
1. 前序遍历 (Preorder Traversal 亦称先序遍历 )—— 访问根结点的操作发生在遍历其左右子树之前。(根,左,右
2. 中序遍历 (Inorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之中(间)。(左,根,右
3. 后序遍历 (Postorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之后。(左,右,根
                        由于树是递归结构,因此我们用递归的方式来遍历树
//前序:
void prevorder(btnode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%d ", root->val);
	prevorder(root->left);
	prevorder(root->right);
}

                1.设置递归返回条件:当遇到空指针时返回;

                2.先遍历根结点(打印),再递归左子树,最后递归右子树;

//中序:
void inorder(btnode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	inorder(root->left);
	printf("%d ", root->val);
	inorder(root->right);

}

                1.和前序一样先设置递归条件;

                2.先递归左子树,再遍历根结点,最后递归右子树;

后序与前两种类似,也只是改变一下顺序;

节点个数以及高度等

1.二叉树的结点个数:


           我们可以用前序遍历来解决,首先 父亲结点已经是一个结点了,只要再递归左右子树加上左右子树的结点数目即可;递归的返回条件是遇到NULL空指针就返回0;

                遇到空结点返回0;叶子结点返回1;非叶子结点:左+右+1;

// 二叉树节点个数
int BinaryTreeSize(btnode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}

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

2.二叉树叶子结点个数:

                和上个问题的思想基本一样只需:遇到空返回0;遇到叶子结点返回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);


}

               

3.二叉树第k层结点个数:
            思想:根的第k层,则递归到下一层是下一次的k-1层,一直递归直到k==1时开始数结点个数;

// 二叉树第k层节点个数
int BinaryTreeLevelKSize(btnode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);

}

4.二叉树查找值为x的节点

                思想:对该树前序遍历一边若找到该x的值则返回该结点的指针,否则返回空指针;

// 二叉树查找值为x的节点
btnode* BinaryTreeFind(btnode* root, int x)
{
	if (root == NULL)
	{
		return NULL;

	}

	if (root->val == x)
	{
		return root;
	}
	btnode* thenode = NULL;
	thenode = BinaryTreeFind(root->left, x);
	if (thenode != NULL)
	{
		return thenode;
	}
	thenode=BinaryTreeFind(root->right, x);
	if (thenode != NULL)
	{
		return thenode;
	}

	return NULL;

}

 二叉树基础oj练习

        1.单值二叉树   题号:965    leetcode链接:965. 单值二叉树 - 力扣(LeetCode)

                

分析:此题只需要左子树值与根相同,右子树值也与根相同即可;两者同时满足就可认为是单值而成树; 

                若根为空直接返回true,若左右子树都分别不为空并且其值不和根结点相同就返回false;

否则递归左右子树向下判断,左右同时满足是单值二叉树,不然就不是单值二叉树;

bool isUnivalTree(struct TreeNode* root)
{

    //通用的一次判断:
    if(root==NULL)
    {
        return true;
    }
    if(root->left && root->val !=root->left->val)
    {
        return false;
    }
     if(root->right && root->val !=root->right->val)
    {
        return false;
    }
    //向下递归:
    return isUnivalTree(root->left) && isUnivalTree(root->right);

}

        2.检查两颗树是否相同   leetcode链接:100. 相同的树 - 力扣(LeetCode)

 

分析:只需要两颗树的  根与根相同,左子树和左子树相同,右子树和右子树相同即可;

若  p与q同时为空返回true, p与q一个为空另一个不为空返回false,p与q都不为空但是值不同返回

false;   否者递归左右, 左右同时返回true就是两颗相同的树;

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

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

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

 3.对称二叉树   leetcode链接:101. 对称二叉树 - 力扣(LeetCode)

 

分析:此题目解法与上体十分类似,首先需要把根结点的左右子树单独拆分出来,形成两颗单独的树,然后只需这两颗树的    根与根相同,左子树等与右子树相同,右子树与左子树相同即可;

操作与上体类似,只要递归的时候 递归 两棵树的 左和右,右和左即可;

bool check(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 check(p->left,q->right) && check(p->right,q->left);
}


bool isSymmetric(struct TreeNode* root)
{

    if(root==NULL)
    {
        return true;

    }

    //分成左右两棵树进行判断;
    return check(root->left,root->right);
    
}

 

4.二叉树的前序遍历    leetcode链接:144. 二叉树的前序遍历 - 力扣(LeetCode) 

                         

 

分析:此题的大致意思是对所给的一颗二叉树进行前序遍历,然后将它的值存放到一个开辟空间的数组中,并返回该数组;

                1.先要得到这个二叉树的结点个数,便于开辟空间。

                2.创建好空间a后我们将其传给beforetree函数,还有二叉数的根结点root,以及一个用来访问数组a的下标的i,对i我们进行传址调用,否则递归或者函数返回的时候形参会被销毁;

                3.前序遍历 我们应该先访问根,再访问左右;若根为空直接返回,否则将它的值存在数组a中;之后递归左子树,再递归右子树;

               

int sizetree(struct TreeNode* root)
{
    if(root==NULL)
    {
        return 0;
    }

    return sizetree(root->left)+sizetree(root->right)+1;

}

void beforetree(int *a,struct TreeNode* root,int *pi)
{
    if(root==NULL)
    {
        return;
    }
    a[(*pi)]=root->val;
    (*pi)++;

    beforetree(a,root->left,pi);
    beforetree(a,root->right,pi);
}



int* preorderTraversal(struct TreeNode* root, int* returnSize)
{
    int n=sizetree(root);

    int* a=(int*)malloc(sizeof(int)*n);

    int i=0;
    beforetree(a,root,&i);
    *returnSize=n;
    return a;
}

                与该题类似的还有  中序遍历  后序遍历;步骤与前序基本一样,只是访问的顺序不一样。中序遍历是先递归左再访问根最后递归右;后序遍历是先递归左再递归右最后访问根; 

            中序leetcode链接:94. 二叉树的中序遍历 - 力扣(LeetCode)

nt sizetree(struct TreeNode* root)
{
    if(root==NULL)
    {
        return 0;
    }
    return sizetree(root->left)+sizetree(root->right)+1;
}

void intree(struct TreeNode* root,int*a,int*pi)
{
    if(root==NULL)
    {
        return ;
    }
    intree(root->left,a,pi);

    a[(*pi)]=root->val;
    (*pi)++;

    intree(root->right,a,pi);
    
}


int* inorderTraversal(struct TreeNode* root, int* returnSize)
{
    int n=sizetree(root);
    *returnSize=n;

    int*a=(int*)malloc(sizeof(int)*n);

    int i=0;
    intree(root,a,&i);
    return a;
}

后序遍历leetcode链接:145. 二叉树的后序遍历 - 力扣(LeetCode) 

int sizetree(struct TreeNode* root)
{
    if(root==NULL)
    {
        return 0;
    }

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


void aftertree(struct TreeNode* root,int*a,int*pi)
{
    if(root==NULL)
    {
        return ;
    }
    aftertree(root->left,a,pi);
    aftertree(root->right,a,pi);

    a[(*pi)]=root->val;
    (*pi)++;
}
int* postorderTraversal(struct TreeNode* root, int* returnSize)
{
    int n=sizetree(root);
    *returnSize=n;

    int* a=(int *)malloc(sizeof(int)*n);
    int i=0;

    aftertree(root,a,&i);

    return a;
}

5.另一颗树的子树。leetcode链接:572. 另一棵树的子树 - 力扣(LeetCode) 

 

分析:此题只需要分别判断    根对应的树是否与子树相同  左子树是否与子树相同   右子树是否与子树相同; 树与树是否相同已经在题2中分析过;

                因为子树不可能为空,所以若根为空直接返回false;否者在进行判断;

bool isSameTree(struct TreeNode* p, struct TreeNode* q)
{
    if(p==NULL && q==NULL)
    {
        return true;
    }

    if(p==NULL || q==NULL)
    {
        return false;
    }

    if(q->val != p->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(isSameTree(root,subRoot)==true)
    {
        return true;
    }

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

}

6.翻转二叉树。 leetcode链接: 226. 翻转二叉树 - 力扣(LeetCode)

 

分析:首先根结点不变,将根结点的左右儿子互换;然后递归下去;

struct TreeNode* invertTree(struct TreeNode* root)
{
    //遇到空返回;
    if(root==NULL)
    {
        return NULL;
    }
    
    //互换左右儿子;
    struct TreeNode*tmp=root->left;
    root->left=root->right;
    root->right=tmp;

    //递归下去,应为函数有返回值,因此创建一个sum临时变量来接收;
    struct TreeNode* sum=NULL;
    sum=invertTree(root->left);
    sum=invertTree(root->right);

    //返回根结点;
    return root;
}

 7.二叉树的最大深度。  leetcode链接:104. 二叉树的最大深度 - 力扣(LeetCode)

                分析:二叉树的最大深度,就是左子树的最大深度或者右子树的最大深度+1;

nt maxDepth(struct TreeNode* root)
{
    if(root==NULL)
    {
        return 0;
    }

    int leftdepth=maxDepth(root->left);
    int rightdepth=maxDepth(root->right);

    return (leftdepth>rightdepth) ? leftdepth+1:rightdepth+1;
}

二叉树的创建和销毁  

1. 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树。

                 分析:1.首先要有结点类型;

                            2.遇到空返回空;

                            3.存入非空的根结点;

                             4.递归左右;

                 递归下去的时候创建结点,递归返回的时候结点开始连接;

//结点类型;
typedef char BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
	//遇到空结点返回空结点:
	if (a[( * pi)] == '#')
	{
		*pi++;
		return NULL;
	}

	//开辟结点空间:
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	//存入数据:
	node->data = a[(*pi)];
	*pi++;

	//递归左子树
	node->left = BinaryTreeCreate(a, n, pi);
	//递归右子树
	node->right=BinaryTreeCreate(a, n, pi);

	return node;

}

2.二叉数的销毁。

                分析:后序遍历  对二叉数进行空间释放

                判断遇到空结点返回,先销毁左子树,再销毁右子树,最后销毁根;

// 二叉树销毁  
void BinaryTreeDestory(BTNode* root)
{
	//后序销毁;

	if (root == NULL)
	{
		return;
	}
	BinaryTreeDestory(root->left);
	BinaryTreeDestory(root->left);

	free(root);

	root = NULL;

}

                        !!! 对于二叉树的层序遍历我们会再介绍完队列后再进行介绍;

 

  

 

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

(int*) nightfall

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值