typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:
BTNode* BuyNode(BTDataType x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL)
{
perror("malloc fail");
return NULL;
}
node->data = x;
node->left = NULL;
node->right = NULL;
return node;
}malloc 函数为新节点分配内存,其大小为 BTNode 结构体的大小malloc 返回 NULL,表示内存分配失败,使用 perror 函数输出错误信息,并返回 NULLx 赋值给新节点的 data 成员left 和 right 都初始化为 NULL,表示该节点暂时没有左右子节点在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。
BTNode* CreatTree()
{
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;
}由于现在对二叉树结构掌握还不够深入,为了降低学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式

void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%d ", root->data);
PreOrder(root->left);
PreOrder(root->right);
}前序遍历依据根、左子树、右子树的顺序,前序遍历又叫做深度优先遍历(DFS)

其本质上是一个有限递归的过程,当左节点递归到最后一个叶节点时,其子节点为 NULL ,向下递归就结束,然后开始回退遍历右节点
🔥值得注意的是: root == NULL 的 if 条件句既是为了表示空树的情况,也是为了结束向下递归的过程
💻测试结果:

void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}中序遍历依据左子树、根、右子树的顺序
其余操作和前序遍历一致
💻测试结果:

void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->data);
}后序遍历依据左子树、右子树、根的顺序
其余操作和前序遍历一致
💻测试结果:

void TreeSize(BTNode* root, int* psize)
{
if (root == NULL)
{
return;
}
++(*psize);
TreeSize(root->left, psize);
TreeSize(root->right, psize);
}获取节点个数首先想到的肯定是遍历二叉树,如上代码所示的方法,用一个移动指针遍历,每到一个节点就 ++ ,这固然可行,但是有更直观简洁的方法
⌨️优化代码:
int TreeSize(BTNode* root)
{
return root == NULL ? 0 :
TreeSize(root->left)
+ TreeSize(root->right)
+ 1;
}这种模式就像学校里的人数统计一样,假设想要统计全校寝室人数
这是一种清晰的统计流程,二叉树结点个数就是从最底下逐渐往上加和进行统计
int TreeHeight(BTNode* root)
{
if (root == NULL)
return 0;
return TreeHeight(root->left) > TreeHeight(root->right)
? TreeHeight(root->left) + 1 : TreeHeight(root->right) + 1;
}二叉树高度获取的思想和二叉树结点个数是一致的,高度取决于左子树和右子树路径较长的那条,直接比较然后递归取值即可
但是这段代码有个问题,比较时需要递归一次,比较完并没有保存每个节点高度的结果,获取结果的时候又要再递归一次,导致代码效率减慢
⌨️优化代码:
int TreeHeight(BTNode* root)
{
if (root == NULL)
return 0;
int leftHeight = TreeHeight(root->left);
int rightHeight = TreeHeight(root->right);
return leftHeight > rightHeight
? leftHeight + 1
: rightHeight + 1;
}所以每次向上回退时,保存每个节点的高度结果即可
int TreeKLevel(BTNode* root, int k)
{
if (root == NULL)
return 0;
if (k == 1)
return 1;
int leftK = TreeKLevel(root->left, k - 1);
int rightK = TreeKLevel(root->right, k - 1);
return leftK + rightK;
}还是利用递归的思想,假设要获取第三层的节点个数,那么需要向上递归两次到达根节点,所以是 k - 1 次
从下往上数层数
1 层,对于根节点的左子节点和右子节点,它们处于第 2 层,距离第 3 层还有 1 层,所以递归调用 TreeKLevel(root->left, 2) 和 TreeKLevel(root->right, 2)
2 层的节点时,对于它们的子节点(即第 3 层的节点),再次递归调用时传入 k - 1 即 1,此时满足 k == 1 的条件,返回 1 表示找到了一个第 3 层的节点
k - 1 作为新的层数参数,最终可以准确计算出二叉树中第 k 层的节点总数
BTNode* TreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->data == x)
return root;
BTNode* lret = TreeFind(root->left, x);
if (lret)
return lret;
BTNode* rret = TreeFind(root->right, x);
if (rret)
return rret;
return NULL;
}if 条件都不满足,说明当前节点不是目标节点,需要继续在其左子树中查找。递归调用 TreeFind(root->left, x) 来查找左子树中是否存在值为 x 的节点,并将返回结果存储在 lret 中。如果 lret 不为 NULL,说明在左子树中找到了目标节点,直接返回 lretlret 为 NULL),则继续在右子树中查找。递归调用 TreeFind(root->right, x) 来查找右子树中是否存在值为 x 的节点,并将返回结果存储在 rret 中。如果 rret 不为 NULL,说明在右子树中找到了目标节点,直接返回 rretx 的节点,说明整个二叉树中不存在该节点,函数最终返回 NULLvoid LevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
while (QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
printf("%d ", front->data);
if (front->left)
QueuePush(&q, front->left);
if (front->right)
QueuePush(&q, front->right);
}
QueueDestroy(&q);
}层序遍历就是从上到下,从左到右进行遍历,层序遍历也叫作广度优先遍历(BFS)

这里需要利用队列的特性来进行,出上一层,带入下一层(每出一个节点,就插入该节点的子节点)引入队列的头文件和源文件
🔥值得注意的是: BTNode* front = QueueFront(&q) 保存了二叉树的节点作为队列的头节点,释放时并不会影响到二叉树本身,而是释放队列头节点

bool TreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
while (QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL)
break;
else
{
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
}
while (QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front)
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true;
}我们要知道一个特性:完全二叉树的非空节点一定是连续的

第一个循环是层序遍历二叉树,直到遇到第一个空就停下来,第二个循环是检查队列中剩余的节点是否都为空,继续遍历队列中剩余的节点,如果遇到非空节点,说明该二叉树不是完全二叉树,返回 false;如果队列中剩余的节点都为空,说明该二叉树是完全二叉树,返回 true
void TreeDestroy(BTNode* root)
{
if (root == NULL)
return;
TreeDestroy(root->left);
TreeDestroy(root->right);
free(root);
root == NULL;
}