c++——二叉树进阶

news2025/5/11 17:40:56

1. 内容安排说明

二叉树在前面C数据结构阶段已经讲过,本节取名二叉树进阶是因为:

1. map和set特性需要先铺垫二叉搜索树,而二叉搜索树也是一种树形结构

2. 二叉搜索树的特性了解,有助于更好的理解map和set的特性

3. 二叉树中部分面试题稍微有点难度,在前面讲解大家不容易接受,且时间长容易忘

4. 有些OJ题使用C语言方式实现比较麻烦,比如有些地方要返回动态开辟的二维数组,非常麻 烦。 因此本节借二叉树搜索树,对二叉树部分进行收尾总结。

2. 二叉搜索树实现

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

1若它的左子树不为空,则左子树上所有节点的值都小于根节点的值

2若它的右子树不为空,则右子树上所有节点的值都大于根节点的值

3它的左右子树也分别为二叉搜索树

2.1二叉搜索树操作

接下来我们对于搜索二叉树的分析都是基于这个树来进行的

1二叉树的搜索

Node* Find(const K& key)
{
	if (_root->_key == key)
	{
		return _root;
	}
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key < key)
		{
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			cur = cur->_left;
		}
		else
		{
			return cur;
		}
	}
	return nullptr;
}

2二叉树的构建

bool Insert(const K& key)
{
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}
	//先找到符合的位置,再进行插入
	Node* prev = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key < key)
		{
			prev = cur;
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			prev = cur;
			cur = cur->_left;
		}
		else
		{
			return false;//搜索二叉树不允许重复
		}
	}
	//这时候找到位置了进行链接
	cur = new Node(key);
	if (prev->_key < key)
	{
		prev->_right = cur;
	}
	else
	{
		prev->_left = cur;
	}
	return true;

}

3二叉树的删除

这里的删除分为三个场景

1删除的节点为叶子节点(没有子节点)(这里直接删除就行了)

2删除的节点有一个子节点

3删除的节点有两个子节点

这里就要用到替换法(左树的最大节点或者右树的最小节点)

具体代码:


		bool Erase(const K& key)
		{
			//这里要保留parent 就不复用Find函数了
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur)
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else//找到了
				{
					//接下来根据各个场景依次判断
					//如果左右其中一个为空,那么属于第一类,直接将空的另一边连接到parent

					//左为空
					if (cur->_left == nullptr)
					{
						if (cur == _root)//防止parent为空
						{
							_root = cur->_right;
						}
						else
						{
							if (parent->_key < cur->_key)
							{
								parent->_right = cur->_right;

							}
							else
							{
								parent->_left = cur->_right;
							}
						}

					}//右为空
					else if (cur->_right == nullptr)
					{
						if (cur == _root)//防止parent为空
						{
							_root = cur->_left;
						}
						else
						{
							if (parent->_key < cur->_key)
							{
								parent->_right = cur->_left;

							}
							else
							{
								parent->_left = cur->_left;
							}
						}
					}
					else//左右都不为空,我们找左树最大的来替代
					{
						parent = cur;//防止parent 为空
						Node* LeftMax = cur->_left;
						while (LeftMax->_right)
						{
							parent = LeftMax;
							LeftMax = LeftMax->_right;
						}
						//找到最大的就替换
						cur->_key = LeftMax->_key;
						//再将这个节点删除,这个节点可能有左树
						if (LeftMax == cur->_left)
						{
							parent->_left = LeftMax->_left;
						}
						else//这时候要特殊处理
						{
							parent->_right = LeftMax->_left;
						}
						cur = LeftMax;

					}
					delete cur;
					return true;
				}

			}
			//没找到
			return false;

		}

接下来我们用递归实现二叉搜索树的递归版本

1查找

public:
	//递归版本的查找
	Node* FindR(const K& key)
	{
		return _FindR(_root, key);
	}
private:
Node* _FindR(Node* root,const K& key)
{
	if (root == nullptr)
	{
		return nullptr;
	}

	if (root->_key == key)
	{
		return root;
	}
	else if (root->_key < key)
	{
		return _FindR(root->_right, key);
	}
	else
	{
		return _FindR(root->_left, key);
	}
	
}

2插入

插入这里就要讲的很多了,这里_InsertR用的是引用返回(这是指针和引用的结合),这就给我们省下了很多力气,因为不用再去判断这个节点是parent 的左树还是右树

