[C][数据结构][树]详细讲解


1.树的概念

  • 树是一种非线性的数据结构

    • 有一个特殊的结点,称为根结点,根节点没有前驱结点
    • 除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2……Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
    • 因此,树是递归定义的
  • 树形结构中,子树之间不能有交集,否则就不是树形结构

  • 任何一棵树都被分为根和子树
    请添加图片描述


2.数的相关概念

  • 节点的度一个节点含有的子树的个数称为该节点的度 - 如上图:A的为6
  • 叶节点度为0的节点称为叶节点 - 如上图:B、C、H、I…等节点为叶节点
  • 分支节点:度不为0的节点 - 如上图:D、E、F、G…等节点为分支节点
  • 父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点 - 如上图:A是B的父节点
  • 子节点:一个节点含有的子树的根节点称为该节点的子节点 - 如上图:B是A的孩子节点
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点 - 如上图:B、C是兄弟节点
  • 树的度:一棵树中,最大的节点的度称为树的度 - 如上图:树的度为6
  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  • 树的高度/深度:树中节点的最大层次 - 如上图:树的高度为4
  • 堂兄弟节点:双亲在同一层的节点互为堂兄弟 - 如上图:H、I互为兄弟节点
  • 节点的祖先从根到该节点所经分支上的所有节点 - 如上图:A是所有节点的祖先
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙 - 如上图:所有节点都是A的子孙
  • 森林:由m(m>0)棵互不相交的树的集合称为森林;
    请添加图片描述

3.树的表示

  • 既然保存值域,也要保存结点和结点之间的关系 - 左孩子右兄弟法

    // 左孩子右兄弟
    typedef int DataType;
    struct BinaryTreeNode
    {
        struct TreeNode* firstChild; // 指向第一个孩子节点
        struct TreeNode *pNextBrother; // 指向其下一个兄弟节点
        DataType data; // 节点中的数据域
    };
    

    请添加图片描述


4.二叉树概念

  • 一棵二叉树是结点的一个有限集合,该集合:

    • 或者为空
    • 由一个根节点加上两棵别称为左子树和右子树的二叉树组成
      请添加图片描述
  • 二叉树不存在度大于2的结点

  • 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树


5.特殊的二叉树

  • 满二叉树

    • 每一个层的结点数都达到最大值,则是满二叉树。
    • 若一个二叉树的层数为K,且结点总数是 2 k − 1 2^k-1 2k1,则它就是满二叉树
  • 完全二叉树

    • 完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的
    • 对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树
    • 满二叉树是一种特殊的完全二叉树
    • sum = 2^k - 1
      请添加图片描述

5.二叉树的性质

  • 若规定根节点的层数为1,则一颗非空二叉树的第i层上最多有 2 ( i − 1 ) 2^{(i-1)} 2(i1)
  • 若规定根节点的层数为1,则**深度为h的二叉树的最大结点数量就是 2 k − 1 2^k-1 2k1 **
  • 对任何一颗二叉树,如果度为0其叶节点个数为 n 0 n_0 n0,度为2的分支结点个数为 n 2 n_2 n2,则有 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1
  • 若规定根节点的层数为1,具有n个节点的满二叉树的深度 h = log ⁡ 2 ( n + 1 ) h=\log_2(n+1) h=log2(n+1)
  • 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的节点有:
    • i > 0 i>0 i>0,i位置节点的双亲序号: ( i − 1 ) / 2 (i-1)/2 (i1)/2 i = 0 i=0 i=0,i为根节点编号,无双亲结点
    • 2 i + 1 < n 2i+1<n 2i+1<n,左孩子序号: 2 i + 1 2i+1 2i+1 2 i + 1 > = n 2i+1>=n 2i+1>=n否则无左孩子
    • 2 i + 2 < n 2i+2<n 2i+2<n,右孩子序号: 2 i + 2 2i+2 2i+2 2 i + 2 > = n 2i+2>=n 2i+2>=n否则无右孩子
  • 即下标间父子间的关系
    leftchild = 2 * parent + 1;
    rightchild = 2* parent + 2;
    parent = (child - 1) / 2;
    
  • 完全二叉树中,若节点总数个数为奇数,则没有度为1的节点,如果节点总个数为偶数,只有一个度为1的节点

6.二叉树的存储结构

1.顺序结构

  • 顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费

  • 实际使用中只有才会使用数组来存储

  • 二叉树顺序存储在物理上是一个数组;在逻辑上是一颗二叉树

    请添加图片描述

2.链式结构

  • 用链来指示元素的逻辑关系。

  • 通常的方法是链表中每个结点由三个域组成,数据域左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。

  • 链式结构又分为二叉链三叉链
    请添加图片描述

  • 普通二叉树增删查改没有什么意义

  • 学习其遍历或者控制结构


7.链式结构二叉树

