关于二叉树

前言:

二叉树是“树”的一种特殊形式,所以了解二叉树前,我们需要先了解关于树的一些名词概念,以便于更好的进行二叉树的学习


树的定义:树是n(n>=0)个结点的有限集合,且各子树之间是不相交的,每个节点有且仅有一个父节点

关于树的一些名词概念:

  • 节点的度
    一个节点含有的子树的个数即为该结点的度
  • 树的度
    一棵树中,最大的节点的度称为该树的度
  • 叶子节点
    度为0的结点(终端结点)
  • 子节点
    一个节点含有的子树的根节点称为该结点的子节点
  • 父节点
    若一个节点含有子节点,则这个节点称为其子结点的父节点
  • 兄弟节点
    具有相同的父节点的节点互称为兄弟节点
  • 节点的层次
    从根开始定义,根为第一层,根的子节点为第二层,以此类推
  • 树的高度
    即树中结点的最大层次

了解完树的基础概念之后,我们来看看二叉树有什么特殊性质吧

关于二叉树的一些特殊性质:

  • 假设一棵满二叉树的高度为h,则结点个数为:N = 2^h - 1
  • 假设有N个结点,则二叉树的高度为:h= log(N+1)
  • 度为0的结点比度为2的结点的个数多一个(n0=n2+1)
    叶子结点的个数比度为2的结点的个数多一个

第一、二点的性质证明:

第一层的节点数为2^0,第二层的节点数为2^1,第三层的节点数为2^2…, 总节点的个数即2^0 + 2^1 + 2^2 + … +2^(h-1) = N依等比数列求和公式可得:N = 2^h - 1 ---> h= log(N+1) 

第三点性质记住即可,对于任一二叉树都通用

紧接着我们来看看二叉树中的更特殊的二叉树

  • 满二叉树
    每一层都是满的(每一层的度都为2)
    满二叉树是一种特殊的完全二叉树(完全且满的二叉树)
  • 完全二叉树
    除了最后一层不满之外,上面每一层都是满的(前h-1层都是满的),
    但是最后一层从左往右的节点都是连续的
  • 搜索二叉树
    任何一棵树,左子树的val值都比根的val值要小,右子树都比根要大;
    AVL树即平衡搜索树,左右子树趋于平衡,高度相差不大于1

了解了基础的二叉树的各个性质之后,作为数据结构的学习,紧接着就到了如何遍历这个二叉树了,一般的二叉树不适用于存储数据(效率以及复杂程度都过高,没有实际意义),故在基础二叉树的章节我们不涉及二叉树的增删查改,而是先学习遍历。

tips:树的结构本身就是一个递归结构:根 子树… 子树又分为根 子树… 所以解决树的相关问题时都是利用分治算法(递归)来解决

二叉树的遍历方式主要分为以下三种:
ps:任何一棵二叉树都要分为三个部分: 根节点,左子树,右子树

  • 深度优先遍历
    递归进行的遍历,主要利用到分治算法,以递归解决问题
    (通过前序/后序 + 中序遍历的结果可以还原一整棵二叉树)​
    • 前序遍历(先根遍历)
      即先访问根,再访问左子树,再访问右子树
      访问左子树时,左子树也是一棵二叉树,即也跟原来问题的规模类似,所以也是先访问根,再访问左子树,再访问右子树,以此类推,直到访问到叶子结点的子节点(左子树为空,右子树为空),即访问到的根为空了就返回
      算法实现源码:
      void PrevOrder(BTNode *Tree)
      {
         if(!Tree)
         {
             printf("NULL ");
             return ;
         }
         printf("%c ",Tree->val);//访问二叉树的结点值
         PrevOrder(Tree->left);
         PrevOrder(Tree->right);
      }
    • 中序遍历(中根遍历)
      即先访问左子树,再访问根,再访问右子树
      算法实现源码
      void InOrder(BTNode *Tree)
      {
         if(!Tree)
         {
             printf("NULL ");
             return ;
         }
         InOrder(Tree->left);
         printf("%c ",Tree->val);//访问二叉树的结点值
         InOrder(Tree->right);
      }

    • 后序遍历(后根遍历)
      先访问左子树,再访问右子树,最后访问根
      访问过程都与前序类似,也是要不断分割至不能分割的子问题为止
      算法实现源码
      void PostOrder(BTNode *Tree)
      {
      
         if(!Tree)
         {
             printf("NULL ");
             return ;
         }
         PostOrder(Tree->left);
         PostOrder(Tree->right);
         printf("%c ",Tree->val);//访问二叉树的结点值
      }
  • 广度优先便利
    非递归进行的遍历
    ​核心思路:利用队列先进先出的特性进行遍历,关键就是上一层数据带下一层数据
    • 层序遍历
      先将根入队列,然后队列不为空则将队头数据取出来,同时判断左子树和右子树(根节点)是否为空,若不为空,则将对应节点依次(左子树,右子树)入队列,依次进行即将每一层依次遍历了一遍。
      算法实现源码
      void LevelOrder(BTNode *Tree)
      {
          if(!Tree)
              return ;
          queue<char> q;//遍历使用队列
          q.push(Tree->val);
          while(!q.empty())
          {
              char ch = q.front();
              printf("%c ",ch);
              q.pop();
              LevelOrder(Tree->left);
              LevelOrder(Tree->right);
          }
      }