public:	
//递归版本的插入
	bool InsertR(const K& key)
	{
		return _InsertR(_root, key);
	}
private:
		bool _InsertR(Node* &root, const K& key)
		{
			if (root == nullptr)
			{
				//这时候要注意,指针加引用很厉害
				root = new Node(key);
				return true;
			}
			if (root->_key < key)
			{
				_InsertR(root->_right, key);
			}
			else if (root->_key > key)
			{
				_InsertR(root->_left, key);
			}
			else
			{
				return false;
			}

		}

3删除

bool _EraseR(Node*& root, const K& key)
{
	if (root == nullptr)
	{
		return false;
	}
	//要转化为子问题分置
	if (root->_key < key)
	{
		_EraseR(root->_right, key);
	}
	else if (root->_key > key)
	{
		_EraseR(root->_left, key);
	}
	else //找到了
	{
		Node* del = root;
		if (root->_left == nullptr)
		{
			root = root->_right;
		}
		else if (root->_right == nullptr)
		{
			root = root->_left;
		}
		else //3左右都不为空
		{
			Node* leftMax = root->_left ;
			while (leftMax->_right)
			{
				leftMax = leftMax->_right;
			}
			swap(leftMax->_key, root->_key);
			//这一步很精髓
			return _EraseR(root->_left, key);
            这里一定是返回了,不然还会走下面删除,就删除多了
		}
		delete del;
		return true;
	}
}
			

这里最后_EraseR的时候不能传leftMax,因为传leftMax有些情况删不掉

以上三个基本的完成了,剩下的destory走一个后续删除就可以了

3.二叉树搜索树应用分析

二叉搜索树在实际应用当中很常见

有两个模型

1key的搜索模型:快速判断不在的场景

门禁系统,小区车辆出入系统

2,key.value的搜索模型:通过一个值,去找另外一个值

商场的车辆出入系统模型(车牌号和入场时间联系起来)

4. 二叉树进阶面试题

606. 根据二叉树创建字符串 - 力扣(LeetCode)

这一题根本就是前序

要注意

1当左树为空,右树为空,都不加括号

2左树不为空,右树为空,右树不加括号

3左树为空,右树不为空,左树加括号

class Solution {
public:
    string tree2str(TreeNode* root) {
        
        if(root==nullptr)
        {
            return "";
        }
        string str=to_string(root->val);
        if(root->left || root->right)
        {
            str+="(";
            str+=tree2str(root->left);
            str+=")";
        }
        if(root->right)
        {
            str+="(";
            str+=tree2str(root->right);
            str+=")"; 
        }
 
        return str;
    }
};

236. 二叉树的最近公共祖先 - 力扣(LeetCode)

class Solution {
public:
    bool Find(TreeNode* root,int val)
    {
        if(root==nullptr)
        return false;
        if(root->val==val)
        return true;

        return Find(root->left,val) || Find(root->right,val);
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root==nullptr)
        return nullptr;
        if(p==root || q == root)
        {
            return root;
        }
        bool pleft,pright,qleft,qright;
        pleft = Find(root->left,p->val);
        pright = !pleft;
        qleft = Find(root->left,q->val);
        qright = !qleft;
        if(pleft && qleft)
        {
            return lowestCommonAncestor( root->left,  p,  q);
        }
        else if(pright && qright)
        {
            return lowestCommonAncestor( root->right,  p,  q);
        }
        else
        {
            return root;
        }
    }

    
};

这里的解法就是暴力求解,我们可以优化解法让它达到O(N)

就是如果能倒着走,就可以转化为链表相交

这里就用一个栈来实现

class Solution {
public:
    bool CreatStack(TreeNode* root,stack<TreeNode*>& st,TreeNode* x)
    {
        if(root==nullptr)
            return false;

        st.push(root);
        if(root==x)
        {
            return true;
        }
        //左右两边找到了就返回
       if(CreatStack(root->left,st,x))
            return true;
        if(CreatStack(root->right,st,x))
            return true;

        st.pop();
        return false;
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        stack<TreeNode*> pstack,qstack;
        CreatStack(root,pstack,p);
        CreatStack(root,qstack,q);

        while(pstack.size()>qstack.size())
        {
            pstack.pop();
        }
        while(pstack.size()<qstack.size())
        {
            qstack.pop();
        }
        while(pstack.top()!=qstack.top())
        {
            pstack.pop();
            qstack.pop();
        }
        return pstack.top();
    }
};

