【C++】list类的模拟实现

news2024/11/17 4:54:11

🏖️作者:@malloc不出对象
⛺专栏:C++的学习之路
👦个人简介:一名双非本科院校大二在读的科班编程菜鸟,努力编程只为赶上各位大佬的步伐🙈🙈
在这里插入图片描述

目录

    • 前言
    • 一、list类的模拟实现
      • 1.1 list的主体框架
      • 1.2 无参构造函数
      • 1.3 push_back
      • 1.4 正向迭代器
      • 1.5 反向迭代器
      • 1.6 insert
      • 1.7 erase
      • 1.8 clear
      • 1.9 析构函数
      • 1.10 构造函数
      • 1.11 赋值运算符重载
      • 1.12 empty
      • 1.13 front && back
      • 1.14 完整代码
    • 二、vector与list的对比


前言

本篇文章我们要来模拟实现的是list类,它的底层是用带头结点的双向循环链表实现的。

一、list类的模拟实现

1.1 list的主体框架

既然我们是用双向循环链表实现的,那么每个结点肯定都存储着next、prev与data信息,那么接下来我们就来定义一个类对它的结点进行初始化操作。

template<class T>  // 模板参数T
struct list_node
{
	list_node<T>* _next; 	//list_node<T>* 是类型
	list_node<T>* _prev;
	T _data;

	list_node(const T& val = T())  // 匿名对象初始化
		: _next(nullptr)
		, _prev(nullptr)
		, _data(val)
	{}
};

我们把节点定义好之后,我们就来定义list类了,list类的成员变量只需要一个哨兵位的头结点就可以了。

template <class T>
class list
{
	typedef list_node<T> node;	
private:
		node* _head;	// 哨兵位头节点
};

1.2 无参构造函数

list()
{
	_head = new node;		// 申请一个节点
	_head->_next = _head;   // _head->_next指向自己
	_head->_prev = _head;   // _head->_prev也指向自己
}

1.3 push_back

双向链表的插入和删除都是非常好实现的,因为每个结点都有上一个节点和下一个节点的信息。这里我们要想实现尾插,我们要找到尾结点再改变它的指向就行了,非常的简单这里我就不做过多的赘述了。另外后续在我们实现insert和erase之后全都可以进行复用,这里只是先给大家打个样。

void push_back(const T& x)
{
	node* tail = _head->_prev;
	node* newnode = new node(x);

	tail->_next = newnode;
	newnode->_prev = tail;
	newnode->_next = _head;
	_head->_prev = newnode;
}

1.4 正向迭代器

有了尾插之后我们可以往链表里面插入数据,下面我们想遍历一下链表,我们知道list不支持[]下标访问,原因是因为它是不连续的空间,所以我们必须使用迭代器对它进行遍历。

我们知道在实现vector类(SGI版本)时,我们的迭代器是作为一个原生指针来使用的,而在vector类(P.J.版本)中我们的迭代器是自定义类型对原生指针的封装,但本质上它们都是在模拟指针的行为!!!那么在list类中迭代器到底充当什么角色呢?我们知道迭代器支持++ - -操作这是为了找到后一个数据和前一个数据的位置,对于list而言它是双向链表它的空间是不连续的,假设迭代器是一个原生指针的话,指针++ - -一步取决于指针所指向的类型,对于不连续的空间来说++ - -能否刚好指向下一个位置或者上一个位置一切都是未知数,因此我们的迭代器在list中是对自定义类型原生指针的封装!!!

我们先来看看SGI版本下对正向迭代器的封装源码:

在这里插入图片描述

好了,也许我们有些地方可能有些不太懂,而且标准库的源码采用了非常多的命名替换,这是命名规范的问题,接下来我们模拟实现的时候不采用标准库这种方式,我们尽量的实现简洁易懂些。

最原始的代码

template<class T>
struct __list_iterator
{
	typedef list_node<T> node;
	typedef __list_iterator<T> self;
	node* _node;

	__list_iterator(node* x)  // 初始化结点
		: _node(x)
	{}

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

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

	self& operator++()
	{
		_node = _node->_next;

		return *this;
	}

	self operator++(int)
	{
		__list_iterator tmp = *this;
		_node = _node->_next;

		return tmp;
	}

