数据结构——常见的几种树(万字解析)

在数据结构的主体中,对于树是有很多分类和用法的,我们这一篇文章将从概念和定义的角度,定性的对这几种分类进行分析,在一些需要熟练掌握代码操作的地方,也会有对应的代码示例

在了解树的几个高阶的分类之前,先来了解游戏啊有关于树的概念和定义

树的定义

树是n(n>=1)个节点的有限集合T,并且满足:
①有一个被称为根节点(root)
②其余的节点可以分为m个互不相交的集合,这些集合本身也都是树,并称他们为根节点的子树。每棵子树同样拥有自己的根节点

树的ADT

树结构的基本概念

下面用一颗树进行比方:
在这里插入图片描述

节点

节点为树中的元素,包含数据项以及若干指向其子树的分支
在上面的树中:A,B ,C,D,E,F,G,H,I,J都是这棵树的节点(node)

节点的度

节点的度也成为degree(),他的含义为节点的子树科目
在上面的树中 degree(A) = 2,degree(G) = 1
因为这是一颗二叉树,所以任何一个节点的度都不会超过2

树的度

树的度就是节点的度的最大值
同节点的度可知,上面的这棵树为一颗二叉树,所以这棵树的度也为2

叶子节点

节点的度为0的节点被称为叶子节点
例如在上面这颗树中,叶子节点有H J E F I 这五个节点,他们都是没有自己的子树

父节点

一个节点的相连接的上一个节点被称为父节点
例如在上面的这棵树中B的父节点为A ; E的父节点为B

树的链式存储结构

一般的情况下,以二叉树为例子,树的结构一般如下图所示:
在这里插入图片描述

struct TreeNode
{
	struct TreeNode* leftchild;
	int data;
	struct TreeNode* rightchild;
} 

树的遍历

以这一颗树作为例子
在这里插入图片描述

树的层次遍历(树的广度优先遍历)

树的层次遍历其实就是树的每一层的元素从左到右进行遍历
用上面的这棵树举例子就是 A B C D L E F G H I

当我们使用代码对树的广度优先遍历的时候,我们需要结合队列进行书写:
例如:
我们先把A节点放入队列
当我们从把A从队列里出来的时候,要把A节点的三个子节点B C D按顺序入队,当我们要取出B节点的时候,同时需要将B节点的子节点入队

树的前序遍历

①先访问根节点
②在访问左节点,接着访问右节点

对于上面这一棵树而言,它的前序遍历为:
A B L E C F D G I H

示例代码如下:

void FirTrav(TreeNode* ps)
{
	if (ps == NULL)
	{
		return;
	}
	printf("%d ", ps->data);
	FirTrav(ps->lchild);
	FirTrav(ps->rchild);
}
树的后序遍历

①先访问左节点,在访问右节点
②最后访问根节点

对于上面这一棵树而言:
L E B F C I G H D A

示例代码为:

void LastTrav(TreeNode* ps)
{
	if(ps == NULL)
	{
		return;
	}
	LastTrav(ps->lchild);
	LastTrav(ps->rchild);
	printf("%d ",ps->data);
}
树的遍历的巧记方法
前序遍历的巧记方法

在树的每个节点的左边画一个圈圈,对这个树进行从左边开始往下画线,先路过的圈圈就先写出来,例如:
在这里插入图片描述

后序遍历的巧记方法

和前序遍历相似,后序遍历一般是在每个节点的右侧画圈圈然后相连
在这里插入图片描述

中序的巧记方法

和前两个相似,只不过是在每个节点的下方画一个圈圈然后进行相连

二叉树

二叉树的定义

在这里插入图片描述
根据二叉树的定义可知,二叉树的每一个节点最多有两个子节点

二叉树的性质

性质1

一颗非空二叉树的第i层上最多有2^(i-1)个节点

性质2

一颗高度为k的二叉树,最多有2^k -1 个节点

性质3

对于一颗非空二叉树,如果叶子节点的节点数为a,度为2的节点数为b,那么有a = b + 1成立

二叉树的创建代码

TreeNode* Create()
{
	int data = 0;
	scanf("%d", &data);
	if (data == -1)
	{
		return NULL;
	}
	else
	{
		TreeNode* newnode = (TreeNode*)malloc(sizeof(TreeNode));
		if (newnode == NULL)
		{
			printf("malloc fail!");
			exit(-1);
		}
		newnode->data = data;
		printf("请输入%d节点的左节点", data);
		newnode->lchild = Create();
		printf("请输入%d节点的右节点", data);
		newnode->rchild = Create();
		return newnode;
	}

}

