数据结构——二叉树的链式结构

一. 前置说明

 在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式

typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType _data;
	struct BinaryTreeNode* _left;
	struct BinaryTreeNode* _right;
}BTNode;
BTNode* CreatBinaryTree()
{
	BTNode* node1 = BuyNode(1);
	BTNode* node2 = BuyNode(2);
	BTNode* node3 = BuyNode(3);
	BTNode* node4 = BuyNode(4);
	BTNode* node5 = BuyNode(5);
	BTNode* node6 = BuyNode(6);

	node1->_left = node2;
	node1->_right = node4;
	node2->_left = node3;
	node4->_left = node5;
	node4->_right = node6;
	return node1;
}

 注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解。
 再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:

1.空树
2.非空:根节点,根节点的左子树、根节点的右子树组成的。

在这里插入图片描述

 从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的

二. 二叉树的遍历

1. 前序、中序以及后序遍历

 学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础

在这里插入图片描述

 按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

  1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。(根 左子树 右子树)
  2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。(左子树 根 右子树)
  3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。(左子树 右子树 根)

 由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历

// 二叉树前序遍历
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%d ", root->val);
	PreOrder(root->left);
	PreOrder(root->right);
}

// 二叉树中序遍历
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PreOrder(root->left);
	printf("%d ", root->val);
	PreOrder(root->right);
}

// 二叉树后序遍历
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PreOrder(root->left);
	PreOrder(root->right);
	printf("%d ", root->val);
}

 前序遍历递归图解:
在这里插入图片描述
在这里插入图片描述

  • 前序遍历结果:1 2 3 4 5 6
  • 中序遍历结果:3 2 1 5 4 6
  • 后序遍历结果:3 1 5 6 4 1

2. 层序遍历

 层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历
在这里插入图片描述

  • 若要进行层序遍历,则要用到队列的思想
  • 先将根节点 (root) 导入队列中,再获取队列中的头部节点(root)
  • 然后将该节点中的数据打印
  • 再判断该节点的左子树是否为空,若不为空,则将左子树导入队列
  • 再判断该节点的右子树是否为空,若不为空,则将右子树导入队列
  • 最后将根节点(root)导出队列,重复以上操作,直到队列为空
// 层序遍历
void LevelOrder(BTNode* root)
{
	Que ps;
	QueInit(&ps);
	if (root)
	{
		QuePush(&ps, root);
	}
	while (!QueEmpty(&ps))
	{
		BTNode* Front = QueFront(&ps);
		printf("%d ", Front->val);
		if (Front->left)
		{
			QuePush(&ps, Front->left);
		}
		if (Front->right)
		{
			QuePush(&ps, Front->right);
		}
		QuePop(&ps);
	}
	QueDestroy(&ps);
}

练习:请写出下面的前序/中序/后序/层序遍历
在这里插入图片描述

  1. 前序遍历结果:A B D E H C F G
  2. 中序遍历结果:D B E H A F C G
  3. 后序遍历结果:D H E B F G C A
  4. 层序遍历结果:A B C D E F G H

3. 节点个数以及高度等

 为了方便代码的测试,先手动构建节点

typedef int BTDataType;
typedef struct BinaryTreeList
{
	struct BinaryTreeList* left;
	struct BinaryTreeList* right;
	BTDataType 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;

	return newnode;
}
int main()
{
	// 手动构建
	BTNode* node1 = BuyNode(1);
	BTNode* node2 = BuyNode(2);
	BTNode* node3 = BuyNode(3);
	BTNode* node4 = BuyNode(4);
	BTNode* node5 = BuyNode(5);

	node1->left = node2;
	node1->right = node5;
	node2->left = node3;
	node2->left = node4;

	return 0;
}

在这里插入图片描述

 二叉树节点的个数:

  • 这里采用后序遍历,先判断左子树是否为空树,再判断右子树是否为空树
  • 因为空树并不算一个节点,所以这里遇到空树就返回 0
  • 当左右子树都判断完,最后加上根节点(+1)
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
	return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

 二叉树叶子节点个数:

  • 首先,叶子节点是指不存在孩子的节点
  • 若一棵树没有任何节点,则为空树,空树的叶子节点个数为 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);
}

 二叉树第k层节点个数:

  • 在二叉树中,若要直接定位到树的第K层,去计算节点个数,这是不容易控制的
  • 所以,要利用相对位置的思想来解决问题,从第一层到第K层,要走K层,若从第二层到第K层,则要走K-1层
  • 所以从根节点往左右子树进行遍历,每要向下一层,都要对K-1,直到K=1时,已经来到了第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);
}

