二叉树链式结构的实现
在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。
//二叉树的结点结构体类型
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.返回二叉树的根结点指针;
二叉树的遍历
前序、中序以及后序遍历
//前序:
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;
}
!!! 对于二叉树的层序遍历我们会再介绍完队列后再进行介绍;