1.二叉树的遍历 – 深度优先遍历(DFS)

  • 由于被访问的结点必是某子树的根
  • 所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树、根的右子树。
  • 前序遍历(Preorder Traversal) ——访问根结点的操作发生在遍历其左右子树之前
    • 根 左子树 右子树
  • 中序遍历(Inorder Traversal) ——访问根结点的操作发生在遍历其左右子树之中(间)
    • 左子树 根 右子树
  • 后序遍历(Postorder Traversal) ——访问根结点的操作发生在遍历其左右子树之后
    • 左子树 右子树 根
      typedef int BTDataType;
      typedef struct BinaryTreeNode
      {
      	struct BinaryTreeNode* left;
      	struct BinaryTreeNode* right;
      	BTDataType data;
      }BTNode;
      
      //深度优先遍历  -  递归思想
      void PreOrder(BTNode* root)
      {
      	assert(root);
      	if (root == NULL)
      	{
      		printf("# ");
      		return;
      	}
       
      	printf("%d ", root->data);
      	PreOrder(root->left);
      	PreOrder(root->right);
      }
       
      void InOrder(BTNode* root)
      {
      	assert(root);
      	if (root == NULL)
      	{
      		printf("# ");
      		return;
      	}
       
      	PreOrder(root->left);
      	printf("%d ", root->data);
      	PreOrder(root->right);
      }
       
      void PostOrder(BTNode* root)
      {
      	assert(root);
      	if (root == NULL)
      	{
      		printf("# ");
      		return;
      	}
       
      	PostOrder(root->left);
      	PostOrder(root->right);
      	printf("%d ", root->data);
      }
      
  • 前序遍历递归图解
    请添加图片描述

2.二叉树的遍历 – 广度优先遍历(DBS)

  • 层序遍历:自上而下,自左至右逐层访问树的结点的过程
    /层序遍历
    //广度优先遍历  -  队列思想
    void LevelOrder(BTNode* root)
    {
    	Queue q;
    	QueueInit(&q);
     
    	//入树根节点
    	if (root)
    	{
    		QueuePush(&q, root);
    	}
     
    	while (!isQueueEmpty(&q));
    	{
    		BTNode* front = QueueFront(&q);
    		QueuePop(&q);
    		printf("%d ", front->data);
     
    		if (root->left)
    		{
    			QueuePush(&q, root->left);
    		}
    		if (root->right)
    		{
    			QueuePush(&q, root->right);
    		}
    	}
    	printf("\n");
     
    	QueueDestroy(&q);
    }
    

3.接口实现

//计数器的形式
int count = 0;
void TreeSize1(BTNode* root)
{
	assert(root);
	if (root == NULL)
	{
		return;
	}
 
	count++;
	TreeSize1(root->left);
	TreeSize1(root->right);
}
 
int TreeSize2(BTNode* root)
{
	assert(root);
	return root == NULL ? 0 : TreeSize2(root->left) + TreeSize2(root->right) + 1;
}
 
int TreeLeafSize(BTNode* root)
{
	assert(root);
	if (root == NULL)
	{
		return 0;
	}
 
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	
	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
 
//求K层节点数量 - 前序
int TreeKLevel(BTNode* root, int k)
{
	//转换成子问题
	//求左子树的第k-1层 + 求右子树的第k-1层
	assert(root);
	assert(k >= 1);
 
	if (root == NULL)
	{
		return 0;
	}
 
	if (k == 1)
	{
		return 1;
	}
 
	return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);
}
 
//求二叉树深度 - 后序
int TreeDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
 
	int leftDepth = TreeDepth(root->left);
	int rightDepth = TreeDepth(root->right);
 
	return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}
 
//查找值为x的节点 - 前序
BTNode* TreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
 
	if (root->data == x)
	{
		return root;
	}
 
	BTNode* ret1 = TreeFind(root->left, x);
	if (ret1)
	{
		return ret1;
	}
 
	BTNode* ret2 = TreeFind(root->right, x);
	if (ret2)
	{
		return ret2;
	}
 
	return NULL;
}
 
//判断二叉树是否是完全二叉树 - 层序
bool isTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
 
	if (root)
	{
		QueuePush(&q, root);
	}
 
	while (!isQueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
 
		//依次压入所有节点
		if (front)
		{
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
		else
		{
			//遇到NULL节点,跳出层序遍历
			break;
		}
	}
 
	//1.如果后面全是空,则为完全二叉树
	//2.如果后面还有非空,则不是完全二叉树
	while (!isQueueEmpty)
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
 
		if (front)
		{
			return false;
		}
	}
 
	//队列已空,说明后面全是空
	return true;
}
 
//销毁二叉树 - 后序
void TreeDestroy(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
 
	TreeDestroy(root->left);
	TreeDestroy(root->right);
	free(root);
}

相关推荐

  1. 数据结构-(C++)

    2024-06-11 09:30:02       39 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-11 09:30:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-11 09:30:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-11 09:30:02       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-11 09:30:02       20 阅读

热门阅读

  1. VB.net调用VC DLL

    2024-06-11 09:30:02       11 阅读
  2. 程序员如何高效挖掘市场需求

    2024-06-11 09:30:02       9 阅读
  3. MyEclipse 新手使用教程

    2024-06-11 09:30:02       11 阅读
  4. adb 脚本化Android系统截图和录屏

    2024-06-11 09:30:02       8 阅读
  5. 记一次大量CSV数据文件同步到数据库

    2024-06-11 09:30:02       7 阅读
  6. go_compiler

    2024-06-11 09:30:02       4 阅读
  7. 平均召回(Average Recall,AR)概述

    2024-06-11 09:30:02       11 阅读
  8. 细说wayland和X11

    2024-06-11 09:30:02       7 阅读
  9. 详细说说机器学习在工业制造的应用

    2024-06-11 09:30:02       8 阅读