	self& operator--()
	{
		_node = _node->_prev;

		return *this;
	}

	self operator--(int)
	{
		__list_iterator tmp = *this;
		_node = _node->_prev;

		return tmp;
	}

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

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

这是我们最原始的代码,那么大家知道为什么源码为什么会多出两个模板参数吗?这里我们实现的是正向迭代器,那么我们要实现const正向迭代器版本呢?难道要再去写一个__list_const_iterator类吗?

显然这样出现了大量的代码重复,我们是极其不支持这种实现方式的,所以我们必须想办法让他们之间可以进行复用,我们只需要改变一下返回值类型、参数类型就能实现iterator和const_iterator版本,这里模板参数的作用就体现出来了,我们可以添加一个模板参数,到时候我们可以实例化一份iterator和const_iterator。至于第三个模板参数是为了重载->运算符函数的,它同样的有T*版本和const T*版本。

为什么要重载->运算符?

struct AA
{
	int _a1;
	int _a2;

	AA(int a1, int a2)
		: _a1(a1)
		, _a2(a2)
	{}
};

void test()
{
	list<AA> lt;
	lt.push_back(AA(1, 1));
	lt.push_back(AA(2, 2));
	lt.push_back(AA(3, 3));

	list<AA>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << (*it)._a1 << " " << (*it)._a2 << endl;  
		++it;
	}
	cout << endl;
}

我们可以看到上诉代码对于一个自定义类型要想访问它的成员变量就必须得写成(*it)._a1、(*it)._a2,先*it得到AA对象,再访问它的成员变量,这种写法是不是未免有些麻烦了?我们平常可以直接使用->去访问它的成员变量,就像这段代码我们可以写成it->_a1、it->_a2,但是我们此时未重载->运算符,所以为了方便使用这里我们还需要重载一下->运算符。

对于__list_iterator类我们可以重载->写出下面的代码:

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

但你有没有发现一些奇怪之处??

在这里插入图片描述

好了,关于为什么要重载->运算符这里我们已经讲清楚了,那么为什么这跟添加第三个模板参数有什么关系呢?原因很简单,一个T*版,一个const T*版,添加第三个模板参数Ptr也是为了复用T*版本。

所以最终我们的__list_iterator可以写成这种版本:

template<class T, class Ref, class Ptr>
struct __list_iterator
{
	typedef list_node<T> node;
	typedef __list_iterator<T, Ref, Ptr> self;
	node* _node;

	__list_iterator(node* x)  // 初始化结点
		: _node(x)
	{}

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

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

	self& operator++()
	{
		_node = _node->_next;
		return *this;
	}

	self operator++(int)
	{
		self tmp(*this);
		_node = _node->_next;

		return tmp;
	}

	self& operator--()
	{
		_node = _node->_prev;

		return *this;
	}

	self operator--(int)
	{
		self tmp(*this);
		_node = _node->_prev;

		return tmp;
	}

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

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

};

我们在list类中就可以实例化iterator和const_iterator这两种版本的迭代器,list类中迭代器的定义如下:

typedef list_node<T> node;
public:
	typedef __list_iterator<T, T&, T*> iterator;
	typedef __list_iterator<T, const T&, const T*> const_iterator;

	iterator begin()
	{
		return iterator(_head->_next);
	}
	
	const_iterator begin() const
	{
		return const_iterator(_head->_next);
	}
	
	iterator end()
	{
		return iterator(_head);
	}
	
	const_iterator end() const
	{
		return const_iterator(_head);
	}

我们来进行测试一下:

在这里插入图片描述

我们可以看到对应的过程,当list<int>显式声明模板类时,此时我们的类模板就根据类型实例化出一个具体的类。

在这里插入图片描述

1.5 反向迭代器

我们知道C++追求极致的性能,既然能复用绝不会写出两份差不多的代码,,所以我们实现反向迭代器并不会像正向迭代器那样倒着来,而是去复用正向迭代器!!!

反向迭代器其实也是一种适配器,它可以适配出各种容器的反向迭代器,其中最重要的就是将正向迭代器作为底层结构来封装反向迭代器,反向迭代器 ++ 就复用正向迭代器的 - -,反向迭代器 - - 就复用正向迭代器的 ++。

