【C++】封装map和set(红黑树实现)

news2024/11/29 0:51:33

前言:

       前面,我们学习了set和map的用法,这两个容器可以完成查找,排序等操作,后来我们在学习过二叉搜索树的基础上又学习了两种特殊的二叉搜索树——AVL树和红黑树,他们俩可以是效率进一步提高,其实set和map的底层就是由红黑树封装而成的!

       所以,本篇文章我们自己学习用红黑树封装set和map。用红黑树封装,特别是用同一棵红黑树实现封装有一定的难度,其中很多操作(比如复用)我们会第一次尝试,所以大家请跟紧作者的脚步来。

目录

(一)如何复用同一棵红黑树

(二)红黑树的改造流程(为封装做铺垫)

1、结点的定义

2、模拟实现结点的比较大小

3、改造后的红黑树

4、map和set迭代器的模拟实现

4.1模版参数的构成

4.2begin()和end()的模拟实现

4.3operator* 和 operator->模拟实现

4.4 operator++ 和 operator--模拟实现

4.5 operator== 和 operator!=模拟实现

4.6迭代器代码详解

 (三)封装map和set


(一)如何复用同一棵红黑树

前提疑问:

在我们这所有的之前我们知道,map和set这两个容器都是用红黑树来实现的,那么就有了接下来的问题。

  • map和set都是用的同一棵红黑树复用的吗
  • 或者这两个容器各自使用一棵红黑树吗

其实在前言中我们就知道了,根据STL的设计理念和泛型编程的理念,我们不会冗余的写两课红黑树,而是用一棵红黑树实现复用的效果。

我们调用STL库中的原码来看:

我们发现:

  • STL库中的原码也确实调用的同一棵红黑树
  • 他们实现的都是key_value模型

我们设set中存放结点的值是K,map中存放的节点是pair<key,value>键值对:

  • 对于set而言,底层红黑树的模版就是RBTree<K, K>
  • 对于map而言,底层红黑树的模版就是RBTree<K, pair<K, V>>

这时我们就有了另一个疑问,两个模板参数的第一个Key,不能省略掉吗??

  • 首先,答案肯定是不能的
  • 那么原因又是什么呢?

因为map的这个类中,无论怎么省略都会有一个查找函数要单独用到Key

如果第一个Key删去了,那么map和set的查找函数就没法统一实现了,违背了我们一开始泛型编程的思想。

综上所述:

  • map和set都是用了,Key_value模型
  • set中的K是K,V也是K
  • map中的K是K,V是pair<K,V>
  • 并且模板参数中第一个K都不能省

(二)红黑树的改造流程(为封装做铺垫)

1、结点的定义

这里由于set和map存放的结点一个是Key一个是pair<Key,Value>,所以我们使用模版,把存放的结点泛化。

2、模拟实现结点的比较大小

  • 上述提到,在模拟实现中,map和set我们复用同一棵红黑树的时候都是用的是Kye_value的结构
  • 但是红黑树中的数据比较又是Key值的比较,而现在我们用的则是pair的比较
  • 虽然编译上是可以通过但是真的就是我们所想要的吗?

pair的比较大小:

很显然这种比较规则不是我们所想要的,并且map和set想要取到用来比较的数据是不同的。 

为了取到我们想要的数据,我们引入了仿函数:

  • 根据map和set的需求不同
  • 我们在红黑树中新引入了一个模板参数KeyOfT

 

引入KeyOfT模板参数后,我们想要不同方式的比较方法只需要在set和map的封装中给出属于他们自己的比较仿函数。

map中的仿函数: 

set中的仿函数:

使用方法:

 此时前面所说的仿函数的便利之处就体现出来了。

3、改造后的红黑树

具体代码:

//红黑树的实现
//KeyOfT --> 支持取出T对象中key的仿函数
template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef __RBTreeIterator<T, T&, T*> iterator;
	typedef __RBTreeIterator<T, const T&, const T*> const_iterator;

	//构造 拷贝构造 赋值 和析构 跟搜索树实现方式是一样的

	//迭代器中序遍历,要找最左结点
	iterator Begin()
	{
		Node* subLeft = _root;
		while (subLeft && subLeft->_left)
		{
			subLeft = subLeft->_left;
		}

		//树的迭代器用结点的指针就可以构造
		return iterator(subLeft);
	}

	iterator End()
	{
		return iterator(nullptr);
	}

	const_iterator Begin() const
	{
		Node* subLeft = _root;
		while (subLeft && subLeft->_left)
		{
			subLeft = subLeft->_left;
		}

		//树的迭代器用结点的指针就可以构造
		return const_iterator(subLeft);
	}

	const_iterator End() const
	{
		return const_iterator(nullptr);
	}

	pair<iterator, bool> Insert(const T& data)
	{
		//1、搜索树的规则插入
		//2、看是否违反平衡规则,如果违反就需要处理:旋转
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK; //根节点是黑色
			return make_pair(iterator(_root), true);
		}

		KeyOfT kot;

		Node* parent = nullptr;
		Node* cur = _root;

		while (cur)
		{
			if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return make_pair(iterator(cur), false);
			}
		}

		//找到符合规则的位置之后再插入
		cur = new Node(data); 
		Node* newnode = cur;
		cur->_col = RED;

		if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

		//三叉链的链接 -- 链上父节点
		cur->_parent = parent;

		//存在连续红色结点
		while (parent && parent->_col == RED)
		{
			//理论而言,祖父是一定存在的,父亲存在且是红不可能是根(根一定是黑的)
			Node* grandfather = parent->_parent;
			assert(grandfather);

			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				//情况一:(叔叔存在且为红)
				if (uncle && uncle->_col == RED)
				{
					//祖父和叔叔变成黑色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二:(叔叔不存在 or 叔叔存在且为黑)
				else
				{
					//单旋
					//	   g
					//	 p
					// c
					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//双旋
					//    g
					//  p
					//    c
					else
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
			//无论父亲和叔叔是左是右都是一样的
			//grandfather->_right == parent;
			else
			{
				Node* uncle = grandfather->_left;
				//情况一:
				if (uncle && uncle->_col == RED)
				{
					//祖父和叔叔变成黑色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					//单旋
					// g
					//   p
					//     c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//双旋
					//  g
					//    p
					//  c
					else
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		//父亲为空就出循环,将根节点设置成黑色
		_root->_col = BLACK;

		return make_pair(iterator(newnode), true);
	}
}

插入(Insert)和寻找(Find)代码:
 


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

		return nullptr;
	}

	pair<itertaor, bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;

			return make_pair(itertaor(_root), true);
		}

		KeyOfT kot;
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return make_pair(itertaor(cur), false);
			}
		}

		cur = new Node(data);
		Node* newnode = cur;
		if (kot(parent->_data) > kot(data))
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				// 情况1:u存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 情况2+3:u不存在/u存在且为黑,旋转+变色
				{
					//     g
					//   p   u
					// c 
					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//     g
						//   p   u
						//     c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						//parent->_col = RED;
						grandfather->_col = RED;
					}

					break;
				}
			}
			else // (grandfather->_right == parent)
			{
				//    g
				//  u   p
				//        c
				Node* uncle = grandfather->_left;
				// 情况1:u存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 情况2+3:u不存在/u存在且为黑,旋转+变色
				{
					//    g
					//  u   p
					//        c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						//    g
						//  u   p
						//    c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;

		return make_pair(itertaor(newnode), true);;
	}

4、map和set迭代器的模拟实现

上面改造后的红黑树中已经涉及到了迭代器,这里我们来模拟实现。

备注:

  • T:数据
  • Ref:引用
  • Ptr:指针
  • Sef:iterator本身

4.1模版参数的构成

要想实现同一棵红黑树的复用,模版参数的构成极为重要。

之前我们也遇到过相似的情况,我们这里的实现方法参看之前。

迭代器的实现中:

  • 涉及到了*解引用操作,返回的是数据的引用;
  • 涉及到了->指针操作,返回的是数据的地址;
  • 涉及到++,--操作,返回的是操作后迭代器本身的位置。

但是以上的行为,我们都涉及结点,所以结点的数据类型也尤为重要

综上,模版参数的构成如下图:

4.2begin()和end()的模拟实现

  • 因为是二叉搜索树,为了更有顺序,所以我们采取的是中序遍历。
  • 那么中序遍历Begin()就应该是最左结点
  • 我们实现的版本中End()定义为空是(nullptr)

ps:

而库中的红黑树则是设计了一个哨兵位的头结点:

所以我们实现的只是简化版本。

4.3operator* 和 operator->模拟实现

就是正常的运用operator,但是operator->和list中讲的一样,返回的是地址的原因是为了连续访问(连续的operator->会优化成一个->)

4.4 operator++ 和 operator--模拟实现

这里迭代器的++和- - 需要分类一下,分别是:前置++,- - 、后置++,- -

前置++ :
因为我们是中序遍历,我们访问完自己之后,下一个该访问哪一个结点?

我们观察任何一棵二叉树都能看出,无非是下面两种情况:

分如下两种情况:(重点)

  • 右子树为空:找孩子是父亲的左的那个祖先节点,否则继续往上走,直到空(nullptr)
  • 右子树为非空:找右子树的最左节点

这一过程有点类似于递归思想,但是是非递归实现的

代码实现:

 前置-- :

有了上面的思路,我们联想一下,现在需要看当前位置左子树是否是空。

前置- -就是倒着走,同样还是对当前位置分两种情况:(重点)

  1. 左子树为空:找孩子是父亲的右的那个祖先节点,否则继续往上走,直到空(nullptr)
  2. 左子树为非空:找左子树的最右节点

只要前置++理解了,那么前置- -完全就是前置++倒过来走一遍。

后置++、后置- - :

和之前实现容器的得带器一样,我们这里直接复用即可:

4.5 operator== 和 operator!=模拟实现

有了之前封装迭代器的经验,这两个的视线还是比较容易得:

4.6迭代器代码详解

template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	Node* _node;

	typedef __RBTreeIterator<T, Ref, Ptr> Self;

	__RBTreeIterator(Node* node)
		:_node(node)
	{}

	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->()
	{
		return &_node->_data;
	}

	Self& operator++()
	{
		if (_node->_right == nullptr)
		{
			//找祖先里面,孩子是父亲左的那个
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && parent->_right == cur)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}
		else
		{
			//右子树的最左结点
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}

			//左为空
			_node = subLeft;
		}
		return *this;
	}

	Self& operator--()
	{
		if (_node->_left == nullptr)
		{
			//找祖先里面,孩子是父亲
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}
		else
		{
			//左子树的最右结点
			Node* subRight = _node->_left;
			while (subRight->_right)
			{
				subRight = subRight->_right;
			}

			_node = subRight;
		}

		return *this;
	}

	Self operator++(int)
	{
		Self tmp(*this);

		++(*this);

		return tmp;
	}

	Self operator--(int)
	{
		Self tmp(*this);
		
		--(*this);

		return tmp;
	}

	bool operator!=(const Self& s) const
	{
		return _node != s._node;
	}

	bool operator==(const Self& s) const
	{
		return _node == s._node;
	}
};

 (三)封装map和set