二叉搜索树与双向链表_牛客题霸_牛客网

这种写法是实在写不出来就这样写

class Solution {
public:
	void Inorder(TreeNode* root,vector<TreeNode*> &v)
	{
		if(root==nullptr)
		{
			return ;
		}
		Inorder(root->left,v);
		v.push_back(root);

		Inorder(root->right,v);
	}

    TreeNode* Convert(TreeNode* pRootOfTree) {
		if(pRootOfTree==nullptr)
		{
			return nullptr;
		}
        vector<TreeNode*> vt;
		Inorder(pRootOfTree,vt);
		TreeNode* root = vt[0];
		TreeNode* end = vt[vt.size()-1];
		root->right = vt[1];
		end->left = vt[vt.size()-2];
		for(size_t i  = 1;i<vt.size()-1;i++)
		{
			vt[i]->left = vt[i-1];
			vt[i]->right = vt[i+1];
		}
		root->left = nullptr;
		end->right = nullptr;
		return root;
    }
};

这个才是正解 

class Solution {
public:
	void _Convert(TreeNode* cur,TreeNode* &prev)
	{
		if(cur==nullptr)
		{
			return ;
		}

		_Convert(cur->left,prev);
		//中序
		cur->left = prev;
		if(prev)
		{
			prev->right = cur;
		}
		prev = cur;
		_Convert(cur->right,prev);
	}

    TreeNode* Convert(TreeNode* pRootOfTree) {
		if(pRootOfTree ==nullptr)
		{
			return nullptr;
		}
        TreeNode* head = pRootOfTree;
		TreeNode* prev = nullptr;
		while(head->left)
		{
			head = head->left;
		}
		_Convert(pRootOfTree, prev);
		head->left==nullptr;
		return head;
    }
};

 105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)

前序:根       左子树          右子树

中序:左子树       根        右子树

class Solution {
public:
    TreeNode* _buildTree(vector<int>& preorder, vector<int>& inorder,
    int& i,int begin,int end)
    {
        if(begin>end)
        {
            return nullptr;
        }
        //建立根节点
        TreeNode* root = new TreeNode(preorder[i]);
        //查找根节点位置,分出左右区间
        int rooti = begin;
        while(rooti<=end)
        {
            if(preorder[i]==inorder[rooti])
            {
                break;
            }
            rooti++;
        }
        i++;
        //区间就是[begin,rooti-1]rooti[rooti+1,end]
        root->left = _buildTree(preorder,inorder,i,begin,rooti-1);
        root->right = _buildTree(preorder,inorder,i,rooti+1,end);
        return root;
    }

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        
        int i = 0;
        int begin = 0;
        int end = inorder.size()-1;
        TreeNode* root = _buildTree(preorder,inorder,i,begin,end);
        return root;
    }
};

106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)

这两题就差不多,就是后续的时候倒着遍历posorder

class Solution {
public:
    TreeNode* _buildTree(vector<int>& inorder, vector<int>& postorder,int& posi,int begin ,int end)
    {
        if(begin>end)
        {
            return nullptr;
        }
        TreeNode* root = new TreeNode(postorder[posi]);
        int rooti = begin;
        while(rooti<=end)
        {
            if(inorder[rooti]==postorder[posi])
                break;
                
            rooti++;
        }
        posi--;
        root->right = _buildTree(inorder,postorder,posi,rooti+1,end);
        root->left = _buildTree(inorder,postorder,posi,begin,rooti-1);
        return root;

    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        int posi = postorder.size()-1;
        int begin = 0;
        int end = inorder.size()-1;
        TreeNode* root = _buildTree(inorder,postorder,posi,begin,end);
        return root;
    }
};

144. 二叉树的前序遍历 - 力扣(LeetCode)

这个要求是非递归实现,这就与我们之前搞的不同了,递归实现很简单,非递归就是用迭代的方式来实现

class Solution {
public:
    
    vector<int> preorderTraversal(TreeNode* root) {
        
        TreeNode* cur = root;
        stack<TreeNode*> st;
        vector<int> v;
        while(cur || !st.empty())
        {
            //1先访问左树节点
            while(cur)
            {
                st.push(cur);
                v.push_back(cur->val);
                cur = cur->left;
            }
            //这里左树为空了
            cur = st.top();
            st.pop();
            //以子问题的方式访问右树
            cur = cur->right;
        }
      return v;

    }
};

