一.二叉树的链式结构
当我们用链式存储二叉树时,常使用的底层结构是链表,不过和单链表不一样,但和循环双向链表相似,其存在数值区域和两个结点区域,后者用来存储左右子树的地址。其次二叉树独特的结构根-左结点-右结点以及其有层次的存储导致了递归的特殊性性质。我个人认为双向循环链表难以使用递归在于缺少第二个条件。最后这篇博客将从二叉树可以递归的角度进行展开。
二.二叉树链式结构的实现
1.二叉树的创建
typedef int BTDataType;
//二叉链
typedef struct BinaryTreeNode
{
BTDataType data; // 当前结点值域
struct BinaryTreeNode* left; // 指向当前结点左孩子
struct BinaryTreeNode* right; // 指向当前结点右孩子
}BTNode;
2.动态创立新结点
//动态创立新结点
BTNode* BuyNode(BTDataType x)
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
assert(newnode);
newnode->data = x;
newnode->left = NULL;
newnode->right = NULL;
return newnode;
}
堆空间开辟的内存空间不会随着函数的结束被系统回收,这是要注意的一点。
3.创建二叉树
//创建二叉树
BTNode* GreatBTree()
{
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;
}
手动构建的二叉树将使各算法的结果更加直观,以下为手动创建的二叉树的示意图:
4.前序遍历
//前序遍历
void PrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
printf("%d ", root->data);
PrevOrder(root->left);
PrevOrder(root->right);
}
我个人理解前序中序后序之所以叫这个是因为根结点的位置不同:根节点在前,左子树,右子树的顺序叫前序;左子树,根,右子树的顺序叫中序.......回到前序的实现思路上,可以理解为以一往直前的递归,然后遵循前序的打印顺序依次遍历。就是先打印根结点后,要划归好每一个小的二叉树,然后从小二叉树开始,再到大二叉树,完成左递归,同理右递归也是如此。如下图所示:
5.中序遍历
//中序遍历
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
6.后序遍历
//后序遍历
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->data);
}
三.二叉树的其他各类问题
1.接口函数
//结点个数
int SumNode(BTNode* root);
//叶子结点个数
int LeafNode(BTNode* root);
//二叉树高度
int HeightTree(BTNode* root);
//二叉树第k层结点个数
int BTreeLeveSize(BTNode* root, int k);
//二叉树查找值为x的结点
BTNode* BTreeFine(BTNode* root, int x);
2.结点个数
//结点个数
int SumNode(BTNode* root)
{
return root == NULL ? 0 : SumNode(root->left) + SumNode(root->right) + 1;
}
这个实现思路在于 第一次进行判断这个1代表的是根结点的个数,及最大的一棵二叉树的根结点,当递归进入root->left后,其可以认为该节点以下的左右结点以及它自己组成了一棵小树,这个1就是这棵小二叉树的根结点,只不过在大二叉树里,这个小树的根结点不过是大树根结点的左结点。依次递归,结束条件在于判断其小树的根结点是否为NULL。左右结点的处理思路皆是如此。
3.叶子节点个数
//叶子结点个数
int LeafNode(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left==NULL && root->right==NULL)
{
return 1;
}
else
{
return LeafNode(root->left) + LeafNode(root->right);
}
}
这个实现思路是以递归的方式遍历数组,只需要判断左右子树是否为NULL即可,注意这个条件需要同时满足。
4.二叉树的高度
//二叉树高度
int HeightTree(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int left = HeightTree(root->left);
int right = HeightTree(root->right);
return left > right ? left + 1 : right + 1;
}
这个实现思路是在难以判断二叉树的形状下(不要想象其通为对称的),在最大二叉树的划分下,去以左右子树的最高值来确定二叉树的高度。同样是以递归的方式实现,这里的1表示了递归经过的每一层后高度加1,左右的不同加1是左右子树的区分。
5.二叉树第k层结点个数
//二叉树第k层结点个数
int BTreeLeveSize(BTNode* root, int k)
{
if (root == NULL)
{
return 0;
}
if (k == 1)
{
return 1;
}
return BTreeLeveSize(root->left, k - 1) + BTreeLeveSize(root->right, k - 1);
}
这里的实现思路是将第k层结点转化成第k-1层结点,和确定叶子结点有异曲同工之妙。
6.二叉树查找值为x的结点
//二叉树查找值为x的结点
BTNode* BTreeFine(BTNode* root, int x)
{
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
if (BTreeFine(root->left, x) == NULL)
{
return BTreeFine(root->right, x);
}
else
{
return BTreeFine(root->left, x);
}
}
这里的实现思路是以递归的方式去依次遍访二叉树的值,要遍访的方式与以孩子兄弟存储方式进行层级遍历类似。这个问题和求二叉树节点的思路类似,可以理解为从左往右的,从上往下的查找。
以上就是二叉树递归算法的实现,递归最好的方式是画图。但是个人认为最重要的是思维,就像每一次递归,程序中的1的位置将决定了递归将以何种方式实现,是应该在遍历前就+1,还是在遍历后+1,将直接影响到我们算法的正确性。
以上就是树和二叉树<3>若有不足之处,还望不惜赐教。