文章目录
二叉树的实现与递归
上期说到了二叉树的一些概念和定义。还对一种特殊的完全二叉树——堆进行了实现。本片文章我们将对二叉树的一些简单的基础的功能进行实现
二叉树结构
来简单回顾一下:
二叉树分为顺序存储和链式存储。当一个树为完全二叉树的时候,使用顺序存储是十分方便的,因为可以通过下标找出对应的孩子节点和双亲节点。但是如果不是完全二叉树,那很容易造成空间的浪费。所以可以将二叉树定义成一个个节点组成的树。每个节点存储着其左右孩子(如果有)节点的地址:
typedef char BTDataType;
typedef struct BinaryTreeNode {
//树的节点
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
而之前又说到过,二叉树本身就是一个递归的结构:
因为一个树可以由根和左右子树组成,而左右子树又可以拆分为根和子树,直到拆分到空节点,即不能再拆分。这是天然的递归结构。所以对于很多功能,使用递归虽然不好理解,但是实现的代码却十分简介简单。下面让我们来看看如何使用递归来完成一些功能。
递归部分
这个部分将重点对如何使用递归来进行功能的实现,并且加深对递归的理解。
再实现功能之前,先来想想递归的重要条件:
1.一个问题可以被拆分成子问题
单看这个概念是十分抽象的。但是其实我们早就接触过递归了,就比如高中学过的数列递推公式:an+1 = an + 1,又或是著名的斐波那契数列 an = an-1 + an-2。
假设使用第一个公式,告诉a1 = 1,求a4:
过程如下:
a4 = a3 + 1
a3 = a2 + 1
a2 = a1 + 1
代入a1 = 1,求出a4 = 4
想必这个过程大伙都做过。这就是递归。我们要求一个值,需要不断代入这个公式,直到走到终止条件再返回。所以对于拆分成子问题这个条件,我们只需要明确这个问题能不能有所谓的“递推公式”。
2.需要有终止条件
就像刚刚上面提到的例子,如果没有终止条件a1 = 1,那是算不出来a4 = 4的,会无限递推下去。所以需要给出条件判断合适停止递归。
总的来说,我认为递归就是这两个要素,在后续的功能实现中,将基于这两个原则进行实现。
二叉树创建
这个部分将分两个部分来讲:
1.手搓一个
何为手搓?就是根据二叉树的形状手动造一个:
就比如要创建出这么一个二叉树用来测试,我们可以自己开辟这么多个节点,手动的将其指针指向对应位置:
BTNode* BinaryTreeBuyNode(BTDataType x) {
BTNode* NewNode = (BTNode*)malloc(sizeof(BTNode));
if (!NewNode) {
perror("BinaryTreeNode Malloc");
exit(-1);
}
NewNode->data = x;
NewNode->left = NULL;
NewNode->right = NULL;
return NewNode;
}
BTNode* CreateTree() {
BTNode* Node1 = BinaryTreeBuyNode('A');
BTNode* Node2 = BinaryTreeBuyNode('B');
BTNode* Node3 = BinaryTreeBuyNode('C');
BTNode* Node4 = BinaryTreeBuyNode('D');
BTNode* Node5 = BinaryTreeBuyNode('E');
BTNode* Node6 = BinaryTreeBuyNode('F');
BTNode* Node7 = BinaryTreeBuyNode('G');
BTNode* Node8 = BinaryTreeBuyNode('H');
Node1->left = Node2;
Node1->right = Node3;
Node2->left = Node4;
Node2->right = Node5;
Node3->left = Node6;
Node3->right = Node7;
Node5->right = Node8;
return Node1;
}
但是这样看起来就很麻烦,很繁杂。但是这样非常适合调试的时候使用。比如在Leetcode上面做题,输入某个用例答案错误的时候,就可以这样子手搓一个进行调试。
但有没有办法根据什么办法来根据输入的要求来进行创建二叉树呢?
答案是有的:
2.根据前序遍历来创建
在这里先讲一下何为前序遍历:
前序遍历的定义是:一棵树和在其内的子树,遍历的时候总是遵循着:
先遍历根节点,再遍历左子树,再遍历右子树(这是一个递归概念)
举个例子解释一下:
这就是这个简单的二叉树的前序遍历过程,对于任意一个子树都需要按照根、左、右的顺序。
所以最后前序遍历的结果序列是(遍历到空以#代替):ABD###CF###
根据根访问顺序的不同,还可以分为中序遍历,后序遍历。这一点就不再赘述了,原理和上面类似,只不过是根节点访问顺序不同罢了。
假设想要根据前序序列**ABD##E#H##CF##G##**创建一个二叉树,可以使用递归。
思路:
声名一个变量i,用作遍历这个序列。
如果不为‘#’,就创建一个节点,插入到当前的根上。然后依次创建当前根的左子树和右子树。
而后让i的位置指向下一个位置。
如果为‘#’,就直接让i指向下一个位置。
终止条件:当i的值大于数组长度时候,就退出
代码所示:
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi) {
//函数的作用:开一个节点 赋值然后返回这个节点
if(*pi >= n || a[*pi]