94. 二叉树的中序遍历 - 力扣(LeetCode)

这一题复制上一题的模板,就是在将要访问右树的时候在push_back;

145. 二叉树的后序遍历 - 力扣(LeetCode)

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        TreeNode* cur = root;
        stack<TreeNode*> st;
        TreeNode* prev = nullptr;
        vector<int> v;
        while(cur || !st.empty())
        {
            //1先访问左树节点
            while(cur)
            {
                st.push(cur);
                cur = cur->left;
            }
            //cur是指要取访问的节点
            //top取出来判断后决定cur的指向
            TreeNode* top = st.top();
            if(top->right==nullptr || top->right == prev)
            {
                //这时候不用访问
                prev = top;
                v.push_back(top->val);
                st.pop();
                
            }
            else
            {
                cur =top->right;
            }
        }
      return v;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2373298.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于flask+pandas+csv的报表实现

基于大模型根据提示词去写SQL执行SQL返回结果输出报表技术上可行的&#xff0c;但为啥还要基于pandas去实现呢&#xff1f; 原因有以下几点&#xff1a; 1、大模型无法满足实时性输出报表的需求&#xff1b; 2、使用大模型比较适合数据量比较大的场景&#xff0c;大模型主要…

PySide6 GUI 学习笔记——常用类及控件使用方法(常用类字体QFont)

文章目录 一、QFont常用方法二、常用方法总结1. 基础属性设置2. 高级样式控制3. 序列化与反序列化4. 字体信息获取 三、应用实例 字体类QFont用于设置界面控件上显示的字体&#xff0c;它包含字体名称、字体尺寸、粗体字、斜体字、删除线、上划线、下划线、字体间距等属性。 如…

宝塔服务安装使用的保姆级教程

宝塔介绍&#xff1a; 宝塔面板&#xff08;BT Panel&#xff09; 是一款 国产的服务器运维管理面板&#xff0c;主要用于简化 Linux/Windows 服务器的网站、数据库、FTP、防火墙等管理操作。它通过图形化界面&#xff08;Web端&#xff09;和命令行工具&#xff08;bt 命令&a…

Linux平台下SSH 协议克隆Github远程仓库并配置密钥

目录 注意&#xff1a;先提前配置好SSH密钥&#xff0c;然后再git clone 1. 检查现有 SSH 密钥 2. 生成新的 SSH 密钥 3. 将 SSH 密钥添加到 ssh-agent 4. 将公钥添加到 GitHub 5. 测试 SSH 连接 6. 配置 Git 使用 SSH 注意&#xff1a;先提前配置好SSH密钥&#xff0c;然…

Java【网络原理】(5)深入浅出HTTPS:状态码与SSL/TLS加密全解析

目录 1.前言 2.正文 2.1状态码 2.2HTTP与HTTPS的关系 2.3SSL协议 2.3.1对称加密 2.3.2非对称加密 2.3.3中间人攻击 2.3.4校验机制 2.3.4.1证书 2.3.4.2数字签名 1. 数字签名的生成过程 2. 数字签名的验证过程 2.4TLS协议&#xff08;握手过程&#xff09; 3.小结…

【基础IO下】磁盘/软硬链接/动静态库

前言&#xff1a; 文件分为内存文件和磁盘文件。磁盘文件是一个特殊的存在&#xff0c;因为磁盘文件不属于冯诺依曼体系&#xff0c;而是位于专门的存储设备中。因此&#xff0c;磁盘文件存在的意义是将文件更好的存储起来&#xff0c;一边后续对文件进行访问。在高效存储磁盘…

SpringBoot项目容器化进行部署,meven的docker插件远程构建docker镜像

需求&#xff1a;将Spring Boot项目使用容器化进行部署 前提 默认其他环境,如mysql,redis等已经通过docker部署完毕, 这里只讨论,如何制作springboot项目的镜像 要将Spring Boot项目使用docker容器进行部署&#xff0c;就需要将Spring Boot项目构建成一个docker镜像 一、手动…

【小记】excel vlookup一对多匹配

一个学生报四门课&#xff0c;输出每个学生课程 应用概述操作预处理数据计数指令 COUNTIFS进行一对多匹配 vlookup 应用概述 应用场景&#xff1a;学生报名考试&#xff0c;需要整理成指定格式&#xff0c;发给考试院。 一个学生最多报考四门 格式实例&#xff1a;准考证号 …