在这里插入图片描述

我们的反向迭代器既然是作为适配器去使用,那么我们就把它封装到单独的一个类中对它进行模拟实现,并且正向迭代作为它的模板参数进行复用它的功能!!

反向迭代器的模拟实现

// iterator.h
namespace curry
{
	template<class Iterator, class Ref, class Ptr>
	struct ReverseIterator
	{
		typedef ReverseIterator<Iterator, Ref, Ptr> Self;
		Iterator _cur;  // _cur就是一个正向迭代器

		ReverseIterator(Iterator it)  
			: _cur(it)
		{}

		Ref operator*()
		{
			Iterator tmp = _cur;
			--tmp;

			return *tmp;
		}

		Self& operator++()
		{
			--_cur;

			return *this;
		}

		Self operator++(int)
		{
			Self tmp = *this;
			--_cur;

			return tmp;
		}

		Self& operator--()
		{
			++_cur;

			return *this;
		}

		Self operator--(int)
		{
			Self tmp = *this;
			++_cur;

			return tmp;
		}
		
		// 返回当前对象的地址
		Ptr operator->()
		{
			return &(operator*());
		}

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

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

只要知道了反向迭代器与正向迭代器的特性,我们就能够很容易的通过复用正向迭代器的成员函数来实现反向迭代器的成员函数!!同时反向迭代器其实解决了所有的双向迭代器的问题,因为只要将对应容器的正向迭代器作为反向迭代器的模板参数我们就能够对反向迭代器进行复用,所以我们之前的vector类的反向迭代器也能够直接使用它的正向迭代器复用实现!!这是一种非常巧妙的思想!!

1.6 insert

void insert(iterator pos, const T& x)
{
	node* cur = pos._node;   // 当前位置
	node* prev = cur->_prev; // 前一个位置

	node* newnode = new node(x);
	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = cur;
	cur->_prev = newnode;
}

实现了insert接口函数,那么我们的push_back与push_front都是可以复用的。

push_back(int x)

void push_back(const T& x)
{
	insert(end(), x);
}

push_front(int x)

void push_back(const T& x)
{
	insert(begin(), x);
}

list类与vector类的insert不同之处在于list类insert不会导致迭代器失效,因为它的空间的不连续的,并且没有挪动数据造成迭代器失效,所以我们也可以看到它的返回值为void,并不需要放回插入位置的迭代器。

1.7 erase

iterator erase(iterator pos)
{
	assert(pos != end());
	node* prev = pos._node->_prev;
	node* next = pos._node->_next;

	prev->_next = next;
	next->_prev = prev;

	delete pos._node;

	return iterator(next);
}

迭代器失效即迭代器所指向的节点的无效,即该节点被删除了,所以对于list类的erase会导致指向删除节点的迭代器失效,其他迭代器不会受到影响而vector类进行erase会导致当前位置或者后续迭代器失效,所以正确的解决办法是给迭代器重新赋值!!

实现了erase函数接口,pop_back()以及pop_front()就可以进行复用了。

pop_back()

void pop_back()
{
	erase(--end());
}

pop_front()

void pop_front()
{
	erase(begin());
}

1.8 clear

void clear()
{
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);	// erase返回下一个位置的迭代器
	}
}

clear释放链表中的结点,_head哨兵位头结点除外。

1.9 析构函数

~list()
{
	clear();
	delete _head;
	_head = nullptr;
}

析构函数的作用是释放所有结点,我们可以先调用clear依次释放链表中的结点,最后再释放头结点。

1.10 构造函数

传统写法

void empty_init()
{
	// 创建并初始化哨兵位头节点
	_head = new node;
	_head->_prev = _head;
	_head->_next = _head;
}

// 拷贝构造传统写法 lt2(lt1)
list(const list<T>& lt)
{
	empty_init();

	for (auto& e : lt)	// 加引用避免自定义类型的拷贝构造
	{
		push_back(e);
	}
}

现代写法

