二叉树详解

目录

一、二叉树的实现

        1.1 二叉树的前序遍历

        1.2 二叉树的中序遍历 

        1.3 二叉树的后续遍历

        1.4 二叉树的节点个数

         1.5 二叉树叶子节点个数

        1.6  二叉树查找值为x的节点

        1.7 二叉树第k层节点个数

        1.8 二叉树的高度

        1.9 二叉树的销毁

 二、二叉树的层序遍历

三、判断二叉树是否是完全二叉树

四、代码展示

        BTNode.h

        BTNode.c

        Queue.h 

        Queue.c 

最后


一、二叉树的实现

        前面我们已经说过了二叉树的定义与概念,现在我们讨论的是如何实现二叉树。

         在二叉树中我们不仅无法确定其具体的孩子数,也无法确定其具体节点数。那该如何实现,那不就是无从下手吗?非也,咱们想不到,自有人会想到,这里,我们实现的思路为:左孩子、右兄弟表示法。即:根左边指向它的左孩子,右边指向它左孩子的兄弟也就是右孩子,这样不就表示出来了。可借助以下代码理解:

typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

        运用此方法,我们可较为方便的表示出各个节点之间的关系。我们现在能用到这样的方法就是因为:我们是站在巨人的肩膀上。

        了解了二叉树的大逻辑后,我们现在要开始研究二叉树的细枝末节了。

        1.1 二叉树的前序遍历

                我们在用代码实现前,我们要先明白,什么是遍历?什么是前序遍历?

                所谓遍历(Traversal)是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。访问结点所做的操作依赖于具体的应用问 题。 遍历是二叉树上最重要的运算之一,是二叉树上进行其它运算之基础。

                所谓的前序遍历即:二叉树沿着:根,左子树,右子树的顺序进行遍历。 

                我们已明白其实现逻辑,现在开始用代码实现吧!

void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;//此处也可打印N
	}
	printf("%d", root->data);//按照前序遍历顺序:根
	BinaryTreePrevOrder(root->left);//左子数
	BinaryTreePrevOrder(root->right);//右子数
}

         

        上面帮助大家理解以下此代码。总之:记住顺序:根,左子树,右子树。 

        1.2 二叉树的中序遍历 

        在理解了前序遍历后,中序遍历也可以理解。中序遍历和前序遍历并无本质区别,只是顺序变换了一下,即:左子树,根,右子树。代码实现如下:

void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreeInOrder(root->left);
	printf("%d  ", root->data);
	BinaryTreeInOrder(root->right);//无不一样之处,只是交换了顺序
}

        1.3 二叉树的后续遍历

        后续遍历的遍历顺序为:左子树,右子树,根。其余思路还是一致。代码实现如下:

void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	printf("%d  ", root->data);
}

        1.4 二叉树的节点个数

                接下来,我们学习如何求二叉树的节点个数。

                假如,你们学校有一个校长,两个院长,四个辅导员,八个班长。校长接到通知,要求统计在校师生人数,你觉得,校长会怎么干?是哪一个小本本,一个一个统计:计算机学院多少人,软件学院多少人,还是把他的左膀右臂——两个院长叫过来。答案显然易见,肯定会摇人,院长见校长这么想,肯定也会叫导员,层层外包,最后,班长成苦命的打工人。

                统计节点个数的思路与上文类似,都是层层分封然后加上自己即可。代码实现如下:

int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

         1.5 二叉树叶子节点个数

                此问题我们可不可以运用上题的思路解决,当然可以。实现代码如下:

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);
}

                这里的实现思路只不过换成只要是叶节点就放回一。

        1.6  二叉树查找值为x的节点

                相信有一部分人看到此题,说:“简单”。然后写出以下代码:

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	return BinaryTreeFind(root->left,x);
	return BinaryTreeFind(root->right,x);
}

                这个代码乍一看,大逻辑好像是对的,其实不然,如果,左子树,能够找到,那最好了。但,右子树和部分左子树根本找不到。为啥?一个函数是不是只有一个返回值,结构体可以有多个。既然只有一个,那么注定只能放回一个,其余的哪怕是找到了也会没找到,无法得用。

                优化后代码如下:

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode*p1 = BinaryTreeFind(root->left,x);
	if (p1)
	{
		return p1;
	}
	BTNode* p2 = BinaryTreeFind(root->right,x);//分别用指针记录,找到才返回
	if (p2)
	{
		return p2;
	}
	return NULL;
}

        1.7 二叉树第k层节点个数

                求第k层高度,各位读者有没有思路。这里的难点为:如何找到该层。只要能找到,统计节点,那都会,对吧。

                这里提供一种想法,仅供参考:每一次使k--,叫k的值和一比。如果等于一,则到了要找的层,不为一就继续,没有就返回0。代码实现思路如下:

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);
}

        1.8 二叉树的高度

                我们在这里想,如何求出二叉树的高度呢?是不是可以转换成左子树和右子树谁更高的问题。谁高就加一,对吧。来先看以下代码:

int TreeHight(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return TreeHight(root->left) > TreeHight(root->right) ?
		TreeHight(root->left) + 1 : TreeHight(root->right) + 1;
}

                这代码,从逻辑和结果上来看,没啥问题。但,复杂度太高。

                

                 咱们的想法是运用递归实现,本题中的代码,没有一个记录的,本次用完,别人想用,就忘记了,就要重新调。读者可能认为是2倍的关系。其实不然,我用故事来解释一下:

                当班长把统计的人数告诉导员时候,导员说:知道了,你回去吧。走到半路,接到电话,导员说:你在来一下,我给院长汇报时忘记了。班长去汇报,回来。又去为啥:院长统计时又忘记了,又去,又回来,没完没了了,可用下图表示:

                从此图可以看出复杂度是很高的,越是底层,干的就越多。所以,我们可做以下优化:

int TreeHight(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int left = TreeHight(root->left);
	int right = TreeHight(root->right);
	return left > right ?
		left + 1 : right + 1;
}

        1.9 二叉树的销毁

                        二叉树的销毁其实传不传二级指针都行,不传的话,只需在调用后,手动置空即可。

void TreeDestory(BTNode* root)
{
	if (root == NULL)
		return;

	TreeDestory(root->left);
	TreeDestory(root->right);
	free(root);
}

 二、二叉树的层序遍历

        以上我们已经明白了二叉树的基本结构的实现。接下来,我们来思考一件事如何实现二叉树的层序遍历。

        首先,我们要说一下何为层序遍历?顾名思义,层序遍历即:一层一层的遍历每个节点即可。

        明白了我们要做的事情,那我们应该如何去完成?大家可能第一时间想到的是递归,毕竟二叉树就是递归实现的。那么?层序遍历用递归可行吗?好像有点不太可行。因为你要顺序的访问每一个节点,递归实现好像有点困难。那我们能否转变思路,这个要求的是层序遍历,那么?我们能否借助数组或链表一个一个把它们放入到该结构中。在我们学习中,符合该结构又符合先进先出的只有那一种结构了——队列

        那,我们能否借助队列实现,当然可以。实现思路如下:

        队列该数据结构特点为:先进先出。我们可以先把根节点放入,我们把它放入后这时叫他出队,出队的同时把它的左右护法放入,对每个节点都进行该操作,直到节点为空。

        这里强调一点:存放数据的类型为 struct BinaryTreeNode* 类型,这样才能存储。实现代码如下:

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front->left)
		{
			QueueFront(front->left);
		}
		if (front->right)
		{
			QueueFront(front->right);
		}
	}
}

                这时 ,读者可能会有以下问题:这里pop掉q,那front能找到它的左右护法吗?大家想一想。答案是:不影响。就好比:你们抓鲁迅管我周树人什么事?(bushi) 你们看咱们只是改变q此时的指向,二叉树的节点全放在队列里面,队列里面pop并不影响你这个二叉树的结构。原来的关系没有改变,故可以找到。

        那有人可能要问了?那它们不是指向同一块地址吗?这种思维有点不太正确。队列中存储的是树节点的指针,而不是节点本身。当从队列中弹出一个节点时,只是移除了节点指针在队列中的位置,不会影响树结构或树中的节点

三、判断二叉树是否是完全二叉树

                学习完如何遍历了,我们现在来学习如何判断二叉树是否为完全二叉树。

                我们对其有何实现思路。递归?然后用节点范围来判断?听起来似乎可行,但又不可行。为何?

                如图的二叉树就无法用此方法来判断。 现在似乎陷入了僵局,该如何处理?我们可否参考上图的结构,每一个入队列,遇到空截止。然后再继续层序遍历,遇到不为空的返回false,一直为空返回true。思路上是可行的,如果上图的二叉树再接一个节点会进队列吗?答案很明显:不会。但是,影响吗?不影响?因为我们通过以上就可以判断出它不为完全二叉树了,那最后一个进不进又区别吗?所以,我们代码实现思路不就出来了?代码实现如下:

bool BinaryTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		QueueFront(&q);
	}
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front == NULL)
		{
			break;
		}
		if (front->left)
		{
			QueueFront(front->left);
		}
		if (front->right)
		{
			QueueFront(front->right);
		}
	}
	while (!QueueEmpty(&q))
	{
		QNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front != NULL)
		{
			return false;
		}
	}
	return true;
}

四、代码展示

        BTNode.h

#pragma once
#include<stdio.h>

typedef char BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;


// 二叉树销毁
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);
//求二叉树高度
int TreeHight(BTNode* root);
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root);

        BTNode.c

#include"BTNode.h"

//二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;//此处也可打印N
	}
	printf("%d", root->data);//按照前序遍历顺序:根
	BinaryTreePrevOrder(root->left);//左子数
	BinaryTreePrevOrder(root->right);//右子数
}
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreeInOrder(root->left);
	printf("%d  ", root->data);
	BinaryTreeInOrder(root->right);//无不一样之处,只是交换了顺序
}
//二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	printf("%d  ", root->data);
}
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 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);
}
// 二叉树第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);
}
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode*p1 = BinaryTreeFind(root->left,x);
	if (p1)
	{
		return p1;
	}
	BTNode* p2 = BinaryTreeFind(root->right,x);//分别用指针记录,找到才返回
	if (p2)
	{
		return p2;
	}
	return NULL;
}
//求二叉树高度
int TreeHight(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int left = TreeHight(root->left);
	int right = TreeHight(root->right);
	return left > right ?
		left + 1 : right + 1;
}
// 二叉树销毁
void TreeDestory(BTNode* root)
{
	if (root == NULL)
		return;

	TreeDestory(root->left);
	TreeDestory(root->right);
	free(root);
}
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front->left)
		{
			QueueFront(front->left);
		}
		if (front->right)
		{
			QueueFront(front->right);
		}
	}
}
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		QueueFront(&q);
	}
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front == NULL)
		{
			break;
		}
		if (front->left)
		{
			QueueFront(front->left);
		}
		if (front->right)
		{
			QueueFront(front->right);
		}
	}
	while (!QueueEmpty(&q))
	{
		QNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front != NULL)
		{
			return false;
		}
	}
	return true;
}

        Queue.h 

#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<assert.h>

typedef struct BinaryTreeNode* QDataType;
// 链式结构:表示队列 
typedef struct QListNode
{
	struct QListNode* next;
	QDataType data;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* phead;
	QNode* ptatil;
	int size;
}Queue;

// 初始化队列 
void QueueInit(Queue* q);
// 队尾入队列 
void QueuePush(Queue* q, QDataType data);
// 队头出队列 
void QueuePop(Queue* q);
// 获取队列头部元素 
QDataType QueueFront(Queue* q);
// 获取队列队尾元素 
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数 
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool QueueEmpty(Queue* q);
// 销毁队列 
void QueueDestroy(Queue* q);

        Queue.c 

#include"Queue.h"

// 初始化队列 
void QueueInit(Queue* q)
{
	assert(q);
	q->phead = q->ptatil = NULL;
	q->size = 0;
}
// 队尾入队列 
void QueuePush(Queue* q, QDataType data)
{
	assert(q);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = data;
	newnode->next = NULL;
	if (q->phead == NULL)
	{
		q->phead = q->ptatil = newnode;
	}
	else
	{
		q->ptatil->next = newnode;
		q->ptatil = newnode;
	}
	q->size++;
}
// 队头出队列 
void QueuePop(Queue* q)
{
	assert(q);
	assert(q->size > 0);
	if (q->phead->next == NULL)
	{
		q->phead = q->ptatil = NULL;
	}
	else
	{
		QNode* tmp = q->phead->next;
		free(q->phead);
		q->phead = tmp;
	}
	q->size--;
}
// 获取队列头部元素 
QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(q->phead);
	return q->phead->data;
}
// 获取队列队尾元素 
QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(q->ptatil);
	return q->ptatil->data;
}
// 获取队列中有效元素个数 
int QueueSize(Queue* q)
{
	assert(q);
	return q->size;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool QueueEmpty(Queue* q)
{
	assert(q);
	return q->size == 0;
}
// 销毁队列 
void QueueDestroy(Queue* q)
{
	assert(q);
	QNode* tmp = q->phead;
	while (tmp)
	{
		QNode* next = tmp->next;
		free(tmp);
		tmp = next;
	}
}

最后

        以上,便是我所有说的二叉树的大部分内容了,剩下的部分会在明天我为大家准备的练习题中进行讲解。如果今天讲出来,不太利于大家的理解。希望大家能把今天讲的知识拿去练习,好好理解巩固一下,我们明天再会!

完!

  • 47
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值