数据结构之二叉树的实现

Posted 小赵小赵福星高照~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构之二叉树的实现相关的知识,希望对你有一定的参考价值。

二叉树的实现

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链

二叉链表结构:

typedef int BTDataType
typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* left;//指向当前结点的左孩子
	struct BinaryTreeNode* right;//指向当前结点的右孩子
	BTDataType data;
}BTNode;

建立节点之间的关系

BTNode* BuyNode(BTDataType X)//建立一个新节点
{
	BTNode* Node = (BTNode*)malloc(sizeof(BTNode));
	if (Node == NULL)
	{
		perror("malloc");
		exit(1);
	}
	else
	{
		Node->data = X;
		Node->left = NULL;
		Node->right = NULL;
		return Node;
	}
	
}
BTNode* CreateBinaryTree()//建立节点之间的关系
{
	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 = node3;
	node2->left = node4;
	node2->right = node5;
	node3->left = node6;

	return node1;
}

二叉树的遍历

二叉树的遍历是指从根节点出发,按照某种次序依次访问二叉树中的所有节点,使得每个节点被访问依次且仅仅被访问一次。二叉树的遍历方法有四种,分别是:前序遍历、中序遍历、后序遍历、层序遍历

前序遍历

规则是若二叉树为空,则空操作返回,否则先访问根节点,然后前序遍历左子树,然后再前序遍历右子树。

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

代码递归图解:

中序遍历

规则是若二叉树为空,则空操作返回,否则从根节点开始(注意不是先访问根节点),然后中序遍历左子树,再访问根节点,然后再中序遍历右子树。

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

后序遍历

规则是若二叉树为空,则空操作返回,否则从根节点开始(注意不是先访问根节点),然后后序遍历左子树,再后序遍历右子树,然后再访问根节点。

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

层序遍历

规则是若二叉树为空,则空操作返回,否则从树的第一层,也就是根节点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对节点依次访问。核心思路:利用队列,先将第一层(即根)入队列,上一层出队,下一层入队

//层序遍历
//核心思路:先入第一层--根
//上一层出来,带入下一层
void BinaryTreeLevel(BTNode* root)
{
    Queue q;
    QueueInit(&q);
    if(root)
        QueuePush(&q,root);
    while(!EmptyQueue(&q))//当队列不为空时
    {
        BTNode* front = QueueFront(&q);
        QueuePop(&q);
        printf("%d ",front->data);
        if(front->left)
            QueuePush(&q,front->left);
        if(front->right)
            QueuePush(&q,front-right);
    }
    printf("\\n");
    QueueDestory(&q);
}

下面我们来看几个题:

  • 某完全二叉树按层次输出(同一层从左到右)的序列为 ABCDEFGH 。该完全二叉树的前序序列为

A、 ABDHECFG
B、 ABCDEFGH
C、 HDBEAFCG
D、 HDEBFGCA

答案解析:

因为它是完全二叉树,又已知它的层次遍历,故该二叉树为:

该前序遍历为:ABDHECFG

  • 二叉树的先序遍历和中序遍历如下:先序遍历:EFHIGJK;中序遍历:HFIEJKG.则二叉树根结点为

A、 E
B、 F
C、 G
D、 H

答案解析:

先序遍历的第一个为根节点,故根节点为E

  • 设一课二叉树的中序遍历序列:badce,后序遍历序列:bdeca,则二叉树前序遍历序列为

A、 adbce
B、 decab
C、 debac
D、 abcde

答案解析:

由后序遍历得根节点为a,由中序遍历可知b是a的左子树,dce是a的右子树,再由后序遍历中dec这个子树可知,该子树的根为c,故c为a的右孩子,再有中序dce可知d是c的左孩子,e是c的右孩子,故该二叉树为:

故前序遍历为:abcde

  • 某二叉树的后序遍历序列与中序遍历序列相同,均为 ABCDEF ,则按层次输出(同一层从左到右)的序列d为

A、 FEDCBA
B、 CBAFED
C、 DEFCBA
D、 ABCDEF

答案解析:

后序遍历和中序遍历相同,很容易可知根节点为F,由中序序列可知,该二叉树没有右子树,左子树为ABCDE,这颗树的根节点为E,他也没有右子树,接下来的每一个子树都没有右子树,故该二叉树为

故层序遍历为:FEDCBA

节点个数以及高度

二叉树结点个数

  • 方法一:遍历
// 二叉树节点个数
//1、遍历
//有缺陷,两次调用会有问题
int size = 0;
void BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	else
	{
		++size;
	}
	BinaryTreeSize(root->left);
	BinaryTreeSize(root->right);
}

int main()
{
	BTNode* root = CreateBinaryTree();
	BinaryTreeSize(root);
	printf("BinaryTreeSize:%d\\n", size);

	BinaryTreeSize(root);
	printf("BinaryTreeSize:%d\\n", size);
	return 0;
}

我们需要定义全局变量或者静态变量size来记录大小,如果在函数内部定义局部变量size的话,函数调用需要建立栈帧,每个栈帧的size都是不一样的,所有不能定义局部变量size,我们进行前序遍历,访问结点的操作为++size,最后在main函数中调用该函数。但是该方法有缺陷,在因为size是全局变量,除第一次调用后我们每调用一次这个函数,我们都需要将size置为0

有没有不定义全局变量的方法呢?当然有,请看下面:

  • 局部变量–传地址
//局部变量--传地址
void BinaryTreeSize(BTNode* root,int* psize)
{
	if (root == NULL)
	{
		return;
	}
	else
	{
		++(*psize);
	}
	BinaryTreeSize(root->left,psize);
	BinaryTreeSize(root->right,psize);
}