template <class Iterator>  // 双向迭代器类型构造
list(Iterator first, Iterator last)
{
	empty_init();
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

void swap(list<T>& tmp)
{
	std::swap(_head, tmp._head);	// 交换哨兵位的头节点
}

// 拷贝构造现代写法 lt2(lt1)
list(const list<T>& lt)
{
	empty_init();	
	list<T> tmp(lt.begin(), lt.end());	// 迭代器区间初始化
	swap(tmp);	
}

1.11 赋值运算符重载

传统写法

list<T>& operator=(const list<T>& lt)
{
	if (this != &lt)	// 防止自己给自己赋值
	{
		clear();	// 清理数据
		for (auto& e : lt)
		{
			push_back(e);
		}
	}
	
	return *this;
}

现代写法

list<T>& operator=(list<T> lt)
{
	swap(lt);
	return *this;
}

一些常用的函数接口就讲到这里了,还有一些简单的函数接口读者下来也可以自己去尝试实现一下。

1.12 empty

bool empty()
{
	return _head->_next == _head &&
		_head->_prev == _head;
}

1.13 front && back

T& front()
{
	assert(!empty());
	return *begin();
}

const T& front() const
{
	assert(!empty());
	return *begin();
}

T& back()
{
	assert(!empty());
	return *(--end());
}

const T& back() const
{
	assert(!empty());
	return *(--end());
}

1.14 完整代码

// list.h

#include "iterator.h"

namespace curry
{
	template<class T>
	struct list_node
	{
		list_node<T>* _next;
		list_node<T>* _prev;
		T _data;

		list_node(const T& val = T())
			: _next(nullptr)
			, _prev(nullptr)
			, _data(val)
		{}
	};

	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef list_node<T> node;
		typedef __list_iterator<T, Ref, Ptr> self;
		node* _node;

		__list_iterator(node* x)  // 初始化结点
			: _node(x)
		{}

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

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

		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		self operator++(int)
		{
			self tmp(*this);
			_node = _node->_next;

			return tmp;
		}

		self& operator--()
		{
			_node = _node->_prev;

			return *this;
		}

		self operator--(int)
		{
			self tmp(*this);
			_node = _node->_prev;

			return tmp;
		}

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

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

	};

	template<class T>
	class list
	{
		typedef list_node<T> node;
	public:
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;
		typedef ReverseIterator<iterator, T&, T*> reverse_iterator;
		typedef ReverseIterator<const_iterator, const T&, const T*> const_reverse_iterator;

		iterator begin()
		{
			return iterator(_head->_next);
		}

		reverse_iterator rbegin()
		{
			return reverse_iterator(_head);
		}

		const_iterator begin() const
		{
			return const_iterator(_head->_next);
		}

		const_reverse_iterator rbegin() const
		{
			return const_reverse_iterator(_head);
		}

		iterator end()
		{
			return iterator(_head);
		}

		reverse_iterator rend()
		{
			return reverse_iterator(_head->_next);
		}

		const_iterator end() const
		{
			return const_iterator(_head);
		}

		const_reverse_iterator rend() const
		{
			return const_reverse_iterator(_head->_next);
		}

		list()
		{
			empty_init();
		}

		// 现代写法
		list(const list<T>& lt)
		{
			empty_init();
			list<T> tmp(lt.begin(), lt.end());
			swap(tmp);
		}

		template<class Iterator>
		list(Iterator first, Iterator last)
		{
			empty_init();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		list<T>& operator=(list<T> lt)
		{
			swap(lt);

			return *this;
		}

		// 释放所有结点
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}

		void swap(list<T>& tmp)
		{
			std::swap(_head, tmp._head);
		}

		// 释放结点,但是_head头结点不处理
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}

		void empty_init()
		{
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;
		}

		void push_back(const T& x)
		{
			insert(end(), x);
		}

		void push_front(const T& x)
		{
			insert(begin(), x);
		}

		void insert(iterator pos, const T& x)
		{
			node* cur = pos._node;
			node* prev = cur->_prev;

			node* newnode = new node(x);
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
		}

		void pop_back()
		{
			erase(--end());
		}

		void pop_front()
		{
			erase(begin());
		}

		iterator erase(iterator pos)
		{
			assert(pos != end());
			node* prev = pos._node->_prev;
			node* next = pos._node->_next;
			prev->_next = next;
			next->_prev = prev;
			delete pos._node;

			return iterator(next);
		}
		
		T& front()
		{
			assert(!empty());
			return *begin();
		}

		const T& front() const
		{
			assert(!empty());
			return *begin();
		}

		T& back()
		{
			assert(!empty());
			return *(--end());
		}

		const T& back() const
		{
			assert(!empty());
			return *(--end());
		}

		bool empty()
		{
			return _head->_next == _head &&
				_head->_prev == _head;
		}
		
	private:
		node* _head;
	};
}

