1.1二叉树链式结构的实现
二叉树的链式结构的实现,是通过自定义类型结构定义二叉树来实现的,与链表结构相似。
1.12二叉树类型的定义
typedef int BTDataType;
typedef struct BinaryTree
{
struct BinaryTree* left;//左子树
struct BinaryTree* right;//右子树
BTDataType a;//数据
}BTNode;
一个二叉树结点需要包含存储在结点中的数据,以及指向左右子树的指针。
1.13二叉树结点的生成
BTNode* BuyNode(BTDataType x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
node->a = x;
node->left = node->right = NULL;
return node;
}
通过传递需要存储在结点中的数据,动态开辟生成一个结点,并将新生成的结点进行初始化,将所指向的左右子树置空。
2.1二叉树的遍历
二叉树的遍历分为前序、中序、后序,以及层序,其中,前中后序的遍历是使用递归的方式更加简单,这三种序列的遍历几乎说是一样的,只要理解了其中一种,其余两种则信手拈来,唯一的不足是可能对于刚刚了解二叉树或者不熟悉递归的小伙伴可能看懂遍历的代码比较困难,而层序遍历与前面三种不同,不再适合使用递归的方式来遍历二叉树,而需要借用队列来实现对它的遍历。
2.11二叉树的前、中,后序遍历
void PrevOrder(BTNode* root)//前序
{
if (root == NULL)
{
return;
}
printf("%d ", root->a);
PrevOrder(root->left);
PrevOrder(root->right);
}
void InOrder(BTNode* root)//中序
{
assert(root);
if (root == NULL)
{
return;
}
PrevOrder(root->left);
printf("%d ", root->a);
PrevOrder(root->right);
}
void PostOrder(BTNode* root)//后续
{
assert(root);
if (root == NULL)
{
return;
}
PrevOrder(root->left);
PrevOrder(root->right);
printf("%d ", root->a);
}
二叉树的前中后序列的遍历是十分相似的,都是通过递归对函数自身反复调用,达到二叉树的遍历,所以这里以二叉树的前序为例分析。二叉树的前序遍历是从根结点开始访问的,然后将在遍历左右子树,其递归则是不断将二叉树分为新的根结点与左右子树,直到无法再分,也就是空结点,所以二叉树的遍历的递归其终止条件是遇到空结点。这里如果想不到,我们也可以通过特殊情况来思考,如果第一次传递的参数本身的根节点就是空树,则我们应该怎么办,显然,我们不需要做任何处理,只需要返回结束函数,二叉树的遍历递归是层层深入的,随第一次对函数的调用,函数先打印根结点的数据,而后调用函数自身本身,先将左子树作为参数进行递归调用,此时进入左子树的函数调用,而左子树右将分为新的左子树和右子树递归调用,直到当最后一层函数调用结束,也就是调用到传递的左子树为空,此时返回到上一层的函数重新调用右子树递归,反复如此,不断深入到遇到空结点,然后函数将回到上一层的函数调用,这样就完成了对二叉树的遍历。而中序以及后序的遍历同样如此。
2.12二叉树的层序遍历
与二叉树的前中后序列的遍历有所不同,二叉树的层序遍历在不再适合使用递归的手段来实现遍历,二叉树的层序遍历是逐层访问的特点与递归式深度优先访问不合,而通常的循环又很难解决对二叉树这种结构进行逐层次的访问,而有人发现,借用队列先进先出的特点可以完美到的解决对二叉树的层序遍历。
typedef struct BinaryTreeNode* QDataType;
typedef struct QueueNode
{
QDataType data;//数据存储
struct QueueNode* next;//指向下一结点
}QNode;
typedef struct Queue
{
QNode* head;//头指针
QNode* tail;//尾指针
int size;//队列数据个数
}Que;
//初始化队列
void QInit(Que* pq);
//添加队列元素
void QPush(Que* pq, QDataType x);
//删除队列元素
void QPop(Que* pq);
//销毁队列
void QDestroy(Que* pq);
//获取队头元素
QDataType QFront(Que* pq);
//判断队列是否为空
bool QEmpty(Que* pq);
队列相关函数 :
void QInit(Que* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
bool QEmpty(Que* pq)
{
assert(pq);
return pq->size == 0;
}
void QPush(Que* pq, QDataType x)
{
assert(pq);
QNode* temp = (QNode*)malloc(sizeof(QNode));
if (temp == NULL)
{
perror("malloc");
exit(-1);
}
temp->data = x;
temp->next = NULL;
if (pq->head == NULL)//空表
{
pq->head = pq->tail = temp;
}
else
{
pq->tail->next = temp;
pq->tail = temp;//位指针后移
}
pq->size++;
}
void QPop(Que* pq)
{
assert(pq);
assert(pq->size != 0);//空队列
if (pq->head == pq->tail)//仅有一个元素的队列
{
QNode* temp = pq->head;
free(temp);
pq->head = pq->tail = NULL;
}
else
{
QNode* headNext = pq->head->next;
free(pq->head);
pq->head = headNext;
}
pq->size--;
}
void QDestroy(Que* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* curNext = cur->next;
free(cur);
cur = curNext;
}
}
QDataType QFront(Que* pq)
{
assert(pq);
assert(pq->head);
return pq->head->data;
}
层序遍历的实现
void LevelOrder(BTNode* root)
{
Que que;
QInit(&que);
if (root != NULL)
{
QPush(&que,root);
}
else
{
QDestroy(&que);
return;
}
while (!QEmpty(&que))
{
BTNode* node = QFront(&que);
printf("%d ", que.head->data->data);
if (node->left != NULL)//左子结点入队
{
QPush(&que, root->left);
}
if (root->right != NULL)//右子结点入队
{
QPush(&que, root->right);
}
QPop(&que);
}
QDestroy(&que);
}
借用队列结构,二叉树的层序遍历将变得十分简单,我们可以提前建立一个队列,其对进行初始化后,我们首先将二叉树的根结点入队列,然后通过先访问队头元素,将其左右子结点入队列,最后再将队头元素出队列,由于队列先进先出的特点,在接下来的循环中,先进入队列的左子结点中的数据将在接下来的循环先被访问,然后录入其左右子结点,而在对其左右子结点的访问前,需要对前上一层的结点访问完成,才能轮到对下一层结点的访问,这样就模拟完成了对二叉树的层序遍历的实现。
3.1二叉树相关结点个数的计算
二叉树结点的计算归根结底实际上是对二叉树的遍历,只不过是在遍历的同时加上了一些判断条件或者值的计算。
3.11二叉树总结点的个数
int TreeSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
else
{
return 1 + TreeSize(root->left) + TreeSize(root->right);
}
}
对于二叉树总结点的计算,其与二叉树的遍历是相似的,只不过是相当于在遍历的同时计算遍历二叉树结点的个数,其同样是可以通过递归实现,将二叉树结点划分为根结点和其左右子树,根节点为1,再计算其左右子树所具有的结点数,这样不断递归划分得到最终二叉树的总结点个数。
3.12二叉树的叶子结点个数
int TreeLeafSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)
{
return 1;
}
else
{
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
}
二叉树的叶子结点的计算需要我们注意的是,叶子结点的判断是没有左右子结点,也就是其左右子节点所指向的为空,这是我们计算叶子结点的关键,再此基础上,我们同样对二叉树进行遍历,找到符合条件的结点并统计。在思考此类问题时,我们应当多善于借用特殊情况来帮助我们解题,例如空树或者单个结点的数,对于空数我们直接返回结果0,对于单个结点的树我们返回结果1,剩下的情况其实就是一样的,我们将树分为了根结点和左右子树,对于根节点或者空结点的情况我们考虑完毕,现在剩下的则是计算左右子树上所包含的叶子结点。
3.13二叉树第K层结点的个数
int TreeKLevel(BTNode* root, int k)
{
assert(k > 0);
if (root == NULL)
{
return 0;
}
if (k == 1)
{
return 1;
}
else
{
return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);
}
}
二叉树第K层结点的计算相对于前面两个算是比较难的,在计算第K的结点是,我们唯一能判断遍历时到达的是二叉树第层的依据只有参数k,遍历的大致方向仍然是将二叉树分为根节点和左右子树,同样的,在没有思路的时候我们,首先需要思考的就是特殊况,它能帮助我们找到些许思路,当为空树时无论k为何值,结果都为0,当为参数k为1时,相当与我们计算的是根结点,结果毫无疑问是1,思考到这里我们也许能发现可以借用k值为1时为突破口来计算第K层节点个数,在每次遍历的同时,将k的值减去1,代表深入下一层,我们同样可以借用特殊情况来思考,当参数k为2时,递归调用下一层函数中k为1,此时对应计算结点的个数,也就是说当k为1时需要计算结点个数,其余情况不需要统计。
结语:
本期关于二叉树链式结构的相关知识到这就介绍完了,如果感觉对你有帮助的话还请点个赞支持一下!有不对或者需要改正的地方还请指正,感谢各位的观看。