关于二叉树的常见题型

本文详细介绍了二叉树的基本概念,包括度、深度、满二叉树、完全二叉树、二叉排序树和平衡二叉树等。同时,重点讨论了红黑树的性质。此外,文章列举了二叉树在面试中常见的问题,如遍历、路径求解、深度计算等,并提及了哈夫曼树的构建及其应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、二叉树相关概念

1.1 基本术语

  • 结点的度:一个结点的子结点的个数称为结点的度。
  • 树的度:树中结点的最大度数为树的度
  • 树的深度(高度):树中结点的最大层数,从1开始。

1.2 二叉树分类

  • 满二叉树:一颗高度为h,并且含有2^h-1个结点的二叉树称为满二叉树。即树中每一层都含有最多的节点。除叶子节点每个节点的度都为2
  • 完全二叉树:当高度为h具有n个结点的二叉树,与高度为h的满二叉树结点编号完全对应时,即为完全二叉树。(若存在度为1的节点,只能有一个,且只有左孩子无右孩子)
  • 二叉排序树:左子树上所有的结点的关键字均小于根节点的关键字,右子树上所有节点的关键字均大于根结点的关键字。(中序遍历可得到递增序列)
  • 平衡二叉树:树上任一结点的左子树和右子树深度之差不超过1
  • 哈夫曼树:在含有n个带权叶子节点的二叉树中,其中带权路径长度最小的二叉树为哈夫曼树(最优二叉树)—哈夫曼编码
  • 红黑树:红黑树是每个节点都带颜色的树,节点颜色或是红色或是黑色,红黑树是查找树。最重要的性质,从根节点到叶子节点的最长的路径不多于最短路径的长度的2倍。红黑树插入、查找、删除的时间复杂度都为O(logn)

红黑树的五个特性:

(1)每个节点要么是红的,要么是黑的

(2)根节点是黑色的

(3)所有叶子节点即空节点NIL都是黑的

(4)如果一个节点是红色的,则两个子节点都是黑的

(5)对每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑节点

1.3 二叉树的几条重要性质:

  1.  非空二叉树上叶子节点的个数等于度数为2的节点数+1   
  2. 结点i所在的层次(深度)为(logn)+1
  3. N个节点的二叉树的高度为(logn)+1或是log(n+1)

二、二叉树的常见面试题

注意事项:

  •  根节点是否为空(递归结点是否为null)
  • 是否只存在根节点(一个节点)
  • 二叉树通常使用递归,清楚递归结束的条件。例如找路径时是遇到叶子节点停止,大多数是当前结点为null
题目汇总

  1. 二叉树的遍历:前序遍历、中序遍历、后序遍历、层次遍历(队列)
  2. 根据中序遍历序列和前序遍历序列(或后序遍历)重建二叉树。
  3. 求二叉树的所有路径(根到叶子节点)
  4. 判断二叉树中是否存在一条路径使得节点之和为给定的值。
  5. 求二叉树的最大深度、最小深度
  6. 求二叉树第k的节点个数(层次遍历:队列)
  7. 求二叉树中叶子节点的个数
  8. 交换二叉树的左右孩子
  9. 判断两棵二叉树是否相同(结构和值都相等)
  10.  判断二叉树是否为完全二叉树
  11. 判断二叉树是否为平衡二叉树
  12. 求二叉树的镜像
  13. 输入两棵二叉树AB,判断B是否为A的子树
  14. 求二叉树中节点的最大距离
  15. 将二叉查找树变为有序的双向链表
  16. 求二叉树中两个节点的最低公共祖先节点
  17. 哈夫曼树的构建

详细代码
  1. 二叉树的遍历:前序遍历、中序遍历、后序遍历、层次遍历(队列)
