学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在我们对于二叉树的了解还处于初级阶段,所以我们手动创建一棵简单的二叉树,以便进入二叉树操作学习,等深入了解二叉树之后,我们再来研究二叉树真正建立的方法。
#include
#include
#include
#include "Queue.h"
typedef int BTDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
BTDataType data;
}BTNode;
BTNode* BuyNode(BTDataType x)
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->data = x;
newnode->left = NULL;
newnode->right = NULL;
return newnode;
}
BTNode* CreatBinaryTree()
{
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->right = node7;
return node1;
}
注意:上述代码并不是创建二叉树的方式
再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:
1. 空树
2. 非空:根节点,根节点的左子树、根节点的右子树组成的。
从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。
学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
访问根的时候决定了其到底是什么遍历。
由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。
递归问题,我们第一个要想的是在哪里展开递归,第二个便是回退的时机,我们想要的得到的结果是什么,这导致我们要施加什么回退的条件来结束递归。回退又可以分为递归的程序结束完回退,以及符合回退条件时回退。
每一个节点都可以看成根节点,每一个节点都有其左子树和右子树。
前序遍历递归图解:(先访问根,在遍历)
//前序
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("# ");
return;
}
printf("%d ", root->data);
PreOrder(root->left);
PreOrder(root->right);
}
中序遍历:(先遍历,在访问根节点)
//中序
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("# ");
return;
}
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
后序遍历:(最后访问根)
//后序
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("# ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->data);
}
在判断当前节点不为空的时候count++
return在count++之前,所以逻辑上当当前节点为空时候,就返回了,count并不会++。
要注意的是,这里我们要定义一个全局变量count ,方便我们计数
int count = 0;
void TreeSize1(BTNode* root)
{
if (root == NULL)
{
return;
}
count++;
TreeSize1(root->left);
TreeSize1(root->right);
}
main函数里 count 要初始化,全局变量谁都能用,避免出现差错。
int main()
{
count = 0;
TreeSize1(root);
printf("TreeSize1:%d\n", count);
return 0;
}
第二种思路:
与第一种思路相似,遇到空就返回,不过使用了三目操作符,更加简单。
int TreeSize2(BTNode* root)
{
return root == NULL ? 0 :
TreeSize2(root->left) + TreeSize2(root->right) + 1;
}
叶子节点是左右节点都为空的节点。这个定义就是回退是的条件。
当 当前节点既不为空,左孩子节点,右孩子节点也都不为空时,继续开展递归,当前节点的左孩子右孩子都为空时(此时为叶子节点),或当前节点为为空,便回退。
//叶子节点数量
//递归往往是回退的时候才带值的
int TreeLeafSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)//左子树右子树都为空是叶子节点的条件
{
return 1;
}
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
要找到确定第k层节点的数量,就要先找到第k层,我们这里从第一层开始,没有第0层。
函数递归展开后其本身并不知道当前是第几层,这时候我们就需要一个函数变量来告诉函数是第几层。
求根节点的第k层就是求其左右子树的第k-1层,在把左右子树看成根节点,就是求k-2层......
依次下去当k等于1 时,当前层数的节点就是我们要求的
//k层节点
//通过控制k 来控制递归
int TreeKLevel(BTNode* root, int k)
{
assert(k >= 1);
if (root == NULL)
{
return 0;
}
if (k == 1)//通过k控制递归的层数,程序到达这里,首先要root != null 要进去就要符合条件
{
return 1;
}
return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);
}
查找x并不需要遍历全部的二叉树,当找到对应的x之后我们要停止遍历二叉树。
停止遍历二叉之后,我们还要把x值所在节点的地址给带回去。
//查找x
//找到就返回,如在左子树里面找到,直接返回不需要再去右子树里面
BTNode* TreeFind(BTNode* root, int x)
{
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
BTNode* ret1 = TreeFind(root->left, x);
if (ret1)
{
return ret1;//假如再次分支找到了,程序不会往下走,直接返回了
}
BTNode* ret2 = TreeFind(root->right, x);
if (ret2)
{
return ret2;
}
return NULL;
}
当前二叉树的深度可以转化成左子树右子树的中最高的那一颗+1。而左右子树的高度又可以转化为他们子树中最高的那一颗+1......
从最深的那一层的节点开始找出他们最深的左右子树返回给上一层。
二叉树的销毁符合后序遍历,先访问在左右子树,在销毁。free要放到遍历完右子树后,如果先free而不是先遍历的话,就会造成野指针的问题。
//销毁
void TreeDestroy(BTNode* root)
{
if (root == NULL)
{
return ;
}
TreeDestroy(root->left);
TreeDestroy(root->right);
free(root);
}