机械转码日记【26】二叉搜索树

news2024/11/15 13:35:13

目录

前言 

1.二叉搜索数的概念

2.二叉搜索树的实现

2.1 基本架构

2.2二叉搜索树的插入

2.2.1普通版本 

2.2.2递归版本 

2.3二叉搜索树的查找

2.3.1普通版本

2.3.2递归版本

2.4二叉搜索树的删除 

 2.4.1普通版本代码

2.4.2递归版本代码 

2.5搜索树的析构函数

2.6搜索树的拷贝构造 

2.7搜索树的赋值重载

3.二叉搜索树的应用


前言 

二叉搜索树是一种特殊的树形结构 ,二叉搜索树又称二叉排序树,它与我们前面学习的堆机械转码日记【3】同属树形结构,但它不是用来排序的,而是用来搜索的。

1.二叉搜索数的概念

 二叉搜索树是具有以下性质的二叉树:

  1. 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  2. 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  3. 它的左右子树也分别为二叉搜索树

2.二叉搜索树的实现

为了更好的了解二叉搜索树的原理和构造,我们直接来实现一个二叉搜索树

2.1 基本架构

想要构建一个搜索二叉树,首先我们需要一个树的节点的结构,它里面有节点的值,以及指向左右子节点的指针:

template<class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;

	K _key;

	BSTreeNode(const K& key)
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
	{

	}
};

然后再封装一个二叉搜索树类,并且将我们刚刚创建的节点作为二叉搜索树的成员: 

template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;//方便使用,不用写BSTreeNode<K>那么长
private:
    //成员变量
	Node* _root=nullptr;
}

2.2二叉搜索树的插入

增删查改,都是数据结构的基本操作,一般二叉搜索树不支持修改(因为一修改可能大小关系就变了),那么我们先来实现增,也就是插入操作。

二叉搜索树的插入逻辑为:

  1. 判断根节点是否为空,如果为空,直接构造一个插入值的根节点就行
  2. 如果根节点非空,那么就需要通过大小判断来找寻正确的插入位置,比根节点大往右子树走,比根节点小往左子树走。(注意找的过程中要记录父亲节点的位置,方便链接父节点和插入的节点)
  3. 链接父节点和子节点

2.2.1普通版本 

	bool Insert(const K& key)
	{
		//判断根节点是否为空,如果为空,直接构造一个插入值的根节点就行
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}

		//非空,开始找合适的插入位置
		//定义父节点和目标节点
		Node* parent = nullptr;
		Node* cur = _root;
		//开始寻找合适插入位置
		while (cur != nullptr)
		{
			if (cur->_key < key)//插入的值比根节点大,往右子树走
			{
				parent = cur;
				cur = cur->_right;//更新子节点和父节点
			}
			else if (cur->_key > key)//插入的值比根节点小,往左子树走
			{
				parent = cur;
				cur = cur->_left;//更新子节点和父节点
			}
			else
			{
				return false;//如果插入的值和根节点相等,则不允许插入,返回false
			}
		}

		cur = new Node(key);//将节点的键值赋给cur
		//链接子节点个父节点
		if (parent->_key < key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
	}

2.2.2递归版本 

 除了上面的循环版本,我们还可以写一个递归版本来实现插入操作

///递归版本//
	bool InsertR(const K& key)
	{
		return _InsertR(_root, key);
	}

	//子函数:
	//引用传参是精髓
	//使用引用,这时候的root就是上一个节点的左右子树的别名
    //修改root的同时也会修改上一个子树的左右节点(完成链接操作)
    //因为我们修改的是指针,所以要实现址传递,也可以用二级指针来完成这个操作,原理相同
	bool _InsertR(Node*& root, const K& key)
	{
		//判断根节点是否为空,如果为空,直接构造一个插入值的根节点就行
		//递归到最后,要插入的那个合适的位置也是空节点
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}

		//插入值比根节点大,往右子树插入
		if (root->_key < key)
			return _InsertR(root->_right, key);
		//插入值比根节点小,往左子树插入
		else if (root->_key > key)
			return _InsertR(root->_left, key);
		//插入值和根节点相等,不允许插入
		else
			return false;
	}

2.3二叉搜索树的查找

二叉搜索树的查找的时间复杂度最高为0(N),此种情况为二叉搜索数的插入为有序,最低为O(logn),当二叉搜索树为完全二叉树的时候会出现这种情况。