4. 二叉树基础oj练习

  1. 单值二叉树
  2. 检查两颗树是否相同
  3. 对称二叉树
  4. 二叉树的前序遍历
  5. 二叉树中序遍历
  6. 二叉树的后序遍历
  7. 另一颗树的子树
  8. 二叉树的最大深度
  9. 翻转二叉树

 单值二叉树:

  • 若二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树
  • 对于空树,它没有节点,它也是单值二叉树
  • 从根节点开始向左右子树进行遍历,若左子树存在,且值相等,则返回 true
  • 右子树存在,且值相等,则返回 true
  • 左右子树同时为真,则该二叉树才为单值二叉树
// 单值二叉树
bool isUnivalTree(struct TreeNode* root){
    if(root == NULL)
    {
        return true;
    }
    if(root -> left && root -> left -> val != root -> val )
    {
         return false;
    }
    else if(root -> right && root ->right -> val != root -> val)
    {
         return false;
    }
    return isUnivalTree(root->left) && isUnivalTree(root->right);
}

 检查两颗树是否相同:

  • 从两棵树的树根开始判断,若两棵树同时为空树,则它们相等
  • 若一个为空,一个不为空,则它们不相等
  • 若两棵树都存在,但根的值不相等,则它们不相等
  • 判断完根后,递归判断根的左右子树,若左右子树都相等,则这两棵树是相同的
// 检查两颗树是否相同
bool isSameTree(struct TreeNode* p, struct TreeNode* q){
    if(p==NULL&&q==NULL)
    {
        return true;
    }
    if(p==NULL||q==NULL)
    {
        return false;
    }
    if(p->val!=q->val)
    {
        return false;
    }
    return isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);
}

 对称二叉树:

  • 判断对称二叉树的思想是与单值二叉树相同的
  • 首先,从根节点(A)的左右子树(B,C)进行判断,若左右子树都为空,则该树是对称二叉树
  • 若一个为空,一个不为空,则该树一定不对称,此时判断来判断左右子树的值是否相等
  • 若相等,则进行递归,继续判断左子树(B)的左子树(D)与右子树©的右子树(G)是否相等
  • 若相等,再判断左子树(B)的右子树(E)与右子树©的左子树(F)是否相同,若相等,则返回 true
  • 继续重复以上递归调用,直到判断出该树是否是对称二叉树
    在这里插入图片描述
// 对称二叉树
bool Check(struct TreeNode* p,struct TreeNode* q)
{
    if(p==NULL&&q==NULL)
    {
        return true;
    }
    if(p==NULL||q==NULL)
    {
        return false;
    }
    if(p->val!=q->val)
    {
        return false;
    }
    return Check(p->left,q->right)&&Check(p->right,q->left);
}
bool isSymmetric(struct TreeNode* root){
    return Check(root->left,root->right);
}

 二叉树的前序遍历:

  • 给定一棵二叉树,进行前序遍历,将遍历的结果保存到数组中,返回该数组和树的节点数量
  • 首先,新建函数TreeSize来计算树节点的个数,然后开辟一个足够存放该二叉树所有节点的数组
  • 再新建一个前序遍历的函数,进行遍历并将节点的数据依次放到数组中遍历完成后返回数组和树节点的个数
// 二叉树的前序遍历
int TreeSize(struct TreeNode* root)
{
    return root==NULL ? 0 : TreeSize(root->left)+TreeSize(root->right)+1;
}
void preorder(struct TreeNode* root,int* a,int* i)
{
    if(root==NULL)
    {
        return;
    }
    a[(*i)++]=root->val;
    preorder(root->left,a,i);
    preorder(root->right,a,i);
}
int* preorderTraversal(struct TreeNode* root, int* returnSize){
    int n=TreeSize(root);
    int* a=(int*)malloc(sizeof(int)*n);
    if(a==NULL)
    {
        perror("malloc fail");
        exit(1);
    }
    int i=0;
    preorder(root,a,&i);
    * returnSize=n;
    return a;
}

 二叉树中序遍历:

