二叉树是一种每个节点最多有两个子节点(左子树和右子树)的树形数据结构,具有明确的左右次序且不可随意颠倒。
为了方便表示二叉树,我们定义一个结构体,里面包含一个数据元素、一个指向左孩子的指针和一个指向右孩子的指针。
//给char一个新名字,之后都用新名字。
//以后如果二叉树中想存其他类型的值,只要修改这里的char就好
typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;//一个数据元素
struct BinaryTreeNode* left;//指向左孩子的指针
struct BinaryTreeNode* right;//指向右孩子的指针
}BTNode;
二叉树最基础的就是前序遍历、中序遍历和后序遍历三种遍历方法,这些名字其实就是根节点的前后访问顺序。
我们平时学习的二叉树基本都省去了空指针,但是其实二叉树中只有一个孩子或者没有孩子的节点,他们的左右指针是指向空的。
前序遍历就是先访问根节点,然后访问左子树,最后访问右子树。图这里只画了前序遍历的,但是弄懂前序遍历,中序和后序和它是差不多的。
//前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
if (root == NULL)//根节点为空,直接返回
return;
printf("%c ", root->data);//先访问根节点
BinaryTreePrevOrder(root->left);//再进入左子树
BinaryTreePrevOrder(root->right);//最后进右子树
}
中序遍历就是先访问左子树,然后访问根节点,最后访问右子树。
//中序遍历
void BinaryTreeInOrder(BTNode* root)
{
if (root == NULL)//根节点为空,直接返回
return;
BinaryTreePrevOrder(root->left);//先进左子树
printf("%c ", root->data);//再访问根节点
BinaryTreePrevOrder(root->right);//最后进右子树
}
后序遍历就是先访问左子树,然后访问右子树,最后访问根节点。
//后序遍历
void BinaryTreePostOrder(BTNode* root)
{
if (root == NULL)//根节点为空直接返回
return;
BinaryTreePrevOrder(root->left);//先进左子树
BinaryTreePrevOrder(root->right);//再进右子树
printf("%c ", root->data);//最后访问根节点
}
通过前序遍历创建一颗二叉树。给出数组"ABD##E#H##CF##G##",'#'表示NULL。先申请节点,只要值不为空,那么就把值赋值给节点;当值是NULL时,那么就直接返回。然后进入左子树,最后进入右子树,由于对它们的操作与对根节点的操作是一样的,所以这里直接递归就好了。
可以根据下图和上面图中画递归的方法理解代码。
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
//这里的pi其实就是调用该函数时,传来的用来表示数组下标的值,比如i
//传来i的地址,是因为函数会使用递归
//如果传来i的值,那个会出现改变形参的值不影响实参的情况,导致代码出现问题
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));//申请节点
if (node == NULL)//申请失败,报错返回
{
perror("node malloc");
}
if (a[(*pi)] == '#')//如果值为空,那么下标往后走
{
(*pi)++;
return NULL;//返回空
}
node->data = a[(*pi)++];//值不为空,直接赋值给节点
node->left = BinaryTreeCreate(a, n, pi);//递归调用进入左树
node->right = BinaryTreeCreate(a, n, pi);//递归调用进入右树
return node;
}
计算二叉树节点个数。计算一颗二叉树共有多少个节点,有两种方法:
(1)如果知道二叉树的深度h,那么可以直接用公式 N=2^h-1 计算得到(这个公式的推导过程我在“二叉树的基本知识”里有写);
(2)但是,当不知道二叉树的深度时,我们就只能想办法通过代码直接计算。对于一颗二叉树,我们可以让其中的每一颗树都统计一下它们的子树中节点的个数,加上它自己,然后返回给它上一层。那么它的上一层就知道了它的子树中节点的个数,在加上它自己,在返回给它的上一层。如此,一直到根节点统计完,就可以知道该二叉树的总的节点数目。
// 计算二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
return 0;
return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
计算二叉树叶子节点个数。叶子节点的特点是没有左子树也没有右子树,它的左指针和右指针都为NULL。所以当遇到一个左右都为空的节点时+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);
}
计算二叉树第k层节点个数。这里有个很巧妙的地方,统计从第一层开始数的第k层节点的个数,其实就是统计从第二层开始数的第k-1层的节点的个数。
//计算二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
if (root == NULL)
return 0;
if (k == 1)//当k=1时,统计个数+1
return 1;
return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
二叉树查找值为x的节点。先将要找的值与根节点比较,如果相等,那就找到了,直接返回根节点。如果不相等,就先去左子树找,记录结果,如果记录的结果不空,那就是找到了;如果为空,就是没找到。然后再去右子树找,与左子树同样的方法。如果都找完了,还没有找到,就返回NULL。
//二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)//根节点为空,直接返回NULL
return NULL;
if (root->data == x)//找到了
return root;
//去左树里找并且记录找的结果
BTNode* lfind = BinaryTreeFind(root->left, x);
if (lfind)//如果结果不为空,就是找到了,直接返回结果
return lfind;
//在左树的找结果为空,再去右树找
BTNode* rfind = BinaryTreeFind(root->right, x);
if (rfind)
return rfind;
//根节点和左右树中都没有找到,那这颗树中就没有该值,返回NULL
return NULL;
}
。二叉树的最大深度是指从根节点到最远叶子节点的最长路径上的节点数。还是递归的思想,一直往下走,直到叶子节点返回到上一层,并且加上它自己,也就是+1。分别记录左右子树的深度,并且返回大的那个。
//计算一颗二叉树的最大深度
int BinaryTreeDepth(BTNode* root)
{
if(root==NULL)//树为空,返回0
return 0;
int leftheight=BinaryTreeDepth(root->left)+1;//左子树深度
int rightheight=BinaryTreeDepth(root->right)+1;//右子树深度
return leftheight>rightheight?leftheight:rightheight;//返回较大的一个
}
二叉树的递归有点难,要多画图理解,同时思路要清晰。