其实在插入的时候我们就已经嵌入了查找的逻辑了,在这里再说一遍。

实现二叉搜索树的查找的逻辑为:

  1. 从根节点开始查找
  2. 如果查找的目标值比根节点大,往右子树找,比根节点小就往左子树找。
  3. 找到最后不为空就找到了,为空就找不到

2.3.1普通版本

bool Find(const K& key)//搜索的时间复杂度:O(N),当有序时,为O(n)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else
			{
				return true;
			}
		}
		return false;
	}

2.3.2递归版本

与上面插入的递归版本不同,由于查找并不需要修改节点指针,只需要用普通指针传参就可以,不需要用引用传参。

    ///递归版本//
	bool FindR(const K& key)
	{
		return _FindR(_root, key);
	}
    //子函数
	bool _FindR(Node* root, const K& key)
	{
		if (root == nullptr)
			return false;

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

2.4二叉搜索树的删除 

要说二叉搜索树的增删查,逻辑最复杂最难搞的还得是删除,它的逻辑是:

  • 先找到需要删除的元素,如果没找到,就返回false,找到了,需要分以下情况进行处理:
  1. 要删除的结点无孩子结点
  2. 要删除的结点只有左孩子结点
  3. 要删除的结点只有右孩子结点
  4. 要删除的结点有左、右孩子结点

看起来有待删除节点有4种情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:

  • 要删除的结点只有左孩子结点或无孩子节点,则删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点——我们把它归类为直接法删除
  • 要删除的结点只有右孩子结点或无孩子节点,则删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点——我们把它归类为直接法删除
  • 要删除的结点有左、右孩子结点,则找到它左子树中的最大子节点或右子树的最小子节点,与要删除的节点的值替换,再来处理该节点的删除问题——我们把它归类为替代法删除

 

 2.4.1普通版本代码

	//搜索树最大的问题是删除
	bool Erase(const K& key)
	{
		Node* parent = nullptr;
		Node* cur = _root;
		//找到这个节点和它的父亲
		while (cur != nullptr)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			//等于说明找到了
			else
			{
				//分	情况
				//一个孩子,分为左孩子和右孩子,if和else if值执行一个,小心root节点,没孩子也属于一个孩子的特殊点,也可以实现
				if (cur->_left == nullptr)//只有右孩子
				{
					//cur为root需要特殊处理
					if (cur == _root)
					{
						_root = cur->_right;
					}
					//再分cur是parent的什么孩子
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_right;//和右孩子链接上
						}
						else if (parent->_right == cur)
						{
							parent->_right = cur->_right;//和右孩子链接上
						}

					}
				}
				else if (cur->_right == nullptr)//只有一个左孩子
				{
					//cur为root需要特殊处理
					if (cur == _root)
					{
						_root = cur->_left;
					}
					//再分cur是parent的什么孩子
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_left;//和左孩子链接上
						}
						else if (parent->_right == cur)
						{
							parent->_right = cur->_left;//和左孩子链接上
						}
					}
				}
				else//两个孩子
				{
					//找右子树的最小节点
					Node* minnode_parent = cur;
					Node* minnode = cur->_right;
					while (minnode->_left != nullptr)
					{
						minnode_parent = minnode;
						minnode = minnode->_left;
					}
					swap(minnode->_key, cur->_key);
					//Erase(minnode->_key);//交换之后不符合搜索树的规则,会找不到
					if (minnode_parent->_left == minnode)
					{
						minnode_parent->_left = minnode->_right;//链接子节点
					}
					else
					{
						minnode_parent->_right = minnode->_right;
					}

					delete minnode;//删掉需要删除的节点
				}
				return true;
			}
		}
		return false;
	}

2.4.2递归版本代码 

	bool EraseR(const K& key)
	{
		return _EraseR(_root, key);
	}

    //子函数
	bool _EraseR(Node*& root, const K& key)
	{
		if (root == nullptr)
			return false;

		if (root->_key < key)
		{
			return _EraseR(root->_right, key);
		}
		else if (root->_key > key)
		{
			return _EraseR(root->_left, key);
		}
		else
		{
			Node* del = root;
			// 删除
			//由于是引用传参,root既是父亲指向子节点指针的别名,同时也是要删除节点指针的别名
			if (root->_left == nullptr)//左为空,
			{
				root = root->_right;//链接父亲和要删除节点的孩子
			}
			else if (root->_right == nullptr)//右为空
			{
				root = root->_left;//链接父亲和要删除节点的孩子
			}
			//找右子树的最小节点
			else
			{
				Node* minRight = root->_right;
				while (minRight->_left)
				{
					minRight = minRight->_left;
				}

				swap(root->_key, minRight->_key);//替换法删除

				return _EraseR(root->_right, key);//到右子树删除,一定能找到
			}

			delete del;
			return true;
		}
	}