// 二叉树中序遍历
int TreeSize(struct TreeNode* root)
{
    return root==NULL ? 0 : TreeSize(root->left)+TreeSize(root->right)+1;
}
void inorder(struct TreeNode* root,int* a,int* i)
{
    if(root==NULL)
    {
        return;
    }
    inorder(root->left,a,i);
    a[(*i)++]=root->val;
    inorder(root->right,a,i);
}
int* inorderTraversal(struct TreeNode* root, int* returnSize){
    int n=TreeSize(root);
    int* a=(int*)malloc(sizeof(int)*n);
    if(a==NULL)
    {
        perror("malloc fail");
        exit(1);
    }
    int i=0;
    inorder(root,a,&i);
    * returnSize=n;
    return a;
}

 二叉树的后序遍历:

// 二叉树的后序遍历
int TreeSize(struct TreeNode* root)
{
    return root==NULL ? 0 : TreeSize(root->left)+TreeSize(root->right)+1;
}
void postorder(struct TreeNode* root,int* a,int* i)
{
    if(root==NULL)
    {
        return;
    }
    postorder(root->left,a,i);
    postorder(root->right,a,i);
    a[(*i)++]=root->val;
}
int* postorderTraversal(struct TreeNode* root, int* returnSize){
    int n=TreeSize(root);
    int* a=(int*)malloc(sizeof(int)*n);
    if(a==NULL)
    {
        perror("malloc fail");
        exit(1);
    }
    int i=0;
    postorder(root,a,&i);
    * returnSize=n;
    return a;
}

 另一颗树的子树:

  • 首先,判断两棵树的根(root,subRoot)是否相等
  • 若相等,则调用isSameTree函数来判断,这两棵树是否相等,
  • 若不相等,则进行递归,将根的左右子树(root->left,root->right)与另一棵树的根(subRoot)比较是否相等,若相等,则继续调用isSameTree函数
  • 重复以上操作,直到判断出结果
    在这里插入图片描述
// 另一颗树的子树
bool isSameTree(struct TreeNode* p, struct TreeNode* q){
    if(p==NULL&&q==NULL)
    {
        return true;
    }
    if(p==NULL||q==NULL)
    {
        return false;
    }
    if(p->val!=q->val)
    {
        return false;
    }
    return isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);
}

bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
    if(root==NULL)
    {
        return false;
    }
    if(root->val == subRoot->val)
    {
        if(isSameTree(root,subRoot))
        {
            return true;
        }
    }
    return isSubtree(root->left,subRoot)||isSubtree(root->right,subRoot);
}

 二叉树的最大深度:

  • 按图讲解,首先,判断根节点为不为空,若不为空,则进行左子树(9)的递归,递归到空树时,返回 0
  • 左子树递归完,递归右子树,若遇空树,也返回 0
  • 此时比较若是 left > right,则 left + 1,若是 right > left,则 right + 1,左子树递归返回 1,开始递归右子树(20)
  • 多次递归返回后,最后得出树的深度
    在这里插入图片描述
// 二叉树的最大深度
int maxDepth(struct TreeNode* root){
    if(root == NULL)
    {
        return 0;
    }
    int left =  maxDepth(root -> left);
    int right = maxDepth(root -> right);
    return left>right ? left+1 : right+1;
}

 翻转二叉树:

  • 按图讲解,首先,判断根(4)是否为空,若为空,则不需要翻转
  • 若不为空,创建一个struct TreeNode*类型的临时变量来保存根(4)的左子树(2)
  • 接着将根(4)的右子树(7)覆盖到根(4)的左子树(2)
  • 再将临时变量的值赋给左子树(2),然后进入左子树(2)的递归,去翻转左子树的所有孩子节点
  • 左子树递归结束后,进行右子树的递归,去翻转右子树的所有孩子节点,最后,整棵树翻转成功
    在这里插入图片描述
// 翻转二叉树
struct TreeNode* invertTree(struct TreeNode* root){
    if(root==NULL)
    {
        return NULL;
    }
    struct TreeNode* tmp=NULL;
    tmp=root->left;
    root->left=root->right;
    root->right=tmp;
    invertTree(root->left);
    invertTree(root->right);
    return root;
}

5. 二叉树的创建和销毁