有了上面的红黑树的改装,我们这里的对map和set的封装就显得很得心应手了。

封装主要是把map和set的基本操作封装在一个类中。

map的封装:

template<class K, class V>
class map
{
	//定义一个内部类
	struct MapKeyOfT
	{
		const K& operator()(const pair<K, V>& kv)//operator() 可以像函数一样去使用
		{
			return kv.first;
		}
	};

public:
	typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;
	typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::const_iterator const_iterator;

	iterator begin()
	{
		return _t.Begin();
	}

	iterator end()
	{
		return _t.End();
	}

	pair<iterator, bool> insert(const pair<K, V>& kv)
	{
		return _t.Insert(kv);
	}

	V& operator[](const K& key)
	{
		pair<iterator, bool> ret = insert(make_pair(key, V()));
		return ret.first->second;
	}
private:
	RBTree<K, pair<K, V>, MapKeyOfT> _t;
};

这里map中的operator[ ]我们知道其原理之后,模拟实现就非常方便,直接调用插入函数,控制好参数和返回值即可。

对set的封装:

template<class K>
class set    
{
	struct SetKeyOfT
	{
		const K& operator()(const K& key)//operator() 可以像函数一样去使用
		{
			return key;
		}
	};
public:

	//加上typename告诉编译器这是个类型,类模板实例化了再去取
	typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
	typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

	iterator begin() const
	{
		return _t.Begin();
	}

	iterator end() const
	{
		return _t.End();
	}

	pair<iterator, bool> insert(const K& key)
	{
		//pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);
		auto ret = _t.Insert(key);
		return pair<iterator, bool>(iterator(ret.first._node), ret.second);
	}

	iterator find(const K& key)
	{
		return _t.Find(key);
	}

private:
	RBTree<K, K, SetKeyOfT> _t;
};