二、vector与list的对比

vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:

vectorlist
底层结构动态顺序表,一段连续空间带头结点的双向循环链表
随机访问支持随机访问,访问某个元素效率O(1)不支持随机访问,访问某个元素效率O(N)
插入和删除任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器原生态指针对原生态指针(节点指针)进行封装
迭代器失效在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
使用场景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随机访问

以上就是本文的所有内容了,如有错处或者疑问欢迎大家在评论区相互交流orz~🙈🙈

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

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

相关文章

使用Python写入数据到Excel:实战指南

在数据科学领域&#xff0c;Excel是一种广泛使用的电子表格工具&#xff0c;可以方便地进行数据管理和分析。然而&#xff0c;当数据规模较大或需要自动化处理时&#xff0c;手动操作Excel可能会变得繁琐。此时&#xff0c;使用Python编写程序将数据写入Excel文件是一个高效且便…

如何推广你的开源项目?

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

mongodb聚合排序的一个巨坑

现象&#xff1a; mongodb cpu动不动要100%&#xff0c;如下图 分析原因&#xff1a; 查看慢日志发现&#xff0c;很多条这样的查询&#xff0c;一直未执行行完成&#xff0c;占用大量的CPU [{$match: {"tags.taskId": "64dae0a9deb52d2f9a1bd71e",grnty: …

电商版面设计之首页设计

首页设计资料 1、首页----多看大美工 2、手表首页 3、水密码官方旗舰店 4、AK男装 5、百雀羚首页设计 6、活动专区 7、店铺有一些活动&#xff0c;会在里面进行体现 8、提前构思&#xff0c;多看别人的店铺设计&#xff0c;是提升自己店铺设计最好的方法 9、产品专区 10、买一送…

date_range()函数--Pandas

1. 函数功能 生成连续的日期时间序列 2. 函数语法 pandas.date_range(startNone, endNone, periodsNone, freqNone, tzNone, normalizeFalse, nameNone, inclusiveboth, *, unitNone, **kwargs)3. 函数参数 参数含义start可选参数&#xff0c;起始日期end可选参数&#xff…

物理机ping不通windows server 2012

刚才尝试各种方法&#xff0c;在物理机上就是ping不能wmware中的windows server 2012 . 折腾了几个小时&#xff0c;原来是icmp 被windows server 2012 禁用了 现在使用使用以下协议就能启用Icmp协议。 netsh firewall set icmpsetting 8然后&#xff0c;就能正常ping 通虚…

银河麒麟服务器系统服务安装流程,会根据服务是否正常判断是否重装服务

流程图 【金山文档】 linux系统服务安装相关https://kdocs.cn/l/csiyUvMWjmwc 总结 要站在面上考虑问题&#xff0c;解压和拷贝这个过程仅仅是系统服务安装的其中一个步骤&#xff0c;不能只是判断文件是否解压去判断&#xff0c;去执行相关逻辑

Linux c++ - 01-开发环境配置

一、环境配置 1.安装gcc,gdb sudo apt update sudo apt install build-essential gdb 安装成功确认 gcc --version g --version gdb --version 2.安装cmake sudo apt install cmake 安装成功确认 cmake --version 3.总结 gcc 用于编译C代码 g 用于编译C代码 VSCode是通…

vs2019 ,c++的STD库全局函数 _Pocma 的思考

&#xff08;1&#xff09;在阅读vs2019上的 STL库的 map 源码时&#xff0c;遇到了这个函数&#xff0c;之前&#xff0c;在别的源码中也经常出现这个函数。那么这个函数起什么作用呢&#xff1f; 在1880行&#xff0c;有对该函数的调用。其定义如下图&#xff1a;&#xff0…

电商设计之分类模块设计

