目录
一、如何理解二叉树的结构
对于二叉树的结构,是递归的最典型的例子。其递归过程,就是二叉树的树枝层层展开的过程。
每棵树,都可以看成根节点 和 左右子树的结合体。
对于根节点的左右子树而言,当左右子树为空树时,也就代表着 某条支路的递归完成,此时往往需要依次return。
若返回类型是不void类型,还需要额外的一次return,返回到上一次调用此函数处,作为返回值。
递归:子问题 + 终结调节
二、构建二叉树需要的基础代码
1.自由构建
typedef int BTDateType; //int 在前
typedef struct BinaryTreeNode
{
BTDateType date;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
BTNode* BuyNode(int x) //获得树的节点:链表型
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
//如果前边已经有一次返回,则可保证此处代码一定不符合前面的 if 返回条件
//找到堆上的空间,并进行修改,因此后续的函数在使用时,才能保证date、left、right是处理好的结果
newnode->date = x;
newnode->left = newnode->right = NULL;
return newnode;
}
//建好树,返回树根
BTNode* CreatBinaryTree() //已经有buy-node函数
{
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
BTNode* node7 = BuyNode(7);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
node5->left = node7;
return node1;
}
需要构建树节点 、 定义date的数据类型、获得新节点的方法、根据自己的思路,自由构建一棵树。
三、代码
二叉树的遍历:前序 中序 后序
void PrevOrder(BTNode* root)
{
//二叉树不能轻易assert空
if (root == NULL) //结束条件
{
printf("N ");
return;
}
//此处root一定不为空
printf("%d ", root->date);
PrevOrder(root->left); //子问题 。 在物理上,就是函数栈帧不断创建和销毁,销毁之后,上一层函数,继续往下执行!
PrevOrder(root->right);
//不能写换行,否则每次递归都会打印换行
}
void InOrder(BTNode* root)
{
//二叉树不能轻易assert空
if (root == NULL) //结束条件
{
printf("N ");
return;
}
//此处root一定不为空
InOrder(root->left); //子问题
printf("%d ", root->date);
InOrder(root->right);
}
void PostOrder(BTNode* root)
{
//二叉树不能轻易assert空
if (root == NULL) //结束条件
{
printf("N ");
return; //表示本层函数栈帧销毁了
}
//此处root一定不为空
PostOrder(root->left); //子问题
PostOrder(root->right);
printf("%d ", root->date);
}
二叉树结点个数
int BTreeSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int leftsize = BTreeSize(root->left);
int rightsize = BTreeSize(root->right);
return leftsize + rightsize + 1;
}
需要注意的是
int leftsize = BTreeSize(root->left);
int rightsize = BTreeSize(root->right);
这就是不断调用递归的过程
并且返回类型是int类型,需要额外的一次返回
return leftsize + rightsize + 1;
求叶子节点的个数
int BTreeLeafSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
//此处root一定不为空
if (root->left == NULL && root->right == NULL) //不能写连等!
{
return 1;
}
return BTreeLeafSize(root->left) + BTreeLeafSize(root->right); //会在此处不断返回本层的结果,返回到上一层的栈帧里的函数调用处。
}
if (root->left == NULL && root->right == NULL) //不能写连等!
{
return 1;
}
需要注意的是,递归的不断调用过程中,就是二叉树不断分叉的过程
return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);
此段代码,可以不断使函数返回上一层
求二叉树的高度:左右子树高度的最大值
int BTreeHeight(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int left_height = BTreeHeight(root->left) + 1; //得 + 1,根节点也是高度
int right_height = BTreeHeight(root->right) + 1;
return left_height > right_height ? left_height : right_height;
}
int left_height = BTreeHeight(root->left) + 1; //得 + 1,根节点也是高度
int right_height = BTreeHeight(root->right) + 1;
应该记录返回值,防止多次递归调用。
二叉树第k层(LevelK)结点个数
//子问题:转化成左子树的第 K - 1层 和 右子树的第 K - 1层
//结束条件(所有的返回条件): K == 1 且节点不为空 或者节点为空
int BTreeLevelKSize(BTNode* root, int k)
{
assert(k != 0);
//if (!root) //不能这么写, ! 操作符 是逻辑操作符,只能对bool类型进行操作
if (root == NULL)
return 0;
if (k == 1)
return 1;
int left_k_size = BTreeLevelKSize(root->left, k - 1); //不断递归调用就是不断分叉,最后直到 子树变成空
int right_k_size = BTreeLevelKSize(root->right, k - 1);
return left_k_size + right_k_size;
}
查找元素,返回节点指针
//查找:本质是遍历 选前序查找!!!
//查找:左树找到,就返回左树,不用去查找右树
BTNode* BTreeFind(BTNode* root, BTDateType x)
{
if (root == NULL) //走到尾
return NULL;
if (root->date == x)
return root;
//root一定不为空
BTNode* left = BTreeFind(root->left, x);
if (left) //如果左树不为空(为root) //左边找到就直接返回left,之前每一层就都不去右边找了,效率更高!
return left;
BTNode* right = BTreeFind(root->right, x);
if (right)
return right;
//到此处之后,一定两者都为空!即没找到。
return NULL;
}
左树找到之后,直接返回左树的指针,不去右树查找
二叉树的层序遍历:队列实现(非递归)
首先应该实现一个队列。
对于队列的DateType类型,应该是tree节点的指针,而不是节点的date。(以便找到left和right树)
void LevelOrder(BTNode* root)
{
Q q;
QueueInit(&q);
if (root)
QueuePush(&q, root); //往队列插入根节点的指针 ,队列存储的date是指针!!!
while (!QueueEmpty(&q)) //队列不为空
{
//出数据
BTNode* front = QueueFront(&q); //front是数据,first是节点
QueuePop(&q); //只是pop掉了队列的一个指针变量,front指针仍然可以找到root节点
printf("%d ", front->date);
//出完一个数据之后,将左右孩子push到队列中。
if (front->left)
QueuePush(&q, front->left); //Push时,push的是指针类型!!! (以便父找子)
if (front->right)
QueuePush(&q, front->right);
}
putchar(10);
QueueDestroy(&q);
}
思想:
利用队列的先进先出
二叉树的父节点出队列pop时,将父节点的孩子节点带入push队列。先pop父节点,再push子树。
if (front->left)
QueuePush(&q, front->left); //Push时,push的是指针类型!!! (以便父找子)
if (front->right)
QueuePush(&q, front->right);
当将二叉树的所有有效数据都push到队列之后,此时不push空指针,只进行pop操作。
二叉树的销毁:最好用后序
void BTreeDestroy(BTNode* root)
{
if (root == NULL)
return;
//后续遍历
BTreeDestroy(root->left); //递归过程 在销毁左子树时,就会在递归中不断用到free过程
BTreeDestroy(root->right);
free(root->right); //销毁过程 无需置空
}
判断二叉树是否是完全二叉树
利用队列先进先出 及 完全二叉树数据连续(数据连续)的性质
步骤:
1.导入所有节点指针,不断出数据
2.数据出到空时,停止出数据。
3.再次出数据,当遇到非空指针时,则为false;若出完没有遇到,则为true。
4.return之前,应该先destroy。
bool BinaryTreeComplete(BTNode* root)
{
Q q;
QueueInit(&q);
if (root != NULL)
QueuePush(&q, root); //存储的是节点的指针
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q); //出数据 如果是完全二叉树,则可以出掉所有数据。
QueuePop(&q);
if (front)
{
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
else //front为空时,此时队列(若为完全二叉树)一定只剩下NULL
break;
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q); //继续出数据
QueuePop(&q);
if (front != NULL)
{
QueueDestroy(&q);
return false; //返回之前先销毁
}
}
QueueDestroy(&q);
return true;
QueueDestroy(&q);
}
需要注意的是,当第 i 层pop掉之后,队列中的元素是 第 i + 1 层的数据。
如果是完全二叉树,当第一次循环停止时,此时一定只剩下NULL
第一层循环:
首先将树的节点,导入到队列中
在导入数据时,不同于层序遍历
层序遍历不会导入空节点
if (front->left)
QueuePush(&q, front->left); //Push时,push的是指针类型!!! (以便父找子)
if (front->right)
QueuePush(&q, front->right);
但是判断是否是完全二叉树时,应该导入空节点
if (front)
{
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
else //所有数据全部弹出
break;
当所有非空数据pop掉之后,循环结束
第二层循环:
不断pop空指针,当出现非空指针时,销毁、return false。