文章目录
-
- 二叉树相关知识
- 前言 树的相关知识
-
- 1.二叉树性质概念
-
- 2.堆的实现
-
- 3.二叉树实现
-
- 4.二叉树相关oj题
- 1.[144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/)
- 2.[965. 单值二叉树](https://leetcode.cn/problems/univalued-binary-tree/)
- 3.[104. 二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/)
- 4.[226. 翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/)
- 5.[100. 相同的树](https://leetcode.cn/problems/same-tree/)
- 6.[101. 对称二叉树](https://leetcode.cn/problems/symmetric-tree/)
- 7.[572. 另一棵树的子树](https://leetcode.cn/problems/subtree-of-another-tree/)
- 8.[110. 平衡二叉树](https://leetcode.cn/problems/balanced-binary-tree/)
文章目录
- 二叉树相关知识
- 前言 树的相关知识
- 1.二叉树性质概念
- 2.堆的实现
- 3.二叉树实现
- 4.二叉树相关oj题
- 1.[144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/)
- 2.[965. 单值二叉树](https://leetcode.cn/problems/univalued-binary-tree/)
- 3.[104. 二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/)
- 4.[226. 翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/)
- 5.[100. 相同的树](https://leetcode.cn/problems/same-tree/)
- 6.[101. 对称二叉树](https://leetcode.cn/problems/symmetric-tree/)
- 7.[572. 另一棵树的子树](https://leetcode.cn/problems/subtree-of-another-tree/)
- 8.[110. 平衡二叉树](https://leetcode.cn/problems/balanced-binary-tree/)
二叉树相关知识
前言 树的相关知识
1.树的结构
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因 为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
2.树的相关概念 百度百科 树的概念
3.树的常用表示
树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既然保存值域,也要保存结点和结点之间 的关系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法 等。我们这里就简单的了解其中最常用的孩子兄弟表示法。
typedef int DataType;
struct Node
{
struct Node* _firstChild1; // 第一个孩子结点
struct Node* _pNextBrother; // 指向其下一个兄弟结点
DataType _data; // 结点中的数据域
};
1.二叉树性质概念
1.二叉树的概念与结构
-
二叉树不存在度大于2的结点
-
二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
特殊的二叉树
-
满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是 说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
-
- 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K 的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对 应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
注意:对于任意的二叉树都是由以下几种情况复合而成的:
2.二叉树的性质
- 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 个结点.
- 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 .
- 对任何一棵二叉树, 如果度为0其叶结点个数为 , 度为2的分支结点个数为 ,则有 = +1
- 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h= log2(n+1). ( (ps:是log以2 为底,n+1为对数)
- 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对 于序号为i的结点有:1.若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点 2. 若2i+1=n否则无左孩子 3. 若2i+2=n否则无右孩子.
3.二叉树的存储结构
二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。
-
顺序存储
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空 间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。二叉树顺 序存储在物理上是一个数组,在逻辑上是一颗二叉树。
-
链式存储
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是 链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所 在的链结点的存储地址 。
接下来开始堆(二叉树的顺序结构)和二叉树(二叉树的链式结构)的实现
2.堆的实现
1.堆的相关知识
堆是由数组实现,物理层面我们在对数组操作,逻辑上在对一棵二叉树操作.
左右孩子与父节点计算公式
leftchild=parent*2+1;左孩子
rightchild=parent*2+2;右孩子
parent=(child-1)/2;父节点
2.堆的函数接口实现
//函数接口实现
#define _CRT_SECURE_NO_WARNINGS 1
#include"heap.h"
//交换函数
void swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//检查内存是否满
void checkheap(Heap* hp)
{
if (hp->_capacity == hp->_size)
{
int newcapacity = hp->_capacity == 0 ? 4 : hp->_capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * newcapacity);
if (tmp == NULL)
{
exit(-1);
}
hp->_capacity = newcapacity;
hp->_a = tmp;
}
}
// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n)
{
assert(hp);
hp->_a = NULL;
hp->_capacity = hp->_size = 0;
for (int i = 0; i < n; i++)
{
HeapPush(hp, a[i]);
}
}
// 堆的销毁
void HeapDestory(Heap* hp)
{
assert(hp);
free(hp->_a);
hp->_a = NULL;
hp->_capacity = hp->_size = 0;
}
// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
assert(hp);
checkheap(hp);
hp->_a[hp->_size++] = x;
//HeapUp(hp, hp->_size - 1);
heapdown(hp, hp->_size,(hp->_size - 2)/2);
}
//小堆向上调整
void HeapUp(Heap* hp, int child)
{
assert(hp);
int parent = (child - 1) / 2;
while (hp->_a[parent] > hp->_a[child])
{
swap(&hp->_a[parent], &hp->_a[child]);
child = parent;
parent = (child - 1) / 2;
}
}
//小堆向下调整(有bug版)
//void heapdown(Heap *hp,int n)
//{
// int parent = 0;
// int child = 0;
// parent = child;
// child = parent * 2 + 1;
// if (hp->_a[child + 1] < hp->_a[child]&&child + 1 < n)
// {
// child++;
// }
// while (child < n)
// {
// if (hp->_a[parent] > hp->_a[child])
// {
// swap(&hp->_a[parent], &hp->_a[child]);
// }
// parent = child;
// child = parent * 2 + 1;
// if (hp->_a[child + 1] < hp->_a[child]&&child+1<n)
// {
// child++;
// }
// }
//}
//小堆向下调整
void heapdown(Heap* hp, int n,int root)
{
while (root >= 0)
{
int parent = root;
int child = 0;
child = parent * 2 + 1;
if (child + 1 < n &&hp->_a[child + 1] < hp->_a[child] )
{
child++;
}
while (child < n)
{
if (hp->_a[parent] > hp->_a[child])
{
swap(&hp->_a[parent], &hp->_a[child]);
}
parent = child;
child = parent * 2 + 1;
if (hp->_a[child + 1] < hp->_a[child] && child + 1 < n)
{
child++;
}
}
root--;
}
}
// 堆的删除
void HeapPop(Heap* hp)
{
assert(hp);
assert(!HeapEmpty(hp));
hp->_a[0] = hp->_a[hp->_size - 1];
hp->_size--;
heapdown(hp, hp->_size,0);
}
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
assert(hp);
assert(!HeapEmpty(hp));
return hp->_a[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{
assert(hp);
return hp->_size;
}
// 堆的判空
int HeapEmpty(Heap* hp)
{
assert(hp);
if (hp->_size == 0)
{
return 1;
}
return 0;
}
//堆排序 降序
void heapsort(int* a, int n)
{
Heap hp;
hp._a = a;
hp._capacity = hp._size = n;
for (int i = 0; i < n; i++)
{
heapdown(&hp, i+1,(i-1)/2);//小堆
}
int i = 1;
while (i<n)
{
swap(&hp._a[0], &hp._a[n - i]);
heapdown(&hp, n-i,0);
i++;
}
}
// TopK问题:找出N个数里面最大/最小的前K个问题。
// 比如:未央区排名前10的泡馍,西安交通大学王者荣耀排名前10的韩信,全国排名前10的李白。等等问题都是Topk问题,
// 需要注意:
// 找最大的前K个,建立K个数的小堆
// 找最小的前K个,建立K个数的大堆
void PrintTopK(int* a, int n, int k)
{
Heap hp;
hp._a = NULL;
hp._capacity = hp._size = 0;
for (int i = 0; i < k; i++)
{
HeapPush(&hp,a[i]);
}
for (int i = k; i < n; i++)
{
if (a[i] > HeapTop(&hp))
{
hp._a[0] = a[i];
heapdown(&hp, hp._size,0);
}
}
for (int i = 0; i < k; i++)
{
printf("%d ",hp._a[i]);
}
}
void TestTopk()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10,34,124,235,445,33,25,5,23,4,2,2,2,7,35,67,45,56,222,367,990};
PrintTopK(arr,sizeof(arr)/sizeof(arr[0]),6);
}
3.堆的函数部分讲解
1.堆的向下调整
自顶向下:主要用于堆删除节点,删除结点之后把最后一个节点替换到,当前根节点位置,对此节点进行自顶向下操作
2.堆的向上调整
自底向上:主要用于节点的插入,当一个节点插入到这个堆之后,向上调整堆,堆还能符合大顶堆或者小顶堆的定义。
3.堆的删除
删除堆的节点,删除堆节点的操作,只能够删除根节点,把根节点和最后一个节点交换位置之后,然后弹出最后一个节点,并且对当前堆进行自顶向下的操作
3.二叉树实现
1.二叉树的函数接口实现
#define _CRT_SECURE_NO_WARNINGS 1
#include"tree.h"
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
if (a[*pi] == '#')
{
(*pi)++;
return NULL;
}
BTNode* newBTnode = (BTNode*)malloc(sizeof(BTNode));
if (newBTnode == NULL)
{
exit(-1);
}
newBTnode->_data = a[*pi];
(*pi)++;
newBTnode->_left = BinaryTreeCreate(a, n, pi);
newBTnode->_right = BinaryTreeCreate(a, n, pi);
return newBTnode;
}
// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{
if (*root == NULL)
{
return;
}
BinaryTreeDestory(&((*root)->_left));
BinaryTreeDestory(&((*root)->_right));
free(*root);
*root = NULL;
}
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
else
{
return 1 + BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right);
}
}
// 二叉树叶子节点个数
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)
{
assert(k > 0);
if (root == NULL)
{
return 0;
}
if (k == 1)
{
return 1;
}
return BinaryTreeLevelKSize(root->_left, k - 1) + BinaryTreeLevelKSize(root->_right, k - 1);
}
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
{
return NULL;
}
if (root->_data == x)
{
return root;
}
BTNode* tmp=BinaryTreeFind(root->_left, x);
if (tmp)
{
return tmp;
}
tmp=BinaryTreeFind(root->_right, x);
if (tmp)
{
return tmp;
}
return NULL;
}
// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%c ", root->_data);
BinaryTreePrevOrder(root->_left);
BinaryTreePrevOrder(root->_right);
}
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
BinaryTreeInOrder(root->_left);
printf("%c ", root->_data);
BinaryTreeInOrder(root->_right);
}
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
BinaryTreePostOrder(root->_left);
BinaryTreePostOrder(root->_right);
printf("%c ", root->_data);
}
//MAX函数
int MAX(int a,int b)
{
return a > b ? a : b;
}
//二叉树的高度
int treehight(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int left = treehight(root->_left);
int right = treehight(root->_right);
return 1 + MAX(left, right);
//return 1+(right>left?right:left);
}
//接下来的两个函数使用了之前队列的函数接口,需要源代码请到我的队列文章获取
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q,root);
while (QueueEmpty(&q))
{
BTNode *tmp = QueueFront(&q);
QueuePop(&q);
printf("%c", tmp->_data);
if (tmp->_left)
{
QueuePush(&q,tmp->_left);
}
if (tmp->_right)
{
QueuePush(&q, tmp->_right);
}
}
printf("\n");
QueueDestroy(&q);
}
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (QueueEmpty(&q))
{
BTNode* tmp = QueueFront(&q);
QueuePop(&q);
if (tmp == NULL)
{
break;
}
QueuePush(&q, tmp->_left);
QueuePush(&q, tmp->_right);
}
while (QueueEmpty(&q))
{
BTNode* tmp = QueueFront(&q);
QueuePop(&q);
if (tmp != NULL)
{
QueueDestroy(&q);
return 0;
}
}
return 1;
QueueDestroy(&q);
}
2.二叉树的函数部分讲解
四种遍历
- 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
- 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
- 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
- 层序遍历 设二叉树的根节点所在 层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层 上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
4.二叉树相关oj题
1.144. 二叉树的前序遍历
解题思路:
直接前序遍历即可
解题代码:
void BinaryTreePrevOrder(struct TreeNode* root,int *a,int *arr)
{
if (root == NULL)
{
return;
}
arr[*a]=root->val;
(*a)++;
BinaryTreePrevOrder(root->left,a,arr);
BinaryTreePrevOrder(root->right,a,arr);
}
int* preorderTraversal(struct TreeNode* root, int* returnSize){
int *arr=(int *)malloc(sizeof(int)*100);
*returnSize=0;
BinaryTreePrevOrder(root,returnSize,arr);
return arr;
}
2.965. 单值二叉树
解题思路:
判断是否为单值二叉树,及判断每一个节点与左右节点的值是否即可,通过分析我们可以使用递归来实现判断,
从子节点开始判断,逐渐往上判断.
解题代码:
bool isUnivalTree(struct TreeNode* root){
if(root==NULL)
{
return true;
}
//都为空
if(root->left==NULL&&root->right==NULL)
{
return true;
}
//其中一个为空
if(root->left==NULL||root->right==NULL)
{
if(root->left)
{
if((root->val!=root->left->val))
{
return false;
}
}
else
{
if((root->val!=root->right->val))
{
return false;
}
}
}
if((root->left!=NULL&&root->val!=root->left->val)||(root->right!=NULL&&root->val!=root->right->val))
{
return false;
}
bool tmp=isUnivalTree(root->left);
if(!tmp)
{
return false;
}
tmp =isUnivalTree(root->right);
return tmp;
}
3.104. 二叉树的最大深度
解题思路:
直接求树的高度即可
解题代码:
int maxDepth(struct TreeNode* root){
if(root==NULL)
{
return 0;
}
int l=maxDepth(root->left);
int r=maxDepth(root->right);
return l>r?l+1:r+1;
}
4.226. 翻转二叉树
解题思路:
按照题意直接翻转即可
解题代码:
struct TreeNode* invertTree(struct TreeNode* root){
if(root==NULL)
{
return NULL;
}
struct TreeNode*tmp=root->left;
root->left= invertTree(root->right);
root->right=invertTree(tmp);
return root;
}
5.100. 相同的树
解题思路:
直接判断各个节点是否相同
解题代码:
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;
}
bool tmp=isSameTree(p->left,q->left);
if(tmp==false)
{
return false;
}
tmp=isSameTree(p->right,q->right);
return tmp;
}
6.101. 对称二叉树
解题思路:
跟上道题类似,直接将这棵树的左子树,右子树放入,判断是否为对称树.在判断时一个节点放左子树,另一个要放右子树.
解题代码:
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;
}
bool tmp=isSameTree(p->left,q->right);
if(tmp==false)
{
return false;
}
tmp=isSameTree(p->right,q->left);
return tmp;
}
bool isSymmetric(struct TreeNode* root){
if(root==NULL)
{
return true;
}
return isSameTree(root->left,root->right);
}
7.572. 另一棵树的子树
解题思路:
直接递归判断是否有相同子树.
解题代码:
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(isSameTree(root,subRoot))
{
return true;
}
return isSubtree(root->left,subRoot)||isSubtree(root->right,subRoot);
}
8.110. 平衡二叉树
解题思路:
暴力,求出每个节点左右子树高度,判断是否符合条件
解题代码:
int treehight(struct TreeNode* root)
{
if(root==NULL)
{
return 0;
}
int l=treehight(root->left);
int r=treehight(root->right);
return l>r?l+1:r+1;
}
bool isBalanced(struct TreeNode* root){
if(root==NULL)
{
return true;
}
return fabs(treehight(root->left) - treehight(root->right)) <= 1 && isBalanced(root->left) && isBalanced(root->right);
}
ULL)
{
return false;
}
if(isSameTree(root,subRoot))
{
return true;
}
return isSubtree(root->left,subRoot)||isSubtree(root->right,subRoot);
}
### 8.[110. 平衡二叉树](https://leetcode.cn/problems/balanced-binary-tree/)
![image-20220815100409153](https://i-blog.csdnimg.cn/blog_migrate/897471a97799644caa39210f56e8ecac.png)
解题思路:
暴力,求出每个节点左右子树高度,判断是否符合条件
解题代码:
```c
int treehight(struct TreeNode* root)
{
if(root==NULL)
{
return 0;
}
int l=treehight(root->left);
int r=treehight(root->right);
return l>r?l+1:r+1;
}
bool isBalanced(struct TreeNode* root){
if(root==NULL)
{
return true;
}
return fabs(treehight(root->left) - treehight(root->right)) <= 1 && isBalanced(root->left) && isBalanced(root->right);
}