int main()
{
	BTNode* root = CreateBinaryTree();
	int size1 = 0;
	BinaryTreeSize(root,&size1);
	printf("BinaryTreeSize:%d\\n", size1);

	int size2 = 0;
	BinaryTreeSize(root, &size2);
	printf("BinaryTreeSize:%d\\n", size2);
	return 0;
}

我们在main函数里面定义局部变量来表示大小,将该局部变量的地址传过去,这样我们递归操作时,就是通过地址解引用操作找到该局部变量,对该局部变量进行操作

  • 分治-以大化小

//3、以大化小
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	else
	{	
		return 1+BinaryTreeSize(root->left)+ BinaryTreeSize(root->right);
		//相当于后序遍历 先算左树再算右树,最后算根
	}
}

二叉树叶子节点个数

叶子节点是什么呢?它的左右孩子均为NULL,这时就是叶子节点。也是分治的思想,当根节点为NULL时,肯定没有叶子节点,直接返回0,当它的左右孩子均为NULL时,当前节点就为叶子节点,否则就是分治的思想:求当前节点的叶子节点个数等于它的左子树的叶子节点+右子树的叶子节点。

// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	else if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	else
	{
		return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
	}
}

二叉树第k层节点个数

求二叉树第k层节点的个数,主要思想:求当前树的第k层节点个数=左子树的第k-1层的节点个数+右子树的第k-1层的节点个数

// 二叉树第k层节点个数
//求当前树的第k层 = 左子树的第k-1层+右子树的第k-1层
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;//k==1,root不为NULL则返回1,k==1时,即是求当前层的节点个数
	}
	else
	{
		return BinaryTreeLevelKSize(root->left, k - 1)
            + BinaryTreeLevelKSize(root->right, k - 1);
	}
}

二叉树的深度

求二叉树的深度:当前树的深度=max(左子树的深度,右子树的深度)+1

//二叉树的/深度高度
//当前树的深度 = max(左子树的深度和右子树的深度)+1
int BinaryTreeDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int leftDepth = BinaryTreeDepth(root->left);
	int rightDepth = BinaryTreeDepth(root->right);
	return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}

二叉树查找值为x的节点

相当于前序遍历,先在当前树查找,查找不到就在左子树查找,左子树查找不到就在右子树查找,要是还找不到就找不到了。

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode* retLeft = BinaryTreeFind(root->left, x);
	if (retLeft)
	{
		return retLeft;
	}

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

二叉树的创建和销毁

二叉树的创建

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
#include<stdio.h>
struct TreeNode
{
    char val;
    struct TreeNode* left;
    struct TreeNode* right;
};
struct TreeNode* CreateTree(char *str,int* i)
{
    if(str[*i]=='#')
    {
        (*i)++;
        return NULL;
    }
    struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->val=str[(*i)++];
    root->left=CreateTree(str,i);
    root->right=CreateTree(str,i);
    
    return root;
}
int main()
{
    char str[100];
    scanf("%s",str);
    int i=0;
    struct TreeNode* root=CreateTree(str,&i);
    return 0;
}

*通过前序遍历来创建二叉树,读取一个字符串,该字符串为该二叉树的前序遍历,然后进行创建,这里和前序遍历二叉树相似,只不过我们的访问方式发生了变化,之前是打印节点,现在是需要赋值,root->val=str[(i)++]。

二叉树销毁

// 二叉树销毁
void BinaryTreeDestory(BTNode** root)//二级指针解决野指针问题
{
    if(root==NULL)
    {
        return;
    }
    BinaryTreeDestory(root->left);
    BinaryTreeDestory(root->right);
   	free(root);
    root=NULL;
}

二叉树的销毁相当于是后序遍历,先销毁左子树,再销毁右子树,最后销毁根节点

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

判断二叉树是否完全二叉树核心思路:层序遍历时,把空也入队列。
完全二叉树:非空是连续
不是完全二叉树:非空不是连续

// 判断二叉树是否是完全二叉树
//核心思路:层序遍历时,把空也入队列。
//完全二叉树,非空是连续
//不是完全二叉树,非空不是连续
bool BinaryTreeComplete(BTNode* root)
{
    Queue q;
    QueueInit(&q);
    if(root)//先入队根节点
    {
        QueuePush(&q,root);
    }
    while(!QueueEmpty(&q))
    {
        BTNode* front = QueueFront(&q);//取队头,将它保存起来
        QueuePop(&q);//出队
        if(front==NULL)
        {
            break;//遇到空就跳出循环
        }
        //不管是不是NULL都入队
        QueuePush(&q,front->left);
        QueuePush(&q,front->right); 
    }
    // 出到空以后,队列中全是空,就是完全二叉树
	// 还有非空,就不是完全二叉树
    while(!QueueEmpty(&q))
    {
        BTNode* front = QueueFront(&q);//取队头,将它保存起来
        QueuePop(&q);//出队
        if(front)
        {
            QueueDestory(&q);
            return false;
        }
    }
    //走到这里说明队列后面是连续的空,故是完全二叉树
    QueueDestory(&q);
    return true;
}

以上是关于数据结构之二叉树的实现的主要内容,如果未能解决你的问题,请参考以下文章

二叉树之二叉搜索树的基本操作实现

数据结构实验之二叉树八:(中序后序)求二叉树的深度

《数据结构》复习之二叉树

算法系列数据结构之二叉查找树

❤️算法系列之二叉树的实现(包含前序中序后序遍历以及节点的查找和删除)❤️

剑指offer十八之二叉树的镜像