附详细代码:——》红黑树封装map和set

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

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

相关文章

stm32f103rct6系统结构框图

一、其中&#xff0c;APB1总线和APB2总线的区别有&#xff1a; 1、时钟频率&#xff1a; APB1是低速总线&#xff08;一般为36M&#xff09;&#xff0c;这意味着APB1总线上的外设运行在较低的时钟速度下&#xff0c;适合处理一些低速外设&#xff0c;如USART、I2C等。 APB2…

工作新时代,腾讯轻联塑造高效办公未来

腾讯轻联&#xff1a;开启便捷、高效的集成新纪元 ⭐ 写在前面⭐ 使用模板快速起步⭐ 自定义流程初体验⭐ 无与伦比的集成强者⭐ 写在最后 ⭐ 写在前面 在当今竞争激烈的商业环境中&#xff0c;提高企业的办公效率和工作流程自动化变得至关重要。腾讯轻联&#xff0c;作为新一…

对于pytorch和对应pytorch网站的探索

一、关于网站上面的那个教程: 适合PyTorch小白的官网教程&#xff1a;Learning PyTorch With Examples - 知乎 (zhihu.com) 这个链接也是一样的&#xff0c; 总的来说&#xff0c;里面讲了这么一件事: 如果没有pytorch的分装好的nn.module用来继承的话&#xff0c;需要设计…

营销邮件主题怎么写?编写邮件主题的技巧?

如何创建营销邮件主题&#xff1f;制作EDM邮件主题的方法策略&#xff1f; 营销邮件主题在整个营销邮件中起着至关重要的作用&#xff0c;它是吸引读者打开邮件的第一步。蜂邮EDM将分享一些关于如何撰写令人惊艳的营销邮件主题的技巧&#xff0c;帮助您吸引更多的目标受众。 …

OpenCV 08(图像滤波器 算子)

一、 索贝尔(sobel)算子 边缘是像素值发生跃迁的位置&#xff0c;是图像的显著特征之一&#xff0c;在图像特征提取&#xff0c;对象检测&#xff0c;模式识别等方面都有重要的作用。 人眼如何识别图像边缘? 比如有一幅图,图里面有一条线,左边很亮,右边很暗,那人眼就很容易…

【Spring面试】Spring事务(图文详解)

概念 一个事务包含两个及两个以上的步骤 Spring是没法提供事务的&#xff0c;Spring事务的本质其实就是数据库对事务的支持&#xff0c;没有数据库的事务支持&#xff0c;spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。 事务…

Vuex仓库的创建

vuex 的使用 - 创建仓库 文章目录 vuex 的使用 - 创建仓库1.安装 vuex2.新建 store/index.js 专门存放 vuex3.创建仓库 store/index.js4 在 main.js 中导入挂载到 Vue 实例上5.测试打印Vuex 1.安装 vuex 安装vuex与vue-router类似&#xff0c;vuex是一个独立存在的插件&#x…

容器编排学习(三)端口映射与Harber镜像仓库介绍

一 对外发布服务&#xff08;端口映射&#xff09; 1 概述 新创建容器的IP 地址是随机的 容器在重启后每次 IP 都会发生变化 容器服务只有宿主机才能访问 如何才能使用容器对外提供稳定的服务? 容器端口可以与宿主机的端口进行映射绑定 从而把宿主机变成对应的服务&a…

Revit 几何体的三种上层应用:特征造型、体量和纯粹几何

排除掉墙梁板柱这些和建筑各专业相关的构件&#xff0c;Revit 的上层应用中&#xff0c;有三类和几何相关的应用&#xff0c;特征造型、体量和纯粹几何。这也是通常三维建模软件的几种基础建模组织形式。 特征造型 几何特征造型是一种设计方法&#xff0c;它强调使用几何学原理…

电子产品CE认证申请,CE-RED认证

CE认证&#xff0c;即只限于产品不危及人类、动物和货品的安全方面的基本安全要求&#xff0c;而不是一般质量要求&#xff0c;协调指令只规定主要要求&#xff0c;一般指令要求是标准的任务。 构成欧洲指令核心的"主要要求"&#xff0c;在欧共体1985年5月7日的&…

亚马逊,速卖通,国际站卖家自己做测评补单有什么价值,怎么做?