前序遍历:
        //递归:前序遍历
	public void preOrder(Node node){
		if(node!=null){
			System.out.print(node.getPerson().getKey()+"\t");
			preOrder(node.getLeftChild());
			preOrder(node.getRightChild());
		}
	}
	
	//非递归:前序遍历
	private void preOrder_2(){
		Node curNode = rootNode;
		while(curNode!=null){
			//打印当前节点
			System.out.print(curNode.getPerson().getKey()+"\t");
			//入栈
			stack.push(curNode);
			//指向左子节点
			curNode = curNode.getLeftChild();
			//查找最左边的子节点
			while(curNode == null&&!stack.isEmpty()){
				curNode = stack.peek();//取栈顶元素
				stack.pop();//出栈
				curNode = curNode.getRightChild();
			}
		}
	}
中序遍历:
        //递归:中序遍历
	public void midOrder(Node node){
		if(node!=null){
			midOrder(node.getLeftChild());
			System.out.print(node.getPerson().getKey()+"\t");
			midOrder(node.getRightChild());
		}
	}
	//非递归:中序遍历(左根右)
	public void midOrder_2(){
		Node curNode = rootNode;
		while(curNode!=null||!stack.isEmpty()){
			if(curNode.getLeftChild()!=null){
				stack.push(curNode);
				curNode = curNode.getLeftChild();
			}else {
				//打印最左端的节点
				System.out.print(curNode.getPerson().getKey()+"\t");
				curNode = curNode.getRightChild();//指向右子节点
				while(curNode == null&&!stack.isEmpty()){
					curNode = stack.peek();
					System.out.print(curNode.getPerson().getKey()+"\t");
					stack.pop();
					curNode = curNode.getRightChild();
				}
			}
		}
	}
后序遍历:
        //递归:后序遍历
	public void behOrder(Node node){
		if (node!=null) {
			behOrder(node.getLeftChild());
			behOrder(node.getRightChild());
			System.out.print(node.getPerson().getKey()+"\t");
		}
	}
	//非递归:后序遍历(左根右)
	public void behOrder_2(){
		Node curNode = rootNode;
		Node preNode = null;
		//先将根入栈
		stack.push(curNode);
		while(!stack.isEmpty()){
			curNode = stack.peek();//当前节点设置为栈顶节点
			if(curNode.getLeftChild()==null&&curNode.getRightChild()==null
			||(preNode!=null&&(curNode.getLeftChild()==preNode||curNode.getRightChild()==preNode))){
				//当前节点无左右节点,或者有左节点或右节点,但已经被访问过
				//则直接输出该节点,将其出栈,将其设为上一个访问的节点
				System.out.print(curNode.getPerson().getKey()+"\t");
				stack.pop();
				preNode = curNode;//已被访问过
			}else {
				//如果不满足上面两种情况,则将其右孩子左孩子依次入栈(先右节点再左节点)
				if (curNode.getRightChild()!=null) {
					stack.push(curNode.getRightChild());
				}
				if(curNode.getLeftChild()!=null){
					stack.push(curNode.getLeftChild());
				}
			}
		}
	}