特殊的二叉树

满二叉树

一颗高度为K 并且具有2^k - 1 个节点的二叉树被称为满二叉树
换句话来说,满二叉树就是一颗二叉树中的任意一层的节点数都达到了最大值
下面是两个二叉树的辨析
在这里插入图片描述
左边图的二叉树的第四排没有达到节点数量的最大值,所以左边的这棵树并不是满二叉树

完全二叉树

在满二叉树的最底层自右至左依次(注意:不能跳过任何一个结点)去掉若干个结点得到的二叉树也被称之为完全二叉树。满二叉树一定是完全二叉树但完全二叉树不一定是满二叉树

完全二叉树的特点

(1)所有的叶结点都出现在最低的两层上。
(2)对任一结点,如果其右子树的高度为k,则其左子树的高度为k或k+1。

二叉树的遍历

二叉树的遍历同样的分为前序遍历,后序遍历和中序遍历,由于前面已经用了前序和后序作为举例,所以在这里用中序遍历进行举例

用这一棵树进行举例:
在这里插入图片描述
按照在每个节点的下方画一个圈圈进行相连的办法,我们对这颗二叉树进行中序遍历,遍历的图片如下:
在这里插入图片描述
所以这一颗树的中序遍历为: B L E A C W X D

二叉树的顺序存储

完全二叉树的存储

在这里插入图片描述
以这一颗树作为例子,将这棵树的节点从上到下,从左到右进行序号标识,然后进行顺序存放
在这里插入图片描述

普通二叉树的顺序存储

在这里插入图片描述
以这一颗二叉树作为例子,和完全二叉树的顺序存储同理,他的顺序存储为:
在这里插入图片描述
其中NULL表示一个子树没有两个子树,空的那个子树位置就用NULL进行替换

单只树的顺序存储

在这里插入图片描述
还是同理写出顺序存储
在这里插入图片描述
在这一颗树的顺序存储中,标红的两个NULL可能不是很好理解,其代表的是A的左子树的左右子树也是NULL为空

哈夫曼树

哈夫曼树在之前的文章中有很详细的讲过
数据结构——哈夫曼树

二叉排序树

二叉排序树的性质:

对于任意的一个节点P而言:
①如果P的左子树为非空子树,则左子树上的所有节点的关键字均小于P节点的关键字的值
②如果P的右子树为非空子树,则右子树上的所有节点的关键字均大于P节点的关键字的值
③节点P的左右子树同样也是二叉排序树

二叉排序树的创建:

例如给出122 99 250 110 200 300 105 230 216这几个数字,构建一颗二叉排序树,排序的结果如下:
在这里插入图片描述
排序的注意事项
①如果需要插入的节点比该根节点,则放在该根节点的左边,否则放在右边
②在插入节点的时候,需要一层一层的进行比较插入
下面是示例代码:

void insert(Bitree** tree, int x) //指向指针变量的指针,结果是指针tree所指向的值
{
    Bitree* temp = NULL;
    if ((*tree) == NULL) //判断根节点是否存在
    {
        temp = (Bitree*)malloc(sizeof(Bitree));
        temp->lchild = temp->rchild = NULL; //左右节点制空
        temp->a = x;
        *tree = temp;
        return;
    }
    if (x < (*tree)->a) //判断是左子树
    {
        insert(&(*tree)->lchild, x); 
    }
    else if (x > (*tree)->a) //判断是右子树
    {
        insert(&(*tree)->rchild, x);
    }
}

二叉排序树的查找

即如果需要查找的数字在该二叉排列树中,那么则返回该数字所在节点的地址,如果不在该树中,则返回NULL

void Find(Bitree* T,int x)
{
    if (T->a == x)
    {
        printf("找到了");
        return;
    }
    if (T->a > x)
    {
        Find(T->lchild, x);
    }
    if (T->a < x)
    {
        Find(T->rchild, x);
    }
}

二叉平衡树(AVL树)

AVL树的性质和定义

①左子树和右子树高度之差的绝对值小于等于1
②左子树和右子树也是平衡二叉树