2.5搜索树的析构函数

析构函数其实很简单,我们只需要递归释放树的每个节点就可以:

	~BSTree()
	{
		DestoryTree(_root);
		_root = nullptr;
	}

	void DestoryTree(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		DestoryTree(root->_left);
		DestoryTree(root->_right);
		delete root;
	}

2.6搜索树的拷贝构造 

如果我们自己不写拷贝构造,编译器会生成默认的拷贝构造,这个时候生成的是浅拷贝函数,浅拷贝会有造成野指针的风险,因此我们必须显式写出一个深拷贝构造,与析构函数递归类似,我们也可以写一个递归拷贝构造:

	BSTree(const BSTree<K>& t)
	{
		_root = CopyTree(t._root);
	}

	Node* CopyTree(Node* root)
	{
		if (root == nullptr)
			return nullptr;

		Node* copyNode = new Node(root->_key);
		copyNode->_left = CopyTree(root->_left);
		copyNode->_right = CopyTree(root->_right);

		return copyNode;
	}

2.7搜索树的赋值重载

按我们前面几篇博客写赋值重载的现代写法,我们可以很容易写出搜索树的赋值重载,我们只需要在传参过程中深拷贝一个搜索树对象,并将被赋值对象和拷贝的搜索树对象的根节点的值交换,函数调用完之后拷贝的搜索树对象就自动析构了。

	BSTree<K>& operator=(BSTree<K> t)
	{
		swap(_root, t._root);
		return *this;
	}

3.二叉搜索树的应用

 搜索有两种模型,K模型和KV模型

   1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜      索到的值。

    比如:查找一篇文章中是否有错误的单词,具体方式如下:

  • 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误

K模型简而言之就是查找关键字在不在,如扫车牌系统,就是搜索看看也没有车牌的关键字,有就抬杆。 

    2.KV模型:每一个关键码key,都有与之对应的值Value,即的键值对。该种方式在现实        生活中非常常见:

  • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英 文单词与其对应的中文就构成一种键值对
  • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出 现次数就是就构成一种键值对

不难看出,我们刚刚在上面实现的二叉搜索树是K模型,那么我们可不可以改造一下,将上面的K模型改成KV模型呢?

	template<class K, class V>
	class BSTree
	{
		typedef BSTreeNode<K, V> Node;
	public:

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}

		///
		Node* FindR(const K& key)
		{
			return _FindR(_root, key);
		}

		bool InsertR(const K& key, const V& value)
		{
			return _InsertR(_root, key, value);
		}

		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}

	private:
		bool _EraseR(Node*& root, const K& key)
		{
			if (root == nullptr)
				return false;

			if (root->_key < key)
			{
				return _EraseR(root->_right, key);
			}
			else if (root->_key > key)
			{
				return _EraseR(root->_left, key);
			}
			else
			{
				Node* del = root;
				// 删除
				if (root->_left == nullptr)
				{
					root = root->_right;
				}
				else if (root->_right == nullptr)
				{
					root = root->_left;
				}
				else
				{
					Node* minRight = root->_right;
					while (minRight->_left)
					{
						minRight = minRight->_left;
					}

					swap(root->_key, minRight->_key);

					return _EraseR(root->_right, key);
				}

				delete del;
				return true;
			}
		}

		bool _InsertR(Node*& root, const K& key, const V& value)
		{
			if (root == nullptr)
			{
				root = new Node(key, value);
				return true;
			}

			if (root->_key < key)
				return _InsertR(root->_right, key, value);
			else if (root->_key > key)
				return _InsertR(root->_left, key, value);
			else
				return false;
		}

		Node* _FindR(Node* root, const K& key)//返回Node*,可以修改value
		{
			if (root == nullptr)
				return nullptr;

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

		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;

			_InOrder(root->_left);
			cout << root->_key << ":" << root->_value << endl;
			_InOrder(root->_right);
		}
	private:
		Node* _root = nullptr;
	};

	void TestBSTree1()
	{
		BSTree<string, string> ECDict;
		ECDict.InsertR("root", "根");
		ECDict.InsertR("string", "字符串");
		ECDict.InsertR("left", "左边");
		ECDict.InsertR("insert", "插入");
		//...
		string str;
		while (cin >> str)  //while (scanf() != EOF)多组输入
		{
			//BSTreeNode<string, string>* ret = ECDict.FindR(str);//写这种也行
			auto ret = ECDict.FindR(str);
			if (ret != nullptr)
			{
				cout << "对应的中文:" << ret->_value << endl;
			}
			else
			{
				cout << "无此单词,请重新输入" << endl;
			}
		}
	}

	void TestBSTree2()
	{
		string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
		//统计水果出现的次数
		BSTree<string, int> countTree;
		for (const auto& str : arr)
		{
			auto ret = countTree.FindR(str);//找到这个水果,
			if (ret == nullptr)//如果为空,说明不存在,则插入,value+1
			{
				countTree.InsertR(str, 1);
			}
			else
			{
				ret->_value++;  //修改value
			}
		}

		countTree.InOrder();//中序遍历打印出水果和它的出现次数
	}
}

