前言:
今天就来讲树的一种特殊结构---二叉树
当然先来给大家看一张图片
看到这棵树了吗?它从根开始,每个结点都有且仅有两个分支,这个结构就是我们的二叉树。
其实我们上次讲的堆也可以看成一棵二叉树,但是人家的本质是一个数组。
我今天所讲的二叉树就是以链式的结构来展开。
再看链式的二叉树的实现前,还是先来回顾一下二叉树的概念:
1.空树。
2.非空树:有左右两棵子树和根节点所形成。
链式二叉树的基本操作:
1.链式二叉树的结构体:
typedef int BTreeData;
struct BTreeNode
{
BTreeData data;
struct BTree* left;
struct BTree* right;
};
typedef struct BTreeNode BTreeNode;
因为我们讲的二叉树是链式的结构,那么我们的一个根节点除了要包含它本身的数据以外,还需要存储它的两个孩子(左右子树)的指针。
2.创建二叉树的结点:
BTreeNode* BuyNode(BTreeData x)
{
BTreeNode* node = (BTreeNode*)malloc(sizeof(BTreeNode));
if (node == NULL)
{
perror("malloc fail");
return NULL;
}
node->data = x;
node->left = NULL;
node->right = NULL;
return node;
}
其实普通的二叉树的基本操作也就四种:
1.前序遍历
2.中序遍历
3.后序遍历
4.层序遍历
那么我们还是先从第一个开始叭!
3.前序遍历:
前序遍历的顺序:
ex:打印整个二叉树的数据
1.先打印根的数据
2.通过根去访问左子树
3.访问左子树的时候,左子树作为根的时候,打印它的数据,再去访问它的左子树,直到它的左子树为空,再返回
4.返回之后,通过当前的根,去访问这个根的右子树。
5.如果这个右子树作为根时,先打印它的数据,再去访问它的左子树,以此类推。
如果觉得我的表述不准确或难以理解,就来看一下下面的图:
这个图是否就清晰了不少呢?
那么它的打印顺序就是: A B D E C F G
那么就开始写代码咯~
void BinaryTreePrevOrder(BTNode* root)
{
if (root == NULL)
{
return;
}
printf("%c->", root->data);
BinaryTreePrevOrder(root->left);
BinaryTreePrevOrder(root->right);
}
如果这个结点是个空结点,那么我们直接返回,当然也可以打印NULL哦,这样更好地去理解遍历的路线。由于我们的前序遍历是:根-左子树-右子树,所以我们就先打印根结点,再去访问左子树,最后去访问右子树。如此去递归。
4.中序遍历:
中序遍历的路线其实和我们的前序遍历的路线是一样的,但是我们的中序遍历的输出时是:左子树-根-右子树。
也就是说,我们要先访问到底部的左子树的左子树为空时,开始对这棵底部的左子树开始打印,一直回溯。
那么还是先来看代码
void BinaryTreeInOrder(BTNode* root)
{
if (root == NULL)
{
return;
}
BinaryTreePrevOrder(root->left);
printf("%c->", root->data);
BinaryTreePrevOrder(root->right);
}
我们会发现这段代码和上面的那段代码就是吧打印的顺序和访问左子树的顺序调换了一下,其实就是这样的,链式二叉树的遍历就是通过递归进行的。
还是看回前面的这张图,打印顺序: D B E A F C G
通过上面的两种遍历我们推出第三种遍历也是极其容易的事情了。
5.后序遍历:
我们就不多说了,直接上代码
void BinaryTreePostOrder(BTNode* root)
{
if (root == NULL)
{
return;
}
BinaryTreePrevOrder(root->left);
BinaryTreePrevOrder(root->right);
printf("%c->", root->data);
}
打印顺序:D E B F G C A
6.层序遍历:
这个遍历就是遍历完一个深度的结点再进入下一层的结点进行遍历,画个简单的图:
就是这么的简单,打印顺序就是 A B C D E F G
但是好像问题来了,不能用简单粗暴的递归完美的把这个遍历很好的解决。
我们来分析一下:
1.先将A遍历
2.再通过A去遍历它的两个孩子B和C
3.再通过B和C去遍历D E F G。
如果像上述去遍历,只能将B和C先存起来,再去按顺序访问B,C并将他们的孩子存到他们的尾部。
我们不可能用栈去存储,因为后面进来的先出去,那么我们只能去选择队列作为存储的容器咯。那么我们还得思考一件事:
我们能一次全存进来,一次性按顺序遍历,梭哈吗?
好像不能哦,我们的队列是一个链式的结构,如果我们让队列将ABCDEFG变成一条单链,他们之间的父子结构就会被破坏了,就无法找到它的左孩子和右孩子了。
那么我们只能先让根进队列,再把左孩子和右孩子入队列,之后马上把这个根给出队列。
那么思路已经有了,那么马上开干!
那么事前还是先把队列的代码给你们叭~
void queueInit(Queue* q) {
if (q == NULL)
return;
q->front = q->rear = NULL;
}
//创建一个新的节点
QNode* creatNode(QDataType data) {
QNode* node = (QNode*)malloc(sizeof(QNode));
node->data = data;
node->next = NULL;
return node;
}
// 队尾入队列
void queuePush(Queue* q, QDataType data) {
if (q == NULL)
return;
//第一次入队
if (q->front == NULL) {
q->front = q->rear = creatNode(data);
}
else {
q->rear->next = creatNode(data);
q->rear = q->rear->next;
}
}
// 队头出队列
void queuePop(Queue* q) {
if (q == NULL || q->front == NULL)
return;
QNode* next = q->front->next;
free(q->front);
q->front = next;
//若元素出队后,队列为空
if (q->front == NULL)
q->rear = NULL;
}
// 获取队列头部元素
QDataType queueFront(Queue* q) {
return q->front->data;
}
// 获取队列队尾元素
QDataType queueBack(Queue* q) {
return q->rear->data;
}
// 获取队列中有效元素个数
int queueSize(Queue* q) {
if (q == NULL)
return 0;
int size = 0;
QNode* node = q->front;
while (node) {
size++;
node = node->next;
}
return size;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int queueEmpty(Queue* q) {
if (q->front == NULL)
return 1;
return 0;
}
// 销毁队列
void queueDestroy(Queue* q) {
QNode* cur = q->front;
while (cur) {
QNode* next = cur->next;
free(cur);
cur = next;
}
q->front = q->rear = NULL;
}
那么队列具备,只欠完成层序遍历的代码啦~
void BinaryTreeLevelOrder(BTNode* root)
{
Queue q;
queueInit(&q);
if (root)
queuePush(&q, root);
while (!queueEmpty(&q)) {
//获取队头元素
BTNode* node = queueFront(&q);
printf("%c ", node->data);
//出队
queuePop(&q);
//保存队头元素的左右孩子节点
if (node->left)
queuePush(&q, node->left);
if (node->right)
queuePush(&q, node->right);
}
queueDestroy(&q);
printf("\n");
}
7.二叉树的销毁:
void BinaryTreeDestory(BTNode** root)
{
if (*root)
{
BinaryTreeDestory(&((*root)->left));
BinaryTreeDestory(&((*root)->right));
free(*root);
}
*root = NULL;
}
养成良好习惯,一定要free掉哦!
那么二叉树就结束啦~下次就是做OJ题了~
不会有人认为已经结束了叭?
下面还有一段呢~
8.二叉树结点的个数:
二叉树的结点个数不就是根本身+ 左子树的结点个数 + 右子树的结点个数吗?
那么我们只需要递归下去即可,那么我们也很容易得知,当一个根为空时,它就可以返回了,而且返回值应该是个0,空嘛~ 他就是一个0
那么直接上代码:
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int left = BinaryTreeLeafSize(root->left);
int right = BinaryTreeLeafSize(root->right);
return left + right + 1;
}
9. 二叉树叶子节点个数:
叶子结点的个数 = 左子树的叶子结点的个数 + 右子树的叶子结点的个数
那么什么时候要停下来呢?
如果这个根为空,我们就会返回0,如果这个根不为空,而且这个根的左子树与右子树都是空的,那么这个根就是一个叶子结点,我们就会返回一个1。
int BinaryTreeLeafSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)
{
return 1;
}
return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
10.二叉树第k层节点个数:
访问第k层的结点个数,其实就是通过第k-1层的数据将所有数据访问到,依次类推,所以我们要使用层序遍历去获得二叉树的第k层的结点个数。
来看代码:
int BinaryTreeLevelKSize(BTNode* root, int k)
{
if (root == NULL)
{
return 0;
}
if (k == 1)
{
return 1;
}
return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
11.二叉树查找值为x的节点:
查找这个值为x的结点,需要我们去从根节点的左子树去找,左子树没有我们要找的结点,我们就去右子树去找。如果我们找到了就返回那个root这个指针,如果到访问到底层的空,则返回NULL指针。
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
BTNode* node = BinaryTreeFind(root->left, x);
if (node == NULL)
{
node = BinaryTreeFind(root->right, x);
}
return node;
}
这次就真讲完了!