树是一种非线性 的数据结构,它是由n(n>0)个有限节点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一颗倒挂的树,也就是说它是根朝上,而叶朝下。
注意:树形结构中,子树之间不能有交集,否则就不是树形结构
节点的度: 一个节点含有的子树的个数称为该节点的度;如上图:F点的度为3
叶节点或终端节点: 度为0的节点称为叶节点;如上图:B、C、H、I、P、Q…都是叶节点
非终端节点或分支节点: 度不为0的节点;如上图J、D、E…
双亲节点或父节点: 若一个节点含有子节点,则称这个节点为其子节点的父节点
孩子节点或子节点: 一个节点含有的子树根节点称为该节点的子节点;如图:B是A的孩子节点
兄弟节点: 具有相同父节点的节点互称为兄弟节点;如图:B、C、D、E、F、G是兄弟节点
树的度: 一棵树中最大节点的度称为树的度;如上图:树的度为6
节点的层次: 从根开始定义起,根为第一层,根的子节点为第二层,以此类推;
树的高度或深度: 树中节点的最大层次;如上图:树的高度为4.
堂兄弟节点: 双亲在同一层的节点互为堂兄弟节点,如图:H、I互为堂兄弟节点
节点的祖先: 从根到该节点所经分支上的所有节点;如图:A是所有节点的祖先
子孙: 以某节点为根的子树中的任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
森林: 由m(m>0)棵互补相交的树的集合称为森林;
树结构相对线性表就比较复杂了,要储存表示起来比较麻烦,既要保存值域,也要保存节点和节点之间的关系 这里介绍一下孩子兄弟表示法
typedef int DataType;
struct Node
{
struct Node* _fistchild1; //第一个孩子的节点
struct Node* _NextBrother; //指向其下一个兄弟的节点
DataType _data; //节点中的数据域
};
一棵二叉树是节点的一个有限集合,该集合:
注意:二叉树都是由一下几种情况组合而成:
2^k - 1
,则他就是满二叉树2^(i-1)
个结点2^h -1
x=y+1
- 当完全二叉树有偶数个节点(2n)的时候,这时候必然有一个度为1的节点,且度为0的节点的个数是所有节点的一半(n)
- 当完全二叉树有奇数个节点(2n-1)的时候,这时候必然有0个度为1的节点,且度为0的节点的个数是n
- 若i>0,则i位置节点的双亲序号:
(i-1)/2
;i=0,i为根节点编号- 若2i+1
2*i+1 ,否则就没有左孩子 - 若2i+2
2*i+2 ,否则就没有右孩子
二叉树的存储结构可以使用两种结构存储:顺序存储结构 和 链式存储结构
顺序存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中只有堆 才会使用数组来存储。
关于堆 的博客可以戳这里->数据结构:堆 的详解
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个节点由三个域组成,数据域 和 左右指针域 左右指针分别用来给出该节点左孩子和右孩子所在的链结点的存储地址。链式结构又分为二叉链和三叉链。
因为堆二叉树的了解还不够深入所以这里用最简单的方法创建一个二叉树
typedef int DataType;
typedef struct BinaryTree
{
DataType x;
struct BinaryTree* _left;
struct BinaryTree* _right;
}BT;
BT* BinaryTreeCreate1(BT* root)
{
BT* n1 = BuyNode(3);
BT* n2 = BuyNode(5);
BT* n3 = BuyNode(4);
BT* n4 = BuyNode(1);
BT* n5 = BuyNode(2);
BT* n6 = BuyNode(0);
n1->_left = n3;
n1->_right = n2;
n3->_left = n4;
n3->_right = n5;
n5->_left = n6;
return n1;
}
二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次
访问根节点的操作发生在遍历其左右子树之前
前序遍历实际上也就是按 根 左 右 的顺序来进行遍历,但要注意的是这里的左和右 是左子树和右子树,而不是左节点和右节点
进行前序遍历先访问根节点的值,然后以左节点为新的根节点,左子树为新的树按照根 左的顺序遍历直到根节点为NULL(如果根节点为NULL则说明该子树已经被遍历完了),然后返回上一级遍历 遍历右子树 按照 ** 根 左**,周而复始。
注意我们这里都是判断根节点是否为空来确定时还要递归下去
leetcode-二叉树的前序遍历
以leetcode上的前序遍历为例,这里要注意一下给的函数的参数的意义a是一个数组里面存储的是二叉树里的值,而returnsize是一个输出型参数,也就是传入一个参数returnsize的地址在函数内部修改使函数结束的时候,返回a数组的大小
一这颗子树为例:
void Pre_order(struct TreeNode*root,int *a,int *returnSize)
{
if(root!=NULL)
{
a[*returnSize]=root->val;
(*returnSize)++;
}
else
return;
Pre_order(root->left,a,returnSize); //左子树进行递归
Pre_order(root->right,a,returnSize); //右子树进行递归
}
int* preorderTraversal(struct TreeNode* root, int* returnSize){
int *p=(int *)malloc(2000*sizeof(int));
*returnSize=0;
Pre_order(root,p,returnSize);
return p;
}
访问根节点的操作发生在遍历其左子树和右子树之间
中序遍历实际上就是先将左子树递归至空(NULL)然后再方位根节点和右子树
leetcode-中序遍历
void postOrder(struct TreeNode*root,int *returnSize,int *a)
{
if(root!=NULL)
{
postOrder(root->left,returnSize,a); //先将左子树遍历至NULL
}
else
{
return;
}
a[*returnSize]=root->val; // 存入根节点的值
(*returnSize)++;
postOrder(root->right,returnSize,a); //遍历右子树 依然按照“左 根 右”
}
int* inorderTraversal(struct TreeNode* root, int* returnSize){
int *p=(int *)malloc(2000*sizeof(int));
int k=0;
postOrder(root,&k,p);
*returnSize=k;
return p;
}
访问根节点的操作发生在遍历其左右子树之中
和上面的思路相同,后续遍历是先递归左子树至空 然后再递归右子树为空,最后返回空节点
leetcode-后续遍历
void postOrder(struct TreeNode*root,int *returnSize,int *a)
{
if(root!=NULL)
{
postOrder(root->left,returnSize,a); //左子树递归
}
else
{
return;
}
postOrder(root->right,returnSize,a); //右子树递归
a[*returnSize]=root->val; //存储根节点
(*returnSize)++;
}
int* postorderTraversal(struct TreeNode* root, int* returnSize){
int *p=(int *)malloc(2000*sizeof(int));
int k=0;
postOrder(root,&k,p);
*returnSize=k;
return p;
}
层序遍历: 设二叉树的根节点所在层数为1,层数遍历就是从所在二叉树的根节点出发,首先访问第一层的节点,然后从左到右访问第二层上的节点,接着第三层的节点
leetcode-层序遍历
这个leetcode题用c语言写还是有点小复杂的,如果不是以数组的形式输出的话会简单不少,难点在于要以数组输出的话,你要控制二叉树每一层的数组大小,因为每一层的元素不一定是满的。
其实这题还有一个难点是看明白函数参数和返回值的意义:首先是返回值返回的是一个指针数组的数组名实际上是一个数组指针,然后** returnColumnSizes是一个输出型数组,分别存放的是 指针数组 每个 数组指针 指向数组的大小,而* returnSize是个输出型参数,存放的是返回的的指针数组的大小
如果要写main函数调用的话
int main()
{
TreeNode* root;
root = CreatBT();
int* returnColumnSizes;
int returnSize;
int** b;
b = levelOrder(root, &returnSize, &returnColumnSizes);
for (int i = 0; i < returnSize; i++) //控制行数
{
for (int j = 0; j < returnColumnSizes[i]; j++) // 控制每一行列的个数
{
printf("%d ", b[i][j]);
}
printf("\n");
}
}
这里实现的基本思路是:
#include
typedef struct TreeNode* Datatype;
typedef struct listNode
{
Datatype x;
struct listNode* next;
}ListNode;
typedef struct Queue
{
ListNode* head;
ListNode* tail;
}Queue;
void QueueInit(Queue* q)
{
ListNode* First = (ListNode*)malloc(sizeof(ListNode));
q->head = q->tail = First;
q->tail->next = NULL;
}
int QueueEmpty(Queue* q)
{
return q->head == q->tail;
}
void QueuePush(Queue* q, Datatype x)
{
assert(q);
q->tail->x = x;
ListNode* NewNode = (ListNode*)malloc(sizeof(ListNode));
q->tail->next = NewNode;
q->tail = NewNode;
q->tail->next = NULL;
}
void QueuePop(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
if (!QueueEmpty(q))
{
ListNode* temp = q->head->next;
free(q->head);
q->head = temp;
}
}
Datatype QueueFront(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->head->x;
}
void QueueDestroy(Queue* q)
{
ListNode* cur = q->head;
while (cur != q->tail)
{
ListNode* temp = cur->next;
free(cur);
cur = temp;
}
free(cur);
cur = NULL;
q->head = NULL;
q->tail = NULL;
}
//前面的都是队列的操作的函数
int** levelOrder(struct TreeNode* root, int* returnSize, int** returnColumnSizes) {
if (root == NULL)
{
*returnSize=0;
*returnColumnSizes=NULL;
return NULL;
}
int** p = (int**)malloc(sizeof(int*));
*p = NULL;
int* q = (int*)malloc(sizeof(int*));
Queue Q1;
QueueInit(&Q1);
QueuePush(&Q1, root);
int colsize = 0; //数组的个数,也就是行数
int index = 1; //每次数组的理论大小
while (!QueueEmpty(&Q1))
{
int m = 0; //每一个数组实际上存了几个数,因为是NULL是不会进入数组的
int k=0;
for (int i = 0; i < index; i++)
{
if (!QueueEmpty(&Q1)&&QueueFront(&Q1))
{
p[colsize] = (int*)realloc(p[colsize], (m + 1) * sizeof(int)); //为新的元素重新调整数组的大小
p[colsize][m] = QueueFront(&Q1)->val;
m++;
struct TreeNode* temp = QueueFront(&Q1);
if(temp->left)
QueuePush(&Q1, temp->left);
if(temp->right)
QueuePush(&Q1, temp->right);
if(temp->left==NULL)
k++;
if(temp->right==NULL)
k++;
}
if (!QueueEmpty(&Q1))
QueuePop(&Q1);
else
break;
}
q = (int*)realloc(q, (colsize + 1) * sizeof(int)); //把每一行的元素个数存入数组
q[colsize] = m;
index=index*2-k; //数组的理论大小要减去这一层空指针的个数
colsize++;
p = (int**)realloc(p,(colsize+1)*sizeof(int*)); //开辟下一行
p[colsize] = NULL;
}
QueueDestroy(&Q1);
*returnSize = colsize;
*returnColumnSizes = q;
return p;
}
例一:
如果一个二叉树的先序遍历和中序遍历如下:先序遍历:EFHIGJK 中序遍历:HFIEJKG请画出这个二叉树:
首先通过先序遍历找到根节点然后在中序遍历里就可以找出对应的左子树右子树序列,再在根节点里画出相应的左子树和右子树序列,这样下一个子树的根节点也就找到了
这其实如果对二叉树的遍历比较了解的话很简单,这里提供两种写法
typedef int DataType;
typedef struct BinaryTree
{
DataType x;
struct BinaryTree* _left;
struct BinaryTree* _right;
}BT;
//二叉树节点个数
void BinaryTreeSize1(BT* root, int* x) //使用输出型参数,但要注意使用指针
{
if (root == NULL)
return;
else
*x+=1;
BinaryTreeSize1(root->_left, x);
BinaryTreeSize1(root->_right, x);
}
int BinaryTreeSize2(BT* root) //直接递归的方法
{
if (root == NULL)
return 0;
return BinaryTreeSize2(root->_left) + BinaryTreeSize2(root->_right) + 1;
}
这里的思路是把叶节点的个数分为:左子树的叶节点个数+右子树的叶节点的个数
typedef int DataType;
typedef struct BinaryTree
{
DataType x;
struct BinaryTree* _left;
struct BinaryTree* _right;
}BT;
//二叉树叶子节点的个数
int BinaryTreeLeafSize(BT* root)
{
if (root == NULL)
return 0;
if (root->_left == NULL&&root->_right==NULL)
return 1;
return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right);
}
本题思路:
把 第K层节点个数= 左子树第K层个数 + 右子树第K层的个数
typedef int DataType;
typedef struct BinaryTree
{
DataType x;
struct BinaryTree* _left;
struct BinaryTree* _right;
}BT;
//二叉树第K层节点个数
int BinaryTreeLevelKSize(BT* root, int k)
{
if (root == NULL)
return 0;
else
{
if (k == 1)
return 1;
else
return BinaryTreeLevelKSize(root->_left, k-1) + BinaryTreeLevelKSize(root->_right, k-1);
}
}
本题思路:
把 二叉树的高度 = 左子树和右子树高度的最大值 +1
typedef int DataType;
typedef struct BinaryTree
{
DataType x;
struct BinaryTree* _left;
struct BinaryTree* _right;
}BT;
//二叉树的深度
int Max(int x, int y)
{
if (x > y)
return x;
else
return y;
}
int BinaryTreeHeight(BT* root)
{
if (root == NULL) //递归二叉树的终止条件
return 0;
return Max(BinaryTreeHeight(root->_left), BinaryTreeHeight(root->_right)) + 1;
}
本题思路:
一直递归直到根节点为NULL,此时返回NULL表示没有找到
然后先在左子树递归,如果在左子树上找到了就直接返回,如果没有找到就返回右子树递归的结果
typedef int DataType;
typedef struct BinaryTree
{
DataType x;
struct BinaryTree* _left;
struct BinaryTree* _right;
}BT;
BT* BinaryTreeFind(BT* root, DataType x)
{
if (root == NULL) //递归到root==NULL还没有找到就说明这棵树上没有
return NULL;
else
{
if (root->x == x)
return root;
else
{
BT* left = BinaryTreeFind(root->_left, x);
if (left) //对左子树返回的值进行判断,如果不为空那么该值在左子树上
return left;
return BinaryTreeFind(root->_right, x);//走到这说明左子树上没有,那么一定在右子树上或者就根本没有
}
}
}
如果是一棵完全二叉树,用上面层序递归的思想进行入 队列 和出 队列 的操作,当队列里面出现第一个NULL指针时,后面必须全部是NULL指针直至队列为空。 根据这个条件,我们还是按照原来的循环写法,只不过在前面加一个判断条件,让其在第一个NULL时停下来,接下来再写一个循环判断后面是否有非空的指针。
typedef int DataType;
typedef struct BinaryTree
{
DataType x;
struct BinaryTree* _left;
struct BinaryTree* _right;
}BT;
//判断二叉树是不是完全二叉树
bool BinaryTreeComplete(BT* ps)
{
int ret = 1;
Queue p;
QueueInit(&p);
if (ps != NULL)
{
QueuePush(&p, ps);
}
while (!QueueEmpty(&p))
{
BT* front = QueueFront(&p);
QueuePop(&p);
if (front == NULL)
break;
QueuePush(&p, front->_left);
QueuePush(&p, front->_right);
}
while (!QueueEmpty(&p))
{
BT* front = QueueFront(&p);
QueuePop(&p);
if (front != NULL)
return false;
}
QueueDestroy(&p);
return true;
}
牛客网原题
这题的难点在于如何将线序遍历的数组转换成二叉树
这其实和线序遍历是一样的 ,按照“根 左 右”的顺序创建节点就可以了,当遇到’#'时,左子树就建立完成了,接着要创建右子树(右子树还是按上面的步骤建立)
#include
#include
typedef char DataType;
typedef struct BinaryTree
{
DataType x;
struct BinaryTree* _left;
struct BinaryTree* _right;
}BT;
BT* CreatTree(char* s, int* i) //这里传地址是因为 要保证递归再回溯的过程中,s[i]不会倒退
{
if(s[*i]!='\0')
{
BT *p=(BT *)malloc(sizeof(BT));
if(s[*i]!='#')
{
p->x=s[*i];
*i+=1;
p->_left=CreatTree(s, i);
*i+=1;
p->_right=CreatTree(s, i);
}
else{
return NULL;
}
return p;
}
return NULL;
}
void Inorder(BT* root)
{
if (root != NULL)
{
Inorder(root->_left);
}
else
{
return;
}
printf("%c ", root->x);
Inorder(root->_right);
}
int main()
{
char s[100];
scanf("%s", s);
BT *ps;
int i = 0;
ps=CreatTree(s,&i );
Inorder(ps);
return 0;
}
Leetcode-单值二叉树
本题思路:
如果能判断到root为NULL就说明该子树为单值二叉树,做这种题一般 一个最终终止条件 和 若干个题目条件(一般判断的都是不满足的情况,这样到最后的终止条件就一定满足情况)
这里的题目条件就是在左右节点都存在的情况下,根节点与左右节点的值相等。
最终二叉树是否为单值二叉树 = 左子树是否为单值二叉树 && 左子树是否为单值二叉树
bool isUnivalTree(struct TreeNode* root){
if(root==NULL) //终止条件
return true;
if(root->right&&root->val!=root->right->val) //题目条件
return false;
if(root->left&&root->val!=root->left->val) //题目条件
return false;
return isUnivalTree(root->left)&&isUnivalTree(root->right); //左子树和右子树是否为单值二叉树是 与 的关系
}
leetcode-原题
思路:
两颗树的节点 有三种情况:
最后 如果根节点是相同的 ,那么如果他的左子树和右子树也是相同的,整个树也就是相同的
所以最后的条件是&&
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);
}
leetcode-对称二叉树
思路:
这里可以用上面的函数,但注意因为对称二叉树,所以在递归时候参数要稍微改一下
如果根节点存在 且 左子树与右子树对称 则返回True 否则就返回false
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->right)&&isSameTree(p->right,q->left);
}
bool isSymmetric(struct TreeNode* root){
if(root&&isSameTree(root->left,root->right))
return true;
return false;
}
leetcode 原题
思路:
这里可以用上面的函数,
终止条件: root为空也就是找到根节点为空时,还没找到相同的子树,所以要返回false
题目条件:
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 isSameTree(root,subRoot);
}
return isSubtree(root->left,subRoot) || isSubtree(root->right ,subRoot);
}
后续还会更新,博客有问题的地方欢迎指正!