二叉树相关知识
在二叉树这一块我们需要大量用到递归的思想。递归即先递推再回归,不论是树的遍历还是树的销毁等,都需要用到递归的思想。
下面需要用到队列相关知识,先把队列要用到的函数放在这里
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <string.h>
typedef struct BinaryTreeNode* DataType;
//队列数据元素结构
typedef struct node
{
DataType info;
struct node* next;
}QueueData;
typedef struct queueRecord {
QueueData* front, * rear;
}LINKQUEUE;
typedef struct queueRecord* PLinkQueue;
PLinkQueue createEmptyQueue_link()
{
//创建一个空队列,实质:生成一个LINKQUEUE类型的结点,并给front 和 rear 成员赋值
struct queueRecord* LQueue = (struct queueRecord*)malloc(sizeof(struct queueRecord));
struct node* newnode = (struct node*)malloc(sizeof(struct node));
newnode->next = NULL;
LQueue->front = newnode;
LQueue->rear = LQueue->front;
return LQueue;
}
int isEmptyQueue_link(PLinkQueue queue)
{ //判定队列是否为空,实质: 看队列的front指针是否为空,若为空,则队列为空
if (queue->front->next == NULL)
{
return 1;
}
return 0;
}
void enQueue_link(DataType x, PLinkQueue queue)
{
//将数据元素x插入队尾。实质:生成一个struct node类型的结点,并给相应成员赋值后插入队尾
struct node* newnode = (struct node*)malloc(sizeof(struct node));
newnode->info = x;
newnode->next = queue->rear->next;
queue->rear->next = newnode;
queue->rear = newnode;
}
DataType deQueue_link(PLinkQueue Q)
{
//出队,实质: 取出Q队列的队首结点,返回该结点的数据元素,并将该结点使用enQueue_link(QueueData *p,PLinkQueue Q)插入队尾
QueueData* temp = Q->front->next;
DataType ret = temp->info;
Q->front->next = temp->next;
free(temp);
return ret;
}
用代码来实现二叉树,首先我们要对二叉树进行定义。
一颗二叉树需要有左节点和右节点,然后还有这个节点上的值。
定义二叉树
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
int val;
}BTNode;
然后我们需要对这个节点进行初始化,也就是创建这个节点
创建节点函数
BTNode* BuyNode(int x)
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->val = x;
newnode->left = NULL;
newnode->right = NULL;
}
把这个节点的值传输函数中,然后赋值给他的val,左右子节点初始化为空
二叉树的销毁函数
销毁这个二叉树,肯定要先销毁他的左右子树,然后在销毁根节点。如果先销毁根节点,那么在销毁左右子树的时候就会出现问题。我们把这个函数写成这样:
void TreeDestory(BTNode* root)
{
if (root == NULL)
{
return;
}
TreeDestory(root->left);
TreeDestory(root->right);
free(root);//最后把根节点释放(销毁)
}
前序遍历:根节点->左子树->右子树
中序遍历:左子树->根节点->右子树
后序遍历:左子树->右子树->根节点
按照这个思想,我们现在来实现树的前中后序遍历:
前序遍历
//前序遍历
void PrevOrder(BTNode* root)
{
if (root == NULL)
{
//printf("NULL ");
return;
}
printf("%d ", root->val);
PrevOrder(root->left);
PrevOrder(root->right);
}
中序遍历
//中序遍历
void InOrder(BTNode* root)
{
if (root == NULL)
{
//printf("NULL ");
return;
}
InOrder(root->left);
printf("%d ", root->val);
InOrder(root->right);
}
后序遍历
//后序遍历
void PostOrder(BTNode* root)
{
if (root == NULL)
{
//printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->val);
}
遍历完了我们可以数出这个树的节点数,以下面这棵树为例子
当我想要求这棵树的节点数的时候,我从根节点下手,则有总结点数=根节点数(1)+左子树节点数+右子树节点数,如下图:
把这个问题继续分下去,左子树也是等于该子树的根节点(1)+左子树节点数+右子树节点数。同理,右边也一样。我们把这个问题无限细化,直到他的子树为空的时候。写出这个函数长下面这样:
求树的节点数
int TreeSize(BTNode* root)
{
//如果这个节点是NULL,返回0,否则就是返回左边的加上右边的,再加上自己(1)
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
进一步,我们需要求出树的叶子结点数量(叶子结点:左右节点为空),和上面思路相似。改一下思路,叶子结点就是左右节点为空的节点,满足这个条件就返回1,为空返回0。写出这个函数:
叶子结点的数量
//叶子结点的数量
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就减一,直到k为1的时候说明走到这一层了,把这个函数写出来:
第k层的节点数
//第k层的节点数
int TreeKLevel(BTNode* root, int k)
{
if (root == NULL)
{
return 0;
}
if (k == 1)
{
return 1;
}
return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);
}
查找就是遍历
查找值为x的节点
//查找值为x的节点
BTNode* TreeFind(BTNode* root, int x)
{
if (root == NULL)
{
return NULL;
}
if (root->val == x)
{
return root;
}
BTNode* ret = NULL;
//左边找
ret = TreeFind(root->left, x);
if (ret != NULL)
return ret;
//左边找不到找右边
ret = TreeFind(root->right, x);
if (ret != NULL)
return ret;
return NULL;
}
层序遍历
我们用队列来实现,每次出一个节点就把他的子节点入进去,如下:
先把1放进去,然后在1出来的时候把他的子节点放进去,为空就不放进去。
变成下面这样,然后把2放出来,他的子节点又进去
然后3变成队头,如下图:
以此类推,直到队列为空。
//层序遍历
void LevelOrder(BTNode* root)
{
struct queueRecord* queue = createEmptyQueue_link();
if (root)
{
enQueue_link(root, queue);
}
BTNode* cur = queue->front->next->info;
//因为带了头结点,所以这里的判空是判断他的头结点的下一个是不是空
while (!isEmptyQueue_link(queue))
{
//当前指针指向队列头结点的下一个的数据域
cur = queue->front->next->info;
if (cur->left)//这个节点的左边不为空就进去
{
enQueue_link(cur->left, queue);
}
if (cur->right)//同上
{
enQueue_link(cur->right, queue);
}
DataType result = deQueue_link(queue);//获得这个节点
printf("%d ", result->val);//把这个节点的数据打印出来
}
}
判断是否完全二叉树
我们用层序遍历,这里只需要把空也放进去,然后当发现空的时候判断后面还有没有空。画个图方便理解。
很明显这个不是一颗完全二叉树,当我们查找到第一个NULL时候,发现后面还有不是NULL的节点,由此判断这不是一颗完全二叉树。把这个函数写出来:
//判断是不是完全二叉树
int TreeComplete(BTNode* root)
{
struct queueRecord* queue = createEmptyQueue_link();
if (root)
{
enQueue_link(root, queue);
}
BTNode* cur = queue->front->next;
//因为带了头结点,所以这里的判空是判断他的头结点的下一个是不是空
while (!isEmptyQueue_link(queue))
{
//当前指针指向队列头结点的下一个的数据域
cur = queue->front->next->info;
if (cur == NULL)
{
break;
}
enQueue_link(cur->left, queue);
enQueue_link(cur->right, queue);
deQueue_link(queue);//把队头出队列
}
QueueData* judge = queue->front->next;
while (judge)
{
if (judge->info)
{
return 0;
}
judge = judge->next;
}
return 1;
}
最后放上我用来测试的主函数,大家可以去试试
int main()
{
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);
BTNode* node8 = BuyNode(8);
BTNode* node9 = BuyNode(9);
node1->left = node2;
node1->right = node3;
node2->left = node4;
node2->right = node5;
node3->left = node6;
node3->right = node7;
node4->left = node8;
node5->left = node9;
PrevOrder(node1);
printf("\n");
InOrder(node1);
printf("\n");
PostOrder(node1);
printf("\n");
printf("treesize:%d\n", TreeSize(node1));
printf("Tree 1 Level:%d\n", TreeKLevel(node1, 1));
printf("Tree 2 Level:%d\n", TreeKLevel(node1, 2));
printf("Tree 3 Level:%d\n", TreeKLevel(node1, 3));
printf("Tree 4 Level:%d\n", TreeKLevel(node1, 4));
LevelOrder(node1);
printf("\nTreeComplete:%d", TreeComplete(node1));
TreeDestory(node1);
node1 = NULL;
return 0;
}
不足之处希望大家多给我提出意见,我们一起进步!