(1)非递归定义 树(tree)是由n(n≥0)个结点组成的有限集合。n=0的树称为空树;n>0的树T: ① 有且仅有一个结点n0,它没有前驱结点,只有后继结点。n0称作树的根(root)结点。 ② 除结点外n0 , 其余的每一个结点都有且仅有一个直接前驱结点;有零个或多个直接后继结点。 (2)递归定义 一颗大树分成几个大的分枝,每个大分枝再分成几个小分枝,小分枝再分成更小的分枝,… ,每个分枝也都是一颗树,由此我们可以给出树的递归定义。 树(tree)是由n(n≥0)个结点组成的有限集合。n=0的树称为空树;n>0的树T: ① 有且仅有一个结点n0,它没有前驱结点,只有后继结点。n0称作树的根(root)结点。 ② 除根结点之外的其他结点分为m(m≥0)个互不相交的集合T0,T1,…,Tm-1,其中每个集合Ti(0≤i<m)本身又是一棵树,称为根的子树(subtree)。 2、掌握树的各种术语: (1) 父母、孩子与兄弟结点 (2) 度 (3) 结点层次、树的高度 (4) 边、路径 (5) 无序树、有序树 (6) 森林 3、二叉树的定义 二叉树(binary tree)是由n(n≥0)个结点组成的有限集合,此集合或者为空,或者由一个根结点加上两棵分别称为左、右子树的,互不相交的二叉树组成。 二叉树可以为空集,因此根可以有空的左子树或者右子树,亦或者左、右子树皆为空。 4、掌握二叉树的五个性质 5、二叉树的二叉链表存储。
<think>首先,用户查询的是数据结构考研中二叉树常见操作的代码实现。我需要基于提供的引用内容来回答,但引用是有限的,所以我会尽量使用这些信息并结合我的知识。 关键引用: - 引用[1]:提到了线索二叉树的结点结构。 - 引用[2]:讨论了二叉树的遍历、节点个数计算、高度差等操作。 - 引用[3]:涉及二叉树的遍历和线索二叉树,包括真题实战。 用户要求中文回答,结构清晰,帮助用户逐步解决问题。系统指令指定了数学表达式的格式、LaTeX语法正确、生成相关问题等。 回答结构: 1. **介绍二叉树**:简要解释二叉树在数据结构中的重要性,特别是在考研中的常见考点。 2. **常见操作列表**:列出用户可能需要的常见操作,如创建、遍历、节点计数等。 3. **代码实现**:为每个操作提供代码实现。代码应使用代码块格式,如示例中的Python代码。但用户查询的是数据结构考研,通常使用C或C++,所以我应该用C语言来实现,因为这是考研中常见的。 4. **逐步解释**:对每个操作简要解释其原理和实现。 5. **引用标识**:在回答中自然地添加引用标识,如[^1]、[^2]等。 6. **相关问题**:在最后生成相关问题,类似于示例。 常见二叉树操作(基于引用和标准知识): - 遍历:前序、中序、后序、层序(引用[2]和[3]提到)。 - 节点计数:总节点数、叶子节点数、第k层节点数(引用[2]提到)。 - 查找节点(引用[2])。 - 销毁二叉树(引用[2])。 - 高度计算(引用[2]提到高度差)。 - 线索二叉树(引用[1]和[3])。 代码实现:我将使用C语言,因为它是考研数据结构中的标准语言。 结构: - 定义二叉树结点结构:先定义基本结点结构。 - 然后为每个操作提供函数实现。 引用标识: - 当提到线索二叉树时,引用[1]。 - 当提到遍历和节点操作时,引用[2]。 - 当提到遍历效率和线索二叉树时,引用[3]。 最后,生成相关问题。 完整回答大纲: 1. **引言**:二叉树是数据结构中的核心概念,考研中常考其操作实现。 2. **二叉树结点定义**:给出结构体定义。 3. **常见操作实现**: - 创建二叉树 - 遍历:前序、中序、后序(递归和迭代)、层序 - 节点计数:总节点数、叶子节点数、第k层节点数 - 查找节点 - 计算高度 - 销毁二叉树 - (可选)线索二叉树相关,如线索化 4. **代码示例**:提供完整或部分代码。 5. **总结**:强调重点。 6. **相关问题**:生成3-5个相关问题。 数学表达式:如果有数学内容,如高度计算,使用$...$或$$...$$格式。但二叉树操作主要是代码,可能数学不多。 确保LaTeX语法正确:例如,在解释高度时,如果有公式。 现在,构建代码: - 结点定义:基于引用[2],但引用[1]是线索二叉树,所以先做基本二叉树。 - 基本结点:`struct TreeNode { int data; struct TreeNode* left; struct TreeNode* right; };` - 操作函数: - `createNode(int data)`: 创建新节点。 - 遍历函数:`preorder`, `inorder`, `postorder`(递归),`levelorder`(使用队列)。 - `countNodes`, `countLeafNodes`, `countNodesAtLevel`(第k层节点数)。 - `findNode`:查找值为x的节点。 - `height`:计算高度。 - `destroyTree`:销毁二叉树。 引用[1]有线索二叉树的定义,我可以简要包括它。 最终回答: - 先定义基本二叉树。 - 然后操作。 - 最后提到线索二叉树作为扩展。 生成相关问题:如“如何实现二叉树的非递归遍历?”、“线索二叉树如何提高效率?”等。</think>以下是针对数据结构考研中二叉树常见操作的代码实现详解。二叉树是数据结构中的核心内容,考研中常考其基本操作,包括遍历、节点计数、高度计算等。我将基于C语言(考研常用语言)逐步解释每个操作的原理,并提供清晰、可靠的代码实现。代码实现参考了考研资料中常见的实践方法[^2][^3],并确保语法正确。二叉树操作通常包括创建、遍历、统计节点、查找、销毁等,我还简要介绍了线索二叉树作为扩展(参考线索二叉树的结点结构[^1])。 ### 1. 二叉树基本结构定义 首先,定义二叉树结点的结构体。每个结点包含数据域和左右子结点指针。 ```c #include <stdio.h> #include <stdlib.h> // 定义二叉树结点结构体 typedef struct TreeNode { int data; // 结点数据(假设为整型) struct TreeNode* left; // 左子结点指针 struct TreeNode* right; // 右子结点指针 } TreeNode; ``` ### 2. 常见操作代码实现 下面列出二叉树在考研中的常见操作,每个操作包括: - **原理简述**:简要解释算法逻辑。 - **代码实现**:提供C语言函数实现,使用递归(考研重点)和迭代方法。 - **时间复杂度**:分析算法效率($O(n)$ 表示线性时间,$n$ 为结点数)。 #### (1) 创建新结点 - **原理**:动态分配内存,初始化结点数据。 - **代码**: ```c TreeNode* createNode(int data) { TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode)); if (newNode == NULL) { perror("Memory allocation failed"); exit(EXIT_FAILURE); } newNode->data = data; newNode->left = NULL; newNode->right = NULL; return newNode; } ``` - **时间复杂度**:$O(1)$。 #### (2) 二叉树遍历(递归实现) 遍历是二叉树的基础操作,考研中常考递归版本[^2][^3]。 - **前序遍历**(根-左-右): - **原理**:先访问根结点,再递归遍历左子树和右子树。 - **代码**: ```c void preorderTraversal(TreeNode* root) { if (root == NULL) return; printf("%d ", root->data); // 访问根结点 preorderTraversal(root->left); preorderTraversal(root->right); } ``` - **中序遍历**(左-根-右): - **原理**:先递归遍历左子树,再访问根结点,最后递归遍历右子树。常用于二叉搜索树的排序。 - **代码**: ```c void inorderTraversal(TreeNode* root) { if (root == NULL) return; inorderTraversal(root->left); printf("%d ", root->data); // 访问根结点 inorderTraversal(root->right); } ``` - **后序遍历**(左-右-根): - **原理**:先递归遍历左子树和右子树,最后访问根结点。常用于销毁树或计算表达式树。 - **代码**: ```c void postorderTraversal(TreeNode* root) { if (root == NULL) return; postorderTraversal(root->left); postorderTraversal(root->right); printf("%d ", root->data); // 访问根结点 } ``` - **层序遍历**(使用队列): - **原理**:按层访问结点,使用队列辅助实现(递归不适合层序)。 - **代码**(需要简单队列实现): ```c #include <queue.h> // 假设有队列库,或自定义队列 void levelorderTraversal(TreeNode* root) { if (root == NULL) return; Queue* queue = createQueue(); // 创建队列 enqueue(queue, root); // 根结点入队 while (!isEmpty(queue)) { TreeNode* current = dequeue(queue); // 出队当前结点 printf("%d ", current->data); if (current->left != NULL) enqueue(queue, current->left); // 左子结点入队 if (current->right != NULL) enqueue(queue, current->right); // 右子结点入队 } freeQueue(queue); // 释放队列内存 } ``` - **时间复杂度**:所有遍历均为 $O(n)$。 #### (3) 节点计数操作 考研中常考节点统计问题,如叶子结点数或第k层结点数[^2]。 - **计算二叉树结点总数**: - **原理**:递归统计左子树和右子树的结点数加1(根结点)。 - **代码**: ```c int countNodes(TreeNode* root) { if (root == NULL) return 0; return 1 + countNodes(root->left) + countNodes(root->right); } ``` - **计算叶子结点数**(无子结点的结点): - **原理**:如果结点无左右子结点,则为叶子结点;否则递归统计。 - **代码**: ```c int countLeafNodes(TreeNode* root) { if (root == NULL) return 0; if (root->left == NULL && root->right == NULL) return 1; // 叶子结点 return countLeafNodes(root->left) + countLeafNodes(root->right); } ``` - **计算第k层结点数**: - **原理**:递归到第k层时计数。高度为 $h$ 的树,第k层结点数满足递推关系。 - **代码**: ```c int countNodesAtLevel(TreeNode* root, int k) { if (root == NULL) return 0; if (k == 1) return 1; // 当前层为k return countNodesAtLevel(root->left, k - 1) + countNodesAtLevel(root->right, k - 1); } ``` - **时间复杂度**:计数操作均为 $O(n)$。 #### (4) 查找结点 - **原理**:递归查找值为x的结点,先查根,再左子树,最后右子树。 - **代码**: ```c TreeNode* findNode(TreeNode* root, int x) { if (root == NULL) return NULL; if (root->data == x) return root; // 找到结点 TreeNode* leftResult = findNode(root->left, x); // 左子树查找 if (leftResult != NULL) return leftResult; return findNode(root->right, x); // 右子树查找 } ``` - **时间复杂度**:$O(n)$。 #### (5) 计算二叉树高度 - **原理**:高度定义为从根到最深叶子结点的路径长度。递归计算左右子树高度,取最大值加1。 - **公式**:高度 $h$ 满足: $$ h(root) = \begin{cases} 0 & \text{if } root = \text{NULL} \\ \max(h(left), h(right)) + 1 & \text{otherwise} \end{cases} $$ - **代码**: ```c int height(TreeNode* root) { if (root == NULL) return 0; int leftHeight = height(root->left); int rightHeight = height(root->right); return (leftHeight > rightHeight) ? leftHeight + 1 : rightHeight + 1; } ``` - **时间复杂度**:$O(n)$。 #### (6) 销毁二叉树 - **原理**:使用后序遍历递归释放内存,避免内存泄漏。 - **代码**: ```c void destroyTree(TreeNode* root) { if (root == NULL) return; destroyTree(root->left); destroyTree(root->right); free(root); // 释放当前结点 } ``` ### 3. 扩展:线索二叉树简介 线索二叉树是考研中的优化考点,它利用空指针存储遍历线索,提高遍历效率[^1][^3]。结点结构包含线索标志(`ltag` 和 `rtag`): ```c typedef struct ThreadNode { int data; struct ThreadNode* lchild, *rchild; int ltag, rtag; // tag=0 表示指针指向孩子,tag=1 表示指针是线索 } ThreadNode, *ThreadTree; ``` - **应用场景**:中序线索化可实现在 $O(n)$ 时间内完成遍历,无需递归栈,适合内存受限环境[^3]。代码实现较复杂,建议参考专门资料。 ### 4. 完整示例代码 以下是一个简单的主函数示例,演示如何使用上述操作: ```c int main() { // 构建一个简单二叉树: 根(1), 左(2), 右(3) TreeNode* root = createNode(1); root->left = createNode(2); root->right = createNode(3); root->left->left = createNode(4); root->left->right = createNode(5); // 测试操作 printf("前序遍历: "); preorderTraversal(root); // 输出: 1 2 4 5 3 printf("\n总节点数: %d\n", countNodes(root)); // 输出: 5 printf("叶子节点数: %d\n", countLeafNodes(root)); // 输出: 3 (4,5,3) printf("高度: %d\n", height(root)); // 输出: 3 destroyTree(root); // 销毁树 return 0; } ``` ### 总结 二叉树操作是数据结构考研的重点,核心在于递归实现遍历和统计。本文提供了常见操作的C语言代码,包括遍历、节点计数、查找、高度计算和销毁,覆盖了考研高频考点[^2][^3]。线索二叉树作为优化手段,可进一步提升遍历效率[^1]。练习时,注意边界条件(如空树)和递归深度,避免栈溢出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值