基础二叉树的结构我们初步要学习的就以上的内容,最后我们来了解一下树的表示方式

  • 一般树的表示方式
    • 左孩子右兄弟表示法
      无论有几个孩子,每个只有两个指针
      一个保存第一个孩子,一个保存其右边的兄弟
    • 双亲表示法
      用数组表示,数组里只存父节点的下标。
  • 二叉树的表示方式
    二叉链 or 三叉链
    二叉链:两个节点,分别指向其左孩子和右孩子
    三叉链:三个节点,分别指向左孩子,右孩子和父节点

最后的最后,虽然是最后,但也是很重要的一个知识点:堆

堆也是一种完全二叉树的一种,不过是用数组进行形象化后的数据结构
堆的逻辑结构是一棵完全二叉树,物理结构是一个数组

首先因为是用数组存储二叉树,我们先来看看它们是如何找到父节点/子节点的

贯穿堆这个数据结构的即下面这个关系式:

  • 下标父子节点的关系
    leftChild = parent*2+1
    rightChild = parent*2+2
    parent = (child-1)/2

关于堆,分为以下两种类型的堆结构

  • 大顶堆
    任意节点的关键字是其子树所有节点中的最大值
  • 小顶堆
    任意节点的关键字是其子树所有节点中的最小值

了解完堆结构后,我们该如何建立一个堆呢,这里我们来学习两个跟堆的建立有关的算法:

向上调整算法 & 向下调整算法

  • 向上调整算法(AdjustUp):较常用于向堆插入数据
    算法思想:该算法用于当对堆进行插入数据时,保持堆的整体结构。
    即:如果向小堆随机插入一个数,插入数据后该小堆结构仍不变
    堆底往堆顶调整(其实只调整了插入结点的所有祖先结点)
    从最后一个数据开始,与父节点进行比较,不断往上交换,直到到达合适的位置

算法实现源码

void AdjustUp(HPDataType* a, int n, int child)
{
	int parent;
	assert(a);
	parent = (child-1)/2;
	//while (parent >= 0)
	while (child > 0)
	{
        //如果孩子大于父亲,进行交换
		if (a[child] > a[parent])
		{
			Swap(&a[parent], &a[child]);
			child = parent;
			parent = (child-1)/2;
		}
		else
		{
			break;
		}
	}
}
  • 向下调整算法(AdjustDown):较常用于从堆“删除”数据
    使用该算法的前提:建小堆时,左右子树都是小堆;建大堆时,左右子树都是大堆
    算法思想:(建小堆)从堆顶往堆底调整
    从根节点开始,选出左右孩子较小的那个与父亲比较,如果比父亲小则与父亲进行交换
    然后继续往下调整,直到调整到叶子结点为止,或者当左右孩子较小那个也比父亲小则也说明调整完毕,也跳出循环

算法实现源码

void AdjustDown(HPDataType* a, int n, int root)
{
	int parent = root;
	int child = parent*2+1;
	while (child < n)
	{
		// 选左右孩纸中大的一个
		if (child+1 < n 
			&& a[child+1] > a[child])
		{
			++child;
		}
 
		//如果孩子大于父亲,进行调整交换 
		if(a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent*2+1;
		}
		else
		{
			break;
		}
	}
}
  • 两个算法的实现要点:都要注意父节点或者子节点的下标,避免越界

堆的应用实例:堆排序 


 关于基础二叉树就到这里结束啦,希望对看到这里的小伙伴能够有所帮助!

写在最后:

如果感觉文章对你有帮助,或者写的还不错的话,可以点个赞,收个藏,顺带转发一手让更多人看到!~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

c.Coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值