在STL的库里面,使用K模型的数据结构是set,使用KV模型的数据结构是map,在后面几篇博客中我们将会学习到它们。

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

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

相关文章

电脑黑屏按什么键恢复?只需要3个键就可以解决黑屏

今天和大家聊一聊电脑黑屏这个问题。相信大家都遇到过电脑黑屏&#xff0c;但是却不知道该如何解决&#xff0c;今天就来给大家分享一些处理方法。如果是电脑黑屏的话&#xff0c;一般情况下&#xff0c;只需要三个键就可以解决问题&#xff0c;电脑黑屏按什么键恢复&#xff1…

【Matplotlib绘制图像大全】(九):Matplotlib使用xticks()修改x轴刻度位置信息

前言 大家好,我是阿光。 本专栏整理了《Matplotlib绘制图像大全》,内包含了各种常见的绘图方法,以及Matplotlib各种内置函数的使用方法,帮助我们快速便捷的绘制出数据图像。 正在更新中~ ✨ 🚨 我的项目环境: 平台:Windows10语言环境:python3.7编译器:PyCharmMatp…

提到Canvas,必须好好唠唠它的图像操作能力

前情提要 接续一下之前对Canvas的探索。本篇分享一下对图像操作的阅读和研究。 日常开发中&#xff0c;时常遇到对图像的处理的场景。精美的图像做为背景或者场景&#xff0c;相对会吸引人。 Canvas图像API十分强大。可以通过Canvas图像API加载图像数据&#xff0c;进行裁剪…

ubuntu篇---ubuntu安装mysql教程

ubuntu篇---ubuntu安装mysql教程一. 首先卸载掉原来的mysql第一步&#xff0c;依次执行下面的语句第2步 清理残留数据第三步 验证原有主机上是否安装mysql&#xff1a;二. 安装mysql三. 修改密码加粗样式ubuntu安装mysql教程 一. 首先卸载掉原来的mysql 第一步&#xff0c;依…

深入理解蓝牙BLE之“扩展广播”

目录 前言&#xff1a; 4.2版本广播&#xff1a; 5.0版本广播&#xff1a; 实现原理&#xff1a; 格式定义&#xff1a; 广播事件类型&#xff1a; 扩展广播&#xff1a; 周期广播&#xff1a; 广播集&#xff1a; HCI接口定义&#xff1a; 4.2版本&#xff1a; 5.…

正式练习的第一个Python功能:加法计算

我本身有着C/C的功底&#xff0c;最近开始自学python&#xff0c;包括网上找教程&#xff0c;买书看。不确定我这种有其他编程语言经验的再学新的语言算不算零基础&#xff0c;总之书就买的《零基础学Python程序设计》。鉴于自己之前已经看过一段时间&#xff0c;这个程序也就不…

[附源码]Python计算机毕业设计SSM开放实验室管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

微服务框架 SpringCloud微服务架构 10 使用Docker 10.3 容器命令介绍

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构10 使用Docker10.3 容器命令介绍10.3.1 容器相关命令10 使用Docker 10.3 …