二叉树的构建及遍历

  • 先创建字符数组 str,用来存储输入的字符串,‘#’ 表示空树
  • 然后创建CreatTree函数来构建二叉树,按先序遍历的方式(根 左子树 右子树)将字符数组中的数据依次输入到二叉树中
  • 再对该二叉树进行中序遍历(左子树 根 右子树)
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
// 再对二叉树进行中序遍历
#include <stdio.h>
typedef struct BinaryTree
{
    int val;
    struct BinaryTree* left;
    struct BinaryTree* right;
}BTNode;
BTNode* CreatTree(char* str,int* pi)
{
    if(str[*pi]=='#')
    {
        (*pi)++;
        return NULL;
    }
    BTNode* new=(BTNode*)malloc(sizeof(BTNode));
    new->val=str[*pi];
    (*pi)++;
    new->left=CreatTree(str,pi);
    new->right=CreatTree(str,pi);
    return new;
}
void Inorder(BTNode* root)
{
    if(root==NULL)
    {
        return;
    }
    Inorder(root->left);
    printf("%c ",root->val);
    Inorder(root->right);
}
int main() {
    char str[100]={0};
    scanf("%s",str);
    int i=0;
    BTNode* root = CreatTree(str,&i);
    Inorder(root);
    return 0;
}

 二叉树的销毁:

  • 首先,进行左子树的递归遍历,当该根节点的左右子树都为空树时,意味着该节点为叶子节点,并对其释放
  • 处理完左子树,再来处理右子树,当左右子树都递归遍历完后,就对根(root)进行释放,并置空
// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{
	if ((*root) == NULL)
	{
		return;
	}
	BinaryTreeDestory(&(*root)->left);
	BinaryTreeDestory(&(*root)->right);
	free(*root);
	*root=NULL;
}

判断二叉树是不是完全二叉树

完全二叉树的定义:若二叉树的深度为 h,除第 h 层外,其它各层的结点数都达到最大个数,第 h 层所有的叶子结点都连续集中在最左边,这就是完全二叉树

  • 首先,这里要用到层序遍历的思想实现,将根先导入队列中,再取队列的首位数据进行判断
  • 若为空,则跳出循环,若不为空,就将该根的左右子树也导入队列,然后取出队列中的首位数据进行判断
  • 重复以上操作,然后判断空节点的后面是否存在非空节点若存在非空节点,则不是完全二叉树,若不存在,则是完全二叉树
    请添加图片描述
    请添加图片描述
#include <stdbool.h>
#include<assert.h>
typedef struct TreeNode* QDataType;
typedef struct QueueNode
{
	struct Queue* next;
	QDataType data;
}QNode;
typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Que;
void QueInit(Que* ps)
{
	assert(ps);
	ps->head = ps->tail = 0;
	ps->size = 0;
}
void QuePush(Que* ps, QDataType x)
{
	assert(ps);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	if (ps->head == NULL)
	{
		ps->head = ps->tail = newnode;
	}
	else
	{
		ps->tail->next = newnode;
		ps->tail = newnode;
	}
	++ps->size;
}
void QuePop(Que* ps)
{
	assert(ps);
	assert(ps->size > 0);/// !QueEmpty(ps)
	if (ps->head->next == NULL)
	{
		free(ps->head);
		ps->head = ps->tail = NULL;
	}
	else
	{
		QNode* next = ps->head->next;
		free(ps->head);
		ps->head = next;
	}
	--ps->size;
}
bool QueEmpty(Que* ps)
{
	assert(ps);
	return ps->size == 0;// ps->head==NULL
}
QDataType QueFront(Que* ps)
{
	assert(ps);
	assert(ps->size > 0); // !QueEmpty(ps)
	return ps->head->data;
}
bool isCompleteTree(struct TreeNode* root ) {
    Que ps;
    QueInit((&ps));
    if (root)
	{
		QuePush(&ps, root);
	}
	while (!QueEmpty(&ps))
	{
		struct TreeNode* Front = QueFront(&ps);
        if(Front==NULL)
        {
            break;
        }
		QuePush(&ps, Front->left);
		QuePush(&ps, Front->right);
		QuePop(&ps);
	}
    while (!QueEmpty(&ps))
    {
		struct TreeNode* Front = QueFront(&ps);
        if(Front!=NULL)
        {
            return false;
        }
		QuePop(&ps);
    }
    return true;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值