LeetCode热题100 两数之和

目录 两数之和题目解析方法一暴力求解代码 方法二哈希代码 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接 &#x1f412;&#x1f412;&#x1f412; 个人主页 &#x1f978;&#x1f978;&#x1f978; C语言 &#x1f43f;️&#x1f43f;️&#x1f43f;…

[春秋云镜] Brute4Road 仿真场景

文章目录 靶标介绍&#xff1a;知识点约束性委派攻击 外网redis主从复制base64提权 内网搭建代理wpcargo插件漏洞mssql弱口令SweetPotato提权远程桌面连接mimikatz抓取hash约束性委派攻击 参考文章 靶标介绍&#xff1a; Brute4Road是一套难度为中等的靶场环境&#xff0c;完成…

鸿蒙系统使用ArkTS开发语言支持身份证阅读器、社保卡读卡器等调用二次开发SDK

har库导入&#xff1a; { "license": "", "devDependencies": {}, "author": "", "name": "entry", "description": "Please describe the basic information.", &qu…

《Python星球日记》 第54天:卷积神经网络进阶

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、深度CNN架构解析1. LeNet-5&#xff08;1998&#xff09;2. AlexNet&#x…

《AI大模型应知应会100篇》第53篇:Hugging Face生态系统入门

第53篇&#xff1a;Hugging Face生态系统入门 ——从模型获取到部署的全流程实战指南 &#x1f4cc; 摘要 在人工智能快速发展的今天&#xff0c;Hugging Face已成为自然语言处理&#xff08;NLP&#xff09;领域最具影响力的开源平台之一。它不仅提供丰富的预训练模型、强大…

【基于 LangChain 的异步天气查询2】GeoNames实现地区实时气温查询

目录 功能简介 一、创建GeoNames账号 1、进入官网 2、创建账号 二、运行代码 weather_runnable.py main.py 运行结果 功能简介 本文主要通过Langchain&#xff0c;结合GeoNames实现了地区温度的实时查询&#xff0c;并通过GPT-4o对温度进行一段简短的描述。 一、创建Ge…

服务器数据恢复—硬盘坏道导致EqualLogic存储不可用的数据恢复

服务器存储数据恢复环境&故障&#xff1a; 一台EqualLogic某型号存储中有一组由16块SAS硬盘组建的RAID5阵列。上层采用VMFS文件系统&#xff0c;存放虚拟机文件&#xff0c;上层一共分了4个卷。 磁盘故障导致存储不可用&#xff0c;且设备已经过保。 服务器存储数据恢复过程…

JAVA实战开源项目:智能学习平台系统 (Vue+SpringBoot) 附源码

本文项目编号 T 181 &#xff0c;文末自助获取源码 \color{red}{T181&#xff0c;文末自助获取源码} T181&#xff0c;文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…

傅利叶十周年,升级核心战略:“有温度”的具身智能蓝图

5月9日&#xff0c;傅利叶十周年庆典暨首届具身智能生态峰会在上海正式召开。本次大会以“十年共创&#xff0c;具身成翼”为主题&#xff0c;汇聚了来自通用机器人与医疗康复领域的顶尖专家学者、合作伙伴与投资机构&#xff0c;共同探索具身智能在未来十年的技术应用与生态发…

AI安全之对抗样本攻击---FGSM实战脚本解析

一、对抗样本与FGSM的背景 在深度学习安全领域&#xff0c;对抗样本&#xff08;Adversarial Examples&#xff09;因其特殊的生成机制备受关注。2015年ICLR会议收录的里程碑式论文《Explaining and Harnessing Adversarial Examples》中&#xff0c;Goodfellow等学者首次系统…

《Python星球日记》 第50天:深度学习概述与环境搭建

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、什么是深度学习&#xff1f;它与传统机器学习的区别1. 深度学习的定义2. 深…

linux搭建hadoop学习

linux搭建hadoop学习 下载安装包: 海外资源可能需要翻墙或者找国内资源 cd /opt wget https://dlcdn.apache.org/hadoop/common/hadoop-2.10.2/hadoop-2.10.2.tar.gz tar -zxvf hadoop-2.10.2.tar.gz mv hadoop-2.10.2 hadoop配置环境变量 # 在/etc/profile文件中添加下面内…