目录
上篇我们对二叉树的顺序存储堆进行了讲述,本文我们来看链式二叉树。
定义链式二叉树同定义链表相同,只是需要注意二叉树有两个指针,类似于双向链表,逻辑上我们将其看作一棵二叉树。下面是定义该树的结构体。
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
在创建二叉树之前,我们需要了解前序、中序、后序以及层序遍历。
学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。
下图展示先序遍历的递归结构:
非递归遍历也即层序遍历:
设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
图示:?
数据结构的实现无非是增删查改,二叉树也不例外。
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTNode* root, BTDataType* str);
// 二叉树销毁
void BinaryTreeDestory(BTNode** root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);
层序遍历
//void BinaryTreeLevelOrder(BTNode* root);
判断二叉树是否是完全二叉树
//bool BinaryTreeComplete(BTNode* root);
先序遍历创建一个二叉树,我们递归遍历每个元素,然后为其创建节点,但叶子节点没有孩子怎么办?
叶子节点没有孩子,因此当递归到叶子节点的孩子时需要返回NULL;我们需要在需要创建的元素数组中做好标记,比如下面这个代码,当我们遇到 '#' 时返回NULL结束函数,不创建节点。
通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
struct BTNode* PreorderCreate(int* a,int* i)
{
if (a[*i] == '#')
{
(*i)++;
return NULL;
}
struct BTNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
root->val = a[*i];
(*i)++;
root->left = PreorderCreate(a, i);
root->right = PreorderCreate(a, i);
return root;
}
销毁一个二叉树,我们设想一下,当销毁了树的根节点,那么我们就找不到他的孩子了。因此根节点必然是最后一个销毁的,所以我们用后序遍历来销毁二叉树。
我们递归遍历最深的节点,依次往上销毁。
// 二叉树销毁(后序遍历)
void BinaryTreeDestory(BTNode** root)
{
if (*root == NULL)
return;
BTNode* cur = *root;
BinaryTreeDestory(&(*root)->left);
BinaryTreeDestory(&(*root)->right);
free(*root);
*root = NULL;
}
树的查找,这里我们以先序遍历为例。
若该节点数据等于x,则返回该节点。否则递归其孩子,我们分别用ret1与ret2记录递归其左右孩子的返回值,然后判断返回值是否存在。根据函数可知,函数的返回只可能为NULL或者是值为x的节点。
至于我们为什么要使用ret1、ret2,那是因为如果直接用?BinaryTreeFind(root->left, x);来判断的话,我们会再次进入这个节点的递归,造成额外的栈帧负担。
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->data == x)
return root;
BTNode* ret1= BinaryTreeFind(root->left, x);
if (ret1)
return ret1;
BTNode* ret2=BinaryTreeFind(root->right, x);
if (ret2)
return ret2;
return NULL;
}
层序遍历为非递归遍历,对于树而言,非递归往往更难。
这里我们借用队列来实现树的层序遍历,关于队列实现层序遍历,我们后面再讲。
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
while (QueueEmpty(&q))
{
if (!root)
QueuePush(&q, root);
BTNode* tmp = QueueFront(&q);
QueuePop(&q);
printf("%d ", tmp->data);
if (!root->left)
QueuePush(&q, root->left);
if (root->right)
QueuePush(&q, root->right);
}
}