对于卖家而言&#xff0c;测评资源确实是一项宝贵的财富。通过自养号和广告相结合的策略&#xff0c;可以快速有效地提升产品销量、增加转化率以及提高Listing的权重。这是一个很好的方法来优化产品在平台上的表现 现在很多卖家都选择自己养号下单&#xff0c;自己做测评 1.可…

【AI】机器学习——线性模型(逻辑斯蒂回归)

文章目录 3.3 逻辑斯蒂回归3.3.1 逻辑回归介绍对数几率函数sigmod函数几率 3.3.2 逻辑回归模型3.3.3 参数求解逻辑斯蒂回归策略 3.3.4 损失函数3.3.5 应用&#xff1a;语句情感判断3.3.6 多角度分析逻辑回归信息论角度数学角度与朴素贝叶斯对比 3.3.7 从二分类到多分类问题多次…

深度学习实战51-基于Stable Diffusion模型的图像生成原理详解与项目实战

大家好,我是微学AI,今天给大家介绍一下深度学习实战51-基于Stable Diffusion模型的图像生成原理详解与项目实战。大家知道现在各个平台发的漂亮小姐姐,漂亮的图片是怎么生成的吗?这些生成的底层原理就是用到了Stable Diffusion模型。Stable Diffusion是一种基于深度学习的图…

《Chain-of-Thought Prompting Elicits Reasoning in Large Language Models》全文翻译

《Chain-of-Thought Prompting Elicits Reasoning in Large Language Models》- Chain-of-Thought Prompting Elicits Reasoning in Large Language Models 论文信息摘要1. 介绍2. 思维链提示3. 算术推理3.1 实验设置3.2 结果3.3 消融研究3.4 思想链的稳健性 4. 常识推理5. 符号…

花见Live Wallpaper Themes 4K Pro for mac(4k视频壁纸)

如果你希望让自己的Mac桌面焕发活力&#xff0c;那么Live Wallpaper & Themes 4K Pro正是一款值得尝试的软件。它提供了丰富的超高清4K动态壁纸和主题&#xff0c;可以让你轻松打造出个性化的桌面环境。 这款软件拥有众多令人惊叹的功能。其中最值得一提的是&#xff0c;它…

项目经验分享|openGauss 刘昱娴:对未知葆有好奇与坚持

开源之夏 项目经验分享 2023 #02 # 项目基本信息 项目名称&#xff1a;分页器组件封装 项目导师&#xff1a;王杏 项目简述与产出要求&#xff1a;分页器组件封装 1. 完成分页器组件封装&#xff1b;支持移动端和pc端 2. 适配Chrome、Safari、Firefox、Edge 等主流浏览器…

分类预测 | MATLAB实现PCA-BiLSTM(主成分双向长短期记忆神经网络)分类预测

分类预测 | MATLAB实现PCA-BiLSTM(主成分双向长短期记忆神经网络)分类预测 目录 分类预测 | MATLAB实现PCA-BiLSTM(主成分双向长短期记忆神经网络)分类预测预测效果基本介绍程序设计参考资料致谢 预测效果 基本介绍 分类预测 | MATLAB实现PCA-BiLSTM(主成分双向长短期记忆神经网…

系列三、Linux中安装Nginx

一、准备工作 1.1、确保gcc安装成功 如果没有安装gcc执行./configure将会报错。 # 使用如下指令安装gcc&#xff1a;两个都要安装 yum -y install gcc yum -y install gcc-c 1.2、下载nginx1.12.2 http://nginx.org/en/download.html 1.3、下载pcre-8.3.7.tar.gz 1.3.…

Qt应用程序连接达梦数据库-飞腾PC麒麟V10

目录 前言1 安装ODBC1.1 下载unixODBC源码1.2 编译安装1.4 测试 2 编译QODBC2.1 修改 qsqldriverbase.pri 文件2.2 修改 odbc.pro 文件2.3 编译并安装QODBC 3 Qt应用程序连接达梦数据库测试4 优化ODBC配置&#xff0c;方便程序部署4.1 修改pro文件&#xff0c;增加DESTDIR 变量…

C++ std::pair and std::list \ std::array

std::pair<第一个数据类型, 第二个数据类型> 变量名 例如&#xff1a; std::pair<int, string> myPair; myPair.first;拿到第一个int变量 myPair.second拿到第二个string变量 std::pair需要引入库#include "utility" std::make_pair() 功能制作一个…