1、现在店铺流行这些简单的风格 2、 3、夏季新品 4、妖精的口袋----店铺展示 5、小狗电器-----优秀分类案例 6、客服中心 7、有线手持款---这里全都是有线首饰款&#xff0c;方便找到东西 8、裂帛 8.1裂帛分类模块 8.2 我点击了T恤 8.3 买雪纺衫&#xff0c;这里面全都是雪纺 …

LLMs多任务指令微调Multi-task instruction fine-tuning

多任务微调是单任务微调的扩展&#xff0c;其中训练数据集包括多个任务的示例输入和输出。在这里&#xff0c;数据集包含指导模型执行各种任务的示例&#xff0c;包括摘要、评论评分、代码翻译和实体识别。 您在这个混合数据集上训练模型&#xff0c;以便它可以同时提高模型在…

【C++】const成员 | 取地址运算符重载

Ⅰ. const成员 两种const 我们知道&#xff0c;用const修饰 能起到保护&#xff0c;使之不被修改的作用。 修饰指针的const有两种位置&#xff1a; 我们学过的this指针&#xff0c;就被后者所修饰&#xff0c;因此无法被修改。 const成员函数 ➡️为了保护函数里的成员&…

使用 Transformer 和 Amazon OpenSearch Service 构建基于列的语义搜索引擎

在数据湖中&#xff0c;对于数据清理和注释、架构匹配、数据发现和跨多个数据来源进行分析等许多操作&#xff0c;查找相似的列有着重要的应用。如果不能从多个不同的来源准确查找和分析数据&#xff0c;就会严重拉低效率&#xff0c;不论是数据科学家、医学研究人员、学者&…

机器学习:无监督学习

文章目录 线性学习方法聚类ClusteringKmeansHAC 分布表示降维PCAMatrix FactorizationManifold LearningLLELaplacian Eigenmapst-SEN 线性学习方法 聚类Clustering Kmeans 随机选取K个中心&#xff0c;然后计算每个点与中心的距离&#xff0c;找最近的&#xff0c;然后更新中…

计算机竞赛 基于大数据的社交平台数据爬虫舆情分析可视化系统

文章目录 0 前言1 课题背景2 实现效果**实现功能****可视化统计****web模块界面展示**3 LDA模型 4 情感分析方法**预处理**特征提取特征选择分类器选择实验 5 部分核心代码6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于大数据…

WOFOST模型与PCSE模型应用

实现作物产量的准确估算对于农田生态系统响应全球变化、可持续发展、科学粮食政策制定、粮食安全维护都至关重要。传统的经验模型、光能利用率模型等估产模型原理简单&#xff0c;数据容易获取&#xff0c;但是作物生长发育非常复杂&#xff0c;中间涉及众多生理生化过程&#…

MySQL - 安装

MySQL 安装 | 菜鸟教程 linux:yum方式安装mysql 8 - 点击领取 一、Cent OS系统 &#xff08;一&#xff09;安装前 1.1 删除mariadb数据库软件包 rpm -qa|grep mari //查询mariadb数据库软件包 rpm -e --nodeps 查到的包名 //删除软件包 2.1 检…

职业发展和认证指导:提供网络安全从业者的职业规划建议,介绍各类认证考试和培训资源

章节一&#xff1a;引言 网络安全在今天的数字化世界中变得愈发重要&#xff0c;随之而来的是对网络安全专业人才的持续需求。作为一个追求在网络安全领域取得成功的从业者&#xff0c;职业规划和持续学习是不可或缺的一部分。本文将为你详细介绍如何规划你的网络安全职业发展…

04.sqlite3学习——DDL(数据定义:创建和删除表)

目录 DDL&#xff08;数据定义&#xff1a;创建和删除表&#xff09; SQLite 创建表 语法 实例 字段修饰符 primary key 定义主键列 AUTOINCREMENT 自动增长 UNIQUE 字段的值唯一 NOT NULL 字段的值不为空 SQLite 修改表 增加字段add 修改表名rename to SQLite 删…

MySQL—buffer pool

一、buffer pool的介绍 Buffer pool是什么 一个内存区域&#xff0c;为了提⾼数据库的性能&#xff0c;数据库操作数据的时候&#xff0c;把硬盘上的数据加载到buffer pool&#xff0c;不直接和硬盘打交道&#xff0c;操作的是 buffer pool的数据&#xff0c;数据库的增删改查…