一类综合的模糊化自适应滑模控制

目录 前言 1.系统描述 2.控制器设计 3.模糊化设计 3.1构造模糊系统 3.2自适应律设计 4仿真分析 4.1仿真系统 4.2仿真结果 前言 上几篇文章分别介绍了模糊化切换增益(也就是模糊化外界扰动d)、模糊化系统部分的不确定项f、模糊化整个切换项&#xff0c;其原理分别为利…

如何制作gif图片?

文章目录一、下载LICEcap【制作gif的工具】&#xff08;按步骤安装即可&#xff09;二、LICEcap的使用录制步骤&#xff1a;三、录制的gif效果展示一、下载LICEcap【制作gif的工具】&#xff08;按步骤安装即可&#xff09; LICEcap是一款简洁易用的动画屏幕录制软件&#xff…

HTML CSS大学生期末网页大作业 DW个人网页设计 人物介绍 历史人物岳飞介绍

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

智慧工地技术方案

二、 系统概述 建筑工地是一个安全事故多发的场所。目前&#xff0c;工程建设规模不断扩大&#xff0c;工艺流程纷繁复杂&#xff0c;如何完善现场施工现场管理&#xff0c;控制事故发生频率&#xff0c;保障文明施工一直是施工企业、政府管理部门关注的焦点。尤其随着社会的…

Elasticsearch_第3章_ elasticsearch_进阶

Elasticsearch_第3章_ elasticsearch_进阶 文章目录Elasticsearch_第3章_ elasticsearch_进阶0.学习目标1.数据聚合1.1.聚合的种类1.2.DSL实现聚合1.2.1.Bucket聚合语法1.2.2.聚合结果排序1.2.3.限定聚合范围1.2.4.Metric聚合语法1.2.5.小结1.3.RestAPI实现聚合1.3.1.API语法1.…

Redis数据结构

一.NoSQL 1.认识NoSQL 关系型数据库&#xff1a;结构化&#xff08;有很多约束&#xff09;&#xff0c;关联的&#xff08;数据库会自己维护数据之间的关联&#xff0c;如外键&#xff09;&#xff0c;SQL查询&#xff08;语法统一&#xff09;&#xff0c;满足事务ACID的特性…

C# Winform 文本面板带滚动条

Winform 中如果需要在一个固定大小的面板中显示一些内容&#xff0c;并且面板能上下拖动&#xff0c;将所有的内容完整的展示&#xff0c;这种需求很常见&#xff0c;下面就演示如何实现的吧 效果&#xff1a; 1.新建一个winform 项目&#xff0c;在界面中拖入一个Panel 将 p…

港科夜闻|香港科技大学校长叶玉如教授,新加坡国立大学曾运雄博士:发现阿尔茨海默病新疗法...

关注并星标每周阅读港科夜闻建立新视野 开启新思维1、香港科技大学校长叶玉如教授、新加坡国立大学曾运雄博士&#xff1a;发现阿尔茨海默病新疗法。由中科院院士、香港科技大学校长叶玉如教授&#xff0c;及新加坡国立大学感染、免疫与炎症研究所的曾运雄博士共同领导的团队研…

rxjs pipeable operators(下)

rxjs pipeable operators&#xff08;下&#xff09; 这一篇主要就是讲 flattening operators&#xff0c;像其他的 pipeable 一样&#xff0c; flattening operators 内部会 subscribe 每一个传进来的 Observable&#xff0c;并且将其返回一个新的 Observable。不过它可以将 …

VsCode + gdb + gdbserver远程调试arm嵌入式linux C/C++程序

基本流程跟我的另一篇文章《VsCode gdb gdbserver远程调试C程序》一样&#xff0c;不一样的是需要重新编译gdb和交叉编译gdbserver。 一、准备工作 sudo apt install libgmp-dev 如果不安装&#xff0c;编译gdb时可能会报错&#xff1a;configure: error: GMP is missing o…

[附源码]计算机毕业设计springboot疫情管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

如何阅读别人的代码

会读好源码,才能写出好代码 而且除了经常写代码&#xff0c;还要保持习惯看看别人是怎么写的&#xff0c;这里我只引出一个话题就是如何阅读别人的代码 。一个工整的代码就好比欣赏一个漂亮的美女一样让人赏心悦目&#xff0c;百看不厌&#xff0c;一个乱糟糟的代码就不忍直视&…