思维导图:
目录
一,什么是二叉树
二叉树,听这个名字就知道这就是一种树?不是的,二叉树其实是一种树形的数据结构。
比如:
1.
.
2.
3.
4.
这四个都叫二叉树。第一个二叉树和第二个二叉树会比较特殊一点,第一个叫做满二叉树, 第二个叫做完全二叉树。像堆这种结构就是一个完全二叉树。完全二叉树的特点就是当根节点的左子树不存在时右子树一定不存在。当然,二叉树的结构还有很多。这里就不一一介绍了。
二 ,二叉树的构建
2.1二叉树的内部结构
二叉树的内部结构是如何的呢?找一个经典的二叉树剖析剖析便知道了。比如经典的满二叉树,它是这样的:
找一个根节点来看:
就知道一个根节点是有两个指向的节点的,分别是左节点与右节点。当然,每个节点内还有它存储的值。所以在搞清楚这些关系以后我们便可以用结构体来定义每个二叉树节点的结构。
代码:
typedef int BTreeType;
typedef struct BTreeNode
{
BTreeType val;//节点的值
struct BTreeNode* right;//左节点
struct BTreeNode* left;//右节点
}BTreeNode;
2.2手撕二叉树
现在我们的节点的结构已经写出来了,那我们就要来搞一棵二叉树了。比如我要搞一颗这样的二叉树:
我们该如何做呢?
首先我们要生产节点:
代码:
BTreeNode* BuyBTree(BTreeType x)
{
BTreeNode* Node = (BTreeNode*)malloc(sizeof(BTreeNode));
if (Node == NULL)
{
perror("malloc fail");
return;
}
Node->val = x;
Node->left = NULL;
Node->right = NULL;
return Node;
}
现在就来生产一下二叉树吧:
二叉树:
代码:
//创建单个节点
BTreeNode* n1 = BuyBTree(1);
BTreeNode* n2 = BuyBTree(5);
BTreeNode* n3 = BuyBTree(7);
BTreeNode* n4 = BuyBTree(8);
BTreeNode* n5 = BuyBTree(9);
BTreeNode* n6 = BuyBTree(10);
BTreeNode* n7 = BuyBTree(13);
//将节点连成树
n1->left = n2;
n2->left = n3;
n3->right = n4;
n1->right = n5;
n5->left = n6;
n5->right = n7;
现在我们就按自己的要求创建完了一棵二叉树了。
2.3求二叉树的节点个数
求节点个数,我猜大家的第一反应就是用遍历来求节点个数,就像链表一样。但是这样搞就会有点复杂。我们在学二叉树的时候一定要记住两件事:1.子问题 2.返回条件。
在说到子问题时,我们初学者就必然会往递归上面去想。这个操作也是一样的。
先写代码:
int BTreeSize(BTreeNode* root)
{
if (root == NULL)
{
return 0;
}
return BTreeSize(root->left) +
BTreeSize(root->right) + 1;
}
如果这个代码解析成图的话就是这样的:
上面红的数字就是每一次调用时的返回值。记住,递归调用的返回值是不会一下子就跑到最外面去的。它的返回值只能返回到调用它的那一层。 返回时就代表一个栈帧的销毁。
2.3计算二叉树的叶子节点
还记得吗?玩递归时要记住的两个关键点就在于:1.子问题。2.返回条件
现在我们如何知道求叶子节点的返回条件呢?是不是得知道叶子节点的特殊结构啊?
叶子节点是什么:
比如在这张图中,被红色框框框住的三个节点就是叶子节点。这三个叶子节点与其它的节点的不同之处就在于叶子节点的左右子树都是空树。所以我们就找到了递归的条件。 现在就可以写代码了。
代码:
int BTreeLearSize(BTreeNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)
{
return 1;
}
return BTreeLearSize(root->right) + BTreeLearSize(root->left);
}
2.4二叉树的高度
计算二叉树的高度这个代码的会比之前两个代码的实现会困难一点,因为求高度毫无疑问是求最高的高度。求最高的高度就会涉及到比较的问题。比如求这个二叉树的高度:
这个二叉树的高度当然就是4,求的是最高的子树的高度。自然就是:1->5->7->8
那这个代码该怎么写呢?返回条件是什么呢?先写代码。
代码:
int BTreeHeigth(BTreeNode* root)
{
if (root == NULL )
{
return 0;
}
int leftSize = BTreeHeigth(root->left);
int rightSize = BTreeHeigth(root->right);
return leftSize > rightSize ? leftSize+1: rightSize +1;
}
在这个代码中,最需要注意的一点便是:
int leftSize = BTreeHeigth(root->left); int rightSize = BTreeHeigth(root->right);
在递归中比较两个值的时候一定要记住要记得标记这两个值,这是提高递归比较效率的好方法。要是没有这一步的代码,你的代码执行的效率会非常低。
2.5计算第K层有多少个节点
还是老方法,要使用递归就必须要将递归的返回条件找到。那要找到第K层节点有多少个节点的返回条件是什么呢?
就像这个二叉树:
第一层有1个节点,第二层有2个节点,第三层有3个节点,第四层有4个节点。
如果这棵二叉树是一个NULL树很明显就返回0,这是一个众说周知的答案。当你要计算第一层的节点个数时很明显就是返回一个1(二叉树的第一层只能有一个根节点)。 现在我们找到了递归的返回条件了,于是我们就要将大问题转换成小问题:因为第一层的节点个数是固定的固定为1。所以可以将求第K层的节点个数通过递归转换为求第一层。先来写个代码。
代码:
int BTreeKsize(BTreeNode* root,int k)
{
if (root == NULL)//当根节点为NULL时就返回0
{
return 0;
}
if (k == 1)
{
return 1;
}
return BTreeKsize(root->right, k - 1) + BTreeKsize(root->left, k - 1);
}
比如在这个图中:
如果我要求第三层的节点个数,那这段代码的执行过程就是这样的:
这张图中红色的数字就是每层节点的返回值。
2.6 二叉树的打印
二叉树的打印顺序根据根节点的打印顺序来分就有三种:1.前序打印 2.中序打印 3.后续打印
前序打印:根->左->右
中序打印:左->根->右
后续打印: 左->右->根
在知道这些概念以后,我们就可以来实现一下这个打印二叉树的代码了。
代码:前序遍历
void PrevPrint(BTreeNode* root)
{
if (root == NULL)
{
printf("N->");
return;
}
printf("%d->", root->val);
BTreePrint(root->left);
BTreePrint(root->right);
}
代码:中序遍历
void InPrint(BTreeNode* root)
{
if (root == NULL)
{
printf("N->");
return;
}
InPrint(root->left);
printf("%d->", root->val);
InPrint(root->right);
}
代码:后续遍历
void PostPrint(BTreeNode* root)
{
if (root == NULL)
{
printf("N->");
return;
}
PostPrint(root->left);
PostPrint(root->right);
printf("%d->", root->val);
}
对于二叉树:
结果为:
2.6寻找二叉树的节点值
还是像之前一样,我们在写这个代码的时候需要找到这个代码的返回条件。
比如,我要在这种二叉树里面找到"8":
我们该如何寻找呢?其实很简单。先写代码,结合代码讲解。
代码:
BTreeNode* BTreeFind(BTreeNode* root, BTreeType x)
{
if (root == NULL)//NULL节点直接返回NULL
{
return NULL;
}
if (root->val == x)//如果根节点的值就是x那就返回根节点
{
return root;
}
BTreeNode* left = BTreeFind(root->left, x);//根节点既不是NULL,根节点的值也不是x那就在左节点中寻找
if (left)//左子节点的返回值不是NULL才返回左节点
{
return left;
}
BTreeNode* right = BTreeFind(root->right, x);//在右子节点中寻找
if (right)//右子节点的返回值不为NULL才返回右节点
{
return right;
}
return NULL;//左右子节点都是NULL,那就返回NULL
}
图解,比如我要找二叉树中是否有值为8的节点。
2.7二叉树的销毁
因为二叉树的节点是malloc出来的,所以我们在用完二叉树以后就要将二叉树free掉。这样就可以避免造成内存泄露的危险。但是二叉树可不是链表,我们还是得要找出二叉树节点Free的条件。这其实显而易见,当一个节点的左右子节点都是NULL节点的时候就可以将这一节点给free掉。当然这个根节点不能是NULL节点。这就要使用到后序遍历了。
代码:
void BTreeDestory(BTreeNode* root)
{
if (root == NULL)//自己是NULL直接返回
{
return ;
}
if (root->left == NULL && root->right == NULL)//之后将自己置为NULL
{
free(root);//当左右节点都是NULL时就可以free掉自己,
root = NULL;//之后将自己置为NULL
return;//返回到上一层节点
}
BTreeDestory(root->left);
BTreeDestory(root->right);
free(root);//在调用完左右节点后将自己free掉
root = NULL;//将自己置为NULL
return;//返回
}