在树上的每个节点附加上一个数字,这个数字的含义是左子树和右子树的高度差,这个数字被称为这个节点的平衡因子
平衡因子=节点左子树的高度-节点右子树的高度

而平衡二叉树(AVL树)的每个节点的平衡因子只能是**-1,0,1**

例如下面这棵树就是一颗AVL树:
在这里插入图片描述

AVL树调整平衡

LL型、LR型、RL型、RR型的不平衡情况
LL型

在这里插入图片描述
LL型的不平衡树采用的是右旋的办法成为AVL树

LR型

在这里插入图片描述
LR型的不平衡树采用的是先左旋再右旋的顺序进行转化

RR型

在这里插入图片描述
RR型的不平衡采用的是左旋的方法进行转化

RL型

在这里插入图片描述
RL型的不平衡采用的是先左旋再右旋的方法转化

平衡树旋转的规则

把平衡因子为中等的变为根,把最小的放在左孩子,把最大的放在右孩子
即找到平衡因子处于三者之间的作为根进行旋转

二叉搜索树(BST)

二叉搜索树的定义

每个根节点的值都比它的左子树的值要,但是比右子树要
但是需要注意的是,在二叉搜索树中,不可以有相同的数字

下图是一个二叉搜索树的举例:
在这里插入图片描述

代码
struct treeNode
{
	int val;
	struct treeNode* left;
	struct treeNode* right;
}

二叉搜索树的插入

在我们往一颗二叉搜索树中插入元素的时候,我们需要对这颗树的位置进行寻找调整,保证插进去后的树依旧为二叉查找树

问题的简单分析

①先判断这颗二叉查找树是否为空树,如果这颗二叉查找树为空树的话,那么插入的那个新元素就是这颗树的根节点(下面的分析均默认这颗二叉查找树不是空树)
②如果需要插入的这个元素的值小于当前根节点的值,那么就继续寻找当前树的左子树,直到找到为空树就直接插入,递归结束
③如果其大于根节点的值,则寻找右子树,同②

代码
struct treeNode* insert(struct treeNode* root,int val)
{
	if(root == NULL)
	{
		struct treeNode* newnode = (struct treeNode*)malloc(sizeof(struct treeNode));
		newnode->val = val;
		newnode->left = NULL;
		newnode->right = NULL;
		return newnode;
    }
    if(val < root->val)
    {
		root->left = insert(root->left,val);
	}
	else if(val > root->val)
	{
		root->right = insert(root->right,val);
	}
	return root;
}

二叉搜索树的删除

问题的简单分析

我们首先要分析我们需要删除的节点属于哪一类型的节点:①没有任何孩子的节点 ②有一个孩子的节点 ③有两个孩子的节点
①如果这个节点没有任何的孩子,那么直接删除这个节点即可
②查找需要删除的节点val,如果val小于当前的节点,则递归到左子树继续寻找,如果val大于当前的节点,则递归到右子树进行寻找
③如果需要删除的是当前的根节点,那么需要找到右子树中最小的节点,并将其赋值给当前的节点,再递归到右子树中删除该节点

代码
struct treeNode* findmin(struct treeNode* node)
{
	while(node->left)
	{
		node = node->left;
	}
	return node;
}

struct treeNode* delete(struct treeNode* root,int val)
{
	if(root == NULL)
		return NULL;
	if(val < root->val)
		root->left = delete(root->left,val);
	else if(val > root->val)
		root->right = delete(root->right,val);
	else
	{
		if(root->left == NULL)
		{
			struct treeNode* temp = root->right;
			free(root);
			return temp;
		}
		if(root->right == NULL)
		{
			struct treeNode* temp = root->left;
			free(root);
			return temp;
		}
		struct treeNode* temp = findmin(root->right);
		root->val = temp->val;
		root->right = delete(root->right,temp->val);
	}	
	return root;
}

二叉搜索树的查找

这个其实是一个非常容易的事情

问题的简单分析

①先判断这颗二叉查找树是否为空,如果为空树的话,直接返回这个需要查找的数字不在此BST中
②如果这个需要查找的数字小于根节点,那么则递归左子树,反则递归右子树

代码
struct treeNode* findnode(struct treeNode* root,int val)
{
	if(root == NULL)
	{
		return NULL;
	}
	if(val < root->val)
	{
		return findnode(root->left,val);
	}
	if(val > root->val)
	{
		return findnode(root->right,val);
	}
	return root;
}
  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值