【list】list库介绍 + 简化模拟实现

本节博客先对list进行用法介绍,再在库的基础上简化其内容和形式,简单进行模拟实现,有需要借鉴即可。

目录

  • 1.list介绍
    • 1.1 list概述
    • 1.2相关接口的介绍
  • 2.简化模拟实现
  • 3.各部分的细节详述
    • 3.1结点
    • 3.2迭代器
      • 细节1:迭代器用原生指针还是专门设计为类的问题
      • 细节2:迭代器++、--行为重载的返回类型问题
      • 细节3:迭代器解引用返回类型
      • 细节4:迭代器operator->重载
    • 3.3链表
      • 细节1:list中提供begin和end函数的理由和返回类型?
      • 细节2:插入元素代码
      • 细节3:删除元素代码
      • 细节4:clear()函数和析构函数
      • 细节5:拷贝构造函数与赋值运算符重载
      • 细节6:insert返回为void?
  • 4.总结

1.list介绍

1.1 list概述

1.概述:list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
2. 底层实现:list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。

3. list与forward_list区别:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。

4. list的优势:与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
5. list的缺陷:与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)

我们用代码来体会一下list的缺点:

void test_op1()
{srand(time(0));const int N = 1000000;//一百万数据//两个链表list<int> lt1;list<int> lt2;//一个顺序表vector<int> v;//生成随机数据,尾插到链表1和顺序表v中去for (int i = 0; i < N; ++i){auto e = rand()+i;//加上这个i主要是为了减少重复数字概率lt1.push_back(e);v.push_back(e);}//vector排序int begin1 = clock();sort(v.begin(), v.end());int end1 = clock();//list排序int begin2 = clock();lt1.sort();int end2 = clock();//打印比较两者用时printf("vector sort:%d\n", end1 - begin1);printf("list sort:%d\n", end2 - begin2);
}
void test_op2()
{srand(time(0));const int N = 1000000;list<int> lt1;list<int> lt2;for (int i = 0; i < N; ++i){auto e = rand();lt1.push_back(e);lt2.push_back(e);}// 拷贝vectorint begin1 = clock();vector<int> v(lt2.begin(), lt2.end());// 排序sort(v.begin(), v.end());// 拷贝回lt2lt2.assign(v.begin(), v.end());int end1 = clock();//lt1排序int begin2 = clock();lt1.sort();int end2 = clock();//打印printf("list copy vector sort copy list sort:%d\n", end1 - begin1);printf("list sort:%d\n", end2 - begin2);
}

1.2相关接口的介绍

在这里插入图片描述

2.简化模拟实现

通过去查看stl中list.h的源码我们可以知道,list是通过一个_head的Node*指针进行维护的,而其中广泛使用迭代器进行传值和访问数据。下面对其先直接摆代码,然后对其中细节进行详细介绍。

#pragma once#include<assert.h>
#include<iostream>namespace zzg
{template<typename T>struct ListNode{ListNode<T>* _next;//这个地方为什么类型不是T*???答:因为我们指针是需要指向一个ListNode<T>*类型的,而非T类型。ListNode<T>* _prev;T _data;//ListNode有参构造ListNode(const T& x = T()):_next(nullptr),_prev(nullptr), _data(x){} };template<class T, class Ref, class Ptr>class list_iterator{typedef struct ListNode<T> Node;typedef list_iterator<T, Ref, Ptr> Self;public:Node* _node;public://带参构造list_iterator(Node* node)//这个地方用值拷贝,用引用会有bug:_node(node){}//++Self& operator++(){_node = _node->_next;return *this;}Self operator++(int)//后置++{Self temp = *this;//拷贝构造一份,这时候会调用编译器自动生成的拷贝构造,为浅拷贝,但是需求满足了。_node = _node->_next;return temp;//这个地方得返回值了,因为现在的Self已经变了}//--Self& operator--(){_node = _node->_prev;return *this;}Self operator--(int)//后置--{Self temp = *this;//拷贝构造一份,这时候会调用编译器自动生成的拷贝构造,为浅拷贝,但是需求满足了。_node = _node->_prev;return temp;//这个地方得返回值了,因为现在的Self已经变了}//*itRef operator*(){return _node->_data;}//!=bool operator!=(const Self& it) {return _node != it._node;}//==bool operator==(const Self& it){return _node == it._node;}//->重载Ptr operator->(){return &_node->_data;}};//template<typename T>//class list_const_iterator//{//	typedef struct ListNode<T> Node;//	typedef list_const_iterator<T> Self;//public://	Node* _node;//public://	//带参构造//	list_const_iterator(Node* node)//这个地方用值拷贝,用引用会有bug//		:_node(node)//	{}//	//++//	Self& operator++()//	{//		_node = _node->_next;//		return *this;//	}//	Self operator++(int)//后置++//	{//		Self temp = *this;//拷贝构造一份,这时候会调用编译器自动生成的拷贝构造,为浅拷贝,但是需求满足了。//		_node = _node->_next;//		return temp;//这个地方得返回值了,因为现在的Self已经变了//	}//	//--//	Self& operator--()//	{//		_node = _node->_prev;//		return *this;//	}//	Self operator--(int)//后置--//	{//		Self temp = *this;//拷贝构造一份,这时候会调用编译器自动生成的拷贝构造,为浅拷贝,但是需求满足了。//		_node = _node->_prev;//		return temp;//这个地方得返回值了,因为现在的Self已经变了//	}//	//*it//	const T& operator*()//	{//		return _node->_data;//	}//	//!=//	bool operator!=(const Self& it)//	{//		return _node != it._node;//	}//	//==//	bool operator==(const Self& it)//	{//		return _node == it._node;//	}//	//->重载//	const T* operator->()//	{//		return &_node->_data;//	}//};template<typename T>class list{public:typedef list_iterator<T, T&, T*> iterator;typedef list_iterator<T, const T&, const T*> const_iterator;typedef struct ListNode<T> Node;private:Node* _head;//头节点public://无参构造函数list(){empty_init();}//拷贝构造list(const list<T>& lt){empty_init();for (auto& e : lt){push_back(e);}}// lt1 = lt3list<T>& operator=(list<T> lt)//先调用拷贝构造,构造出一个lt来{swap(lt);//然后交换这个局部变量与this,原this中是其他的东西return *this;//返回this本身}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;}//清理函数void clear(){iterator it = begin();while (it != end()){it = erase(it);}}//析构函数~list(){clear();delete _head;_head = nullptr;}iterator begin(){return _head->_next;//隐式类型转换//return iterator(_head->_next);}iterator end(){return _head;//隐式类型转换//return iterator(_head);}//const + 迭代器 --> 迭代器本身不可修改//我们需要的:迭代器指向的内容不可修改 const T*类型 而不是 T* const类型//如果我们直接在一般迭代器前面+const,即const iterator --> 该迭代器不可修改,因为这是一个自定义类//解决,直接再单独一个自定义const迭代器类出来const_iterator begin() const{return _head->_next;//return iterator(_head->_next);}const_iterator end() const{return _head;//return iterator(_head);}//尾插void push_back(const T& x){insert(end(), x);}//头插void push_front(const T& x){insert(begin(), x);}//void push_back(const T& x)//{//	Node* newnode = new Node(x);//	//找到尾巴//	Node* tail = _head->_prev;//	tail->_next = newnode;//链接临近两点//	newnode->_prev = tail;//	newnode->_next = _head;//	_head->_prev = newnode;//}//任意插入void insert(iterator pos, const T& val){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(val);prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;}//任意删除iterator erase(iterator pos){Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;return iterator(next);}//尾删void pop_back(){erase(--end());}//头删void pop_front(){erase(begin());}};struct A{public:int _a;int _b;A(int a = 0, int b = 0){_a = a;_b = b;}};//测试函数void test_list1(){/*list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);lt.push_back(6);lt.pop_back();lt.pop_back();lt.pop_back();list<int>::iterator it = lt.begin();while (it != lt.end()){std::cout << *it << " ";it++;}std::cout << std::endl;*/list<A> Al;Al.push_back({ 1,1 });Al.push_back({ 2,2 });Al.push_back({ 3,3 });Al.push_back({ 4,4 });Al.push_back({ 5,5 });Al.push_back({ 6,6 });list<A>::iterator it = Al.begin();while (it != Al.end()){//std::cout << (*it)._a << '/' << (*it)._b << std::endl;std::cout << it->_a << '/' << it->_b << std::endl;//it->_a  --->  it->->_a == it.operator->()->_a;it++;}std::cout << std::endl;}
}

3.各部分的细节详述

主要包含三个部分,一是整体的链表类,二是链表中的每个元素结点类,还有就是用来访问修改结点的迭代器类

下面分开进行细节介绍。

3.1结点

每个结点我们很熟悉,无非是两个指针和一个数据,一个指针指向它前面的结点,另一个指向其后面的结点,数据中放具体元素的值。

下面是基本结构框架:

template<typename T>
struct ListNode
{ListNode<T>* _next;//这个地方为什么类型不是T*???答:因为我们指针是需要指向一个ListNode<T>*类型的,而非T类型。ListNode<T>* _prev;T _data;//ListNode有参构造ListNode(const T& x = T()):_next(nullptr),_prev(nullptr), _data(x){} 
};

细节:

  • 使用struct,标注为公有属性,方便外部调用
  • list是带头双向循环链表,因而每个结点要有两个指针
  • 提供全缺省的默认构造函数

思考:每个结点的两个指针为什么是ListNode*类型而不是T*类型呢?
答:因为我们每个结点的指针指向的是一个结点,T仅仅是一个结点中的数据而已。

3.2迭代器

template<class T, class Ref, class Ptr>
class list_iterator
{typedef struct ListNode<T> Node;typedef list_iterator<T, Ref, Ptr> Self;
public:Node* _node;
public://带参构造list_iterator(Node* node)//这个地方用值拷贝,用引用会有bug:_node(node){}
}

细节1:迭代器用原生指针还是专门设计为类的问题

思考:list迭代器为什么要专门设置一个类???
答:
这是由于list的每个节点物理空间不连续,导致迭代器不能像之前string\vector那样简单的设计为原生指针,而是设计为一个类,以此来扩大我们对迭代器行为控制权限,重新设计*,->,++等操作。

vector,string原生指针充当迭代器:
像之前string,vector这种容器,其原生指针T*就是天然的迭代器,因为++就会自动指向到下一个数据,*引用也是拿到的我们想要的数据。
但是在list中,我们++T*,很明显由于地址不连续的缘故,压根不知道会指向的是什么(大概率会是随机值)。
在这里插入图片描述

但是需要重点注意的是,我们所设计的迭代器类是模拟的string/vector中T*的动作。

细节2:迭代器++、–行为重载的返回类型问题

//++
Self& operator++()
{_node = _node->_next;return *this;
}Self operator++(int)//后置++
{Self temp = *this;//拷贝构造一份,这时候会调用编译器自动生成的拷贝构造,为浅拷贝,但是需求满足了。_node = _node->_next;return temp;//这个地方得返回值了,因为现在的Self已经变了
}
//--
Self& operator--()
{_node = _node->_prev;return *this;
}Self operator--(int)//后置--
{Self temp = *this;//拷贝构造一份,这时候会调用编译器自动生成的拷贝构造,为浅拷贝,但是需求满足了。_node = _node->_prev;return temp;//这个地方得返回值了,因为现在的Self已经变了
}

思考:为什么前置++返回的是类对象引用,而后置++返回类型是一般类型?
答:这要结合函数设计来看,在前置++中,我们返回的是类本身;而后置++,我们返回的是一个局部的类对象,局部类对象在出函数后会自动销毁

细节3:迭代器解引用返回类型

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

注:

//iterator:
typedef list_iterator<T, Ref, Ptr> Self;
//list:
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;

返回Ref,用来区分const_iterator 和 iterator。
在这里插入图片描述

思考:为什么不重载const iterator?
答:const + 类 --> 表示该指针不可修改,并非我们所期望的指针指向内容不可修改。

思考:iterator 与 const_iterator 是同一个类吗?
答:不是。是利用同一份类模板生成的完全不同两份类。

细节4:迭代器operator->重载

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

注:

//iterator:
typedef list_iterator<T, Ref, Ptr> Self;
//list:
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
struct A
{
public:int _a;int _b;A(int a = 0, int b = 0){_a = a;_b = b;}
};main:
list<A> Al;
Al.push_back({ 1,1 });
Al.push_back({ 2,2 });
Al.push_back({ 3,3 });
Al.push_back({ 4,4 });
Al.push_back({ 5,5 });
Al.push_back({ 6,6 });list<A>::iterator it = Al.begin();
while (it != Al.end())
{//std::cout << (*it)._a << '/' << (*it)._b << std::endl;std::cout << it->_a << '/' << it->_b << std::endl;//it->_a  --->  it->->_a == it.operator->()->_a;it++;
}
std::cout << std::endl;

为了可读性,编译器把it->->_a优化为了it->_a

思考:上述代码的it->->_a代表了什么?
答:it->_a —> it->->_a == it.operator->()->_a;,这里在编译器写法上理论上应该写两个箭头的,一个用于运算符重载函数的调用,另一个是为了进入到指针里面访问数据,这里编译器为了可读性将其优化为了一个箭头。

3.3链表

链表类主要结构如下:

template<typename T>
class list
{
public:typedef list_iterator<T, T&, T*> iterator;typedef list_iterator<T, const T&, const T*> const_iterator;typedef struct ListNode<T> Node;//无参构造函数list(){empty_init();}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;}private:Node* _head;//头节点
}

细节1:list中提供begin和end函数的理由和返回类型?

iterator begin()
{return _head->_next;//隐式类型转换//return iterator(_head->_next);
}iterator end()
{return _head;//隐式类型转换//return iterator(_head);
}//const + 迭代器 --> 迭代器本身不可修改
//我们需要的:迭代器指向的内容不可修改 const T*类型 而不是 T* const类型
//如果我们直接在一般迭代器前面+const,即const iterator --> 该迭代器不可修改,因为这是一个自定义类
//解决,直接再单独一个自定义const迭代器类出来
const_iterator begin() const
{return _head->_next;//return iterator(_head->_next);
}const_iterator end() const
{return _head;//return iterator(_head);
}

思考:list提供begin和end的原因:
答:
1._head是私有的。
2.更加方便的使用迭代器,在上面代码我们可以发现,返回的都是迭代器类型。

返回迭代器类型的原因:
与后面使用迭代器相兼容。

细节2:插入元素代码

//尾插
void push_back(const T& x)
{insert(end(), x);
}
//头插
void push_front(const T& x)
{insert(begin(), x);
}//void push_back(const T& x)
//{
//	Node* newnode = new Node(x);//	//找到尾巴
//	Node* tail = _head->_prev;
//	tail->_next = newnode;//链接临近两点
//	newnode->_prev = tail;//	newnode->_next = _head;
//	_head->_prev = newnode;
//}//任意插入
void insert(iterator pos, const T& val)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(val);prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;
}

细节3:删除元素代码

//任意删除
iterator erase(iterator pos)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;return iterator(next);
}
//尾删
void pop_back()
{erase(--end());
}
//头删
void pop_front()
{erase(begin());
}

思考:为什么erase()要返回迭代器类型???
答:因为要及时对外更新迭代器指针,防止迭代器失效

思考:为什么pop_back()没有返回迭代器类型?
答:因为pop_back()不会对外接收迭代器,不存在对外更新迭代器问题。但是erase是接收迭代器的,因而要及时更新。
在这里插入图片描述
思考:–end()是否会影响到_head,为什么?
答:会影响到,但是影响到的是_head的值拷贝,没有影响到“母体”。
在这里插入图片描述

细节4:clear()函数和析构函数

void empty_init()
{_head = new Node;_head->_next = _head;_head->_prev = _head;
}//清理函数
void clear()
{iterator it = begin();while (it != end()){it = erase(it);}
}//析构函数
~list()
{clear();delete _head;_head = nullptr;
}

细节5:拷贝构造函数与赋值运算符重载

//拷贝构造
list(const list<T>& lt)
{empty_init();for (auto& e : lt){push_back(e);}
}
// lt1 = lt3
list<T>& operator=(list<T> lt)//先调用拷贝构造,构造出一个lt来
{swap(lt);//然后交换这个局部变量与this,原this中是其他的东西return *this;//返回this本身
}

思考:为什么赋值运算符重载函数参数用list lt而不是引用呢?
答:复用拷贝构造函数,是为现代写法。

细节6:insert返回为void?

//任意插入
void insert(iterator pos, const T& val)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(val);prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;
}

在string中insert我们返回的是迭代器,但是这里为什么返回值是void呢?
答:因为string是连续空间,插入数据会挪动数据,造成迭代器失效。但是链表是由结点链接而成,插入数据不会挪动数据,不会造成迭代器失效问题。

在vector中 insert/erase因为增删都会牵扯到数据挪动问题,两个函数肯定都要去返回迭代器来更新外部迭代器。
但是对于list,insert不会挪动数据因而不会失效,但是erase时候,原结点被删除,会造成迭代器失效。
在这里插入图片描述

4.总结

list模拟实现核心就是一个类迭代器的实现,相比之前string、vector,list迭代器更值得细细思考与总结。
list模拟实现还一个难点在于使用类模板,应注意类模板问题。


EOF

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

风控指南:国内车险欺诈呈现四大趋势

目录 车险欺诈呈现内外勾结的团伙化 防范车险欺诈需要多重合作 保险企业需要提升反欺诈能力 监管部门需要加强协同合作 2024年4月11日&#xff0c;国家金融监督管理总局官网发布国家金融监督管理总局关于《反保险欺诈工作办法&#xff08;征求意见稿&#xff09;》公开征求意见…

Spark-广播变量详解

Spark概述 Spark-RDD概述 1.为什么会需要广播变量&#xff1f; 广播变量是为了在分布式计算环境中有效地向集群中的所有节点广播大型只读数据集而设计的。 在分布式环境中&#xff0c;通常会遇到需要在所有节点上使用相同的数据集的情况&#xff0c;但是将这些数据集复制到每个…

ChatGPT移动应用收入在GPT-4o发布后迎来最大涨幅

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Redis崩溃后,如何进行数据恢复的?no.24

本课时我们主要学习通过 RDB、AOF、混合存储等数据持久化方案来解决如何进行数据恢复的问题。 Redis 持久化是一个将内存数据转储到磁盘的过程。Redis 目前支持 RDB、AOF&#xff0c;以及混合存储三种模式。 RDB Redis 的 RDB 持久化是以快照的方式将内存数据存储到磁盘。在…

SpringCloud系列(27)--OpenFeign日志增强

前言&#xff1a;在上一章节中我们简单的介绍了如何去调节OprnFeign的连接超时时间&#xff0c;在OpenFeign的使用过程中可能需要对Feign接口的调用情况进行监控和输出&#xff0c;这时候就需要对OpenFeign进行日志增强处理&#xff0c;所以本节的内容即是关于OpenFeign的日志增…

智能科技的新风潮:探索Web3与物联网结合

引言 随着科技的不断进步和创新&#xff0c;智能科技正成为新时代的主旋律。在这个充满活力和变革的时代&#xff0c;Web3技术与物联网的结合成为了一股新的风潮。本文将深入探讨这一新趋势&#xff0c;揭示Web3与物联网结合的意义、挑战和前景。 Web3技术的特点与优势 区块链…

Nginx企业级负载均衡:技术详解系列(11)—— 实战一机多站部署技巧

你好&#xff0c;我是赵兴晨&#xff0c;97年文科程序员。 工作中你是否遇到过这种情况&#xff1a;公司业务拓展&#xff0c;新增一个域名&#xff0c;但服务器资源有限&#xff0c;只能跟原有的网站共用同一台Nginx服务器。 也就是说两个网站的域名都指向同一台Nginx服务器…

spring boot 之 结合aop整合日志

AOP 该切面仅用于请求日志记录&#xff0c;若有其他需求&#xff0c;在此基础上扩展即可&#xff0c;不多逼逼&#xff0c;直接上代码。 引入切面依赖 <!-- 切面 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>sp…

excel里如何将数据分组转置?

这个表格怎样转换为下表&#xff1f;按照国家来分组&#xff0c;把不同年份对应的不同序列值进行转置&#xff1f;&#xff1f; 这演示用数据透视表就完成这个数据转换。 1.创建数据透视表 选中数据中任意单元格&#xff0c;点击插入选项卡&#xff0c;数据透视表&#xff0c;…

Day21:Leetcode513.找树左下角的值 +112. 路径总和 113.路径总和ii + 106.从中序与后序遍历序列构造二叉树

LeetCode&#xff1a;513.找树左下角的值 解决方案&#xff1a; 1.思路 在遍历一个节点时&#xff0c;需要先把它的非空右子节点放入队列&#xff0c;然后再把它的非空左子节点放入队列&#xff0c;这样才能保证从右到左遍历每一层的节点。广度优先搜索所遍历的最后一个节点…

【机器学习】—机器学习和NLP预训练模型探索之旅

目录 一.预训练模型的基本概念 1.BERT模型 2 .GPT模型 二、预训练模型的应用 1.文本分类 使用BERT进行文本分类 2. 问答系统 使用BERT进行问答 三、预训练模型的优化 1.模型压缩 1.1 剪枝 权重剪枝 2.模型量化 2.1 定点量化 使用PyTorch进行定点量化 3. 知识蒸馏…

CentOS7安装Redis

安装Redis&#xff0c;并使用PHP连接Redis 一、准备工作 1、安装LNMP 参考&#xff1a;搭建LNMP服务器-CSDN博客文章浏览阅读876次&#xff0c;点赞14次&#xff0c;收藏4次。LNMP 架构通常用于构建高性能、可扩展的 Web 应用程序。Nginx 作为前端 Web 服务器&#xff0c;负…

正则表达式(知识总结篇)

本篇文章主要是针对初学者&#xff0c;对正则表达式的理解、作用和应用 正则表达式&#x1f31f; 一、&#x1f349;正则表达式的概述二、&#x1f349;正则表达式的语法和使用三、 &#x1f349;正则表达式的常用操作符四、&#x1f349;re库主要功能函数 一、&#x1f349;正…

科技查新中医学科研项目查新点如何确立与提炼?案例讲解

一、前言 医学科技查新包括立项查新和成果查新两个部分&#xff0c;其中医学立项查新&#xff0c;它是指在医学科研项目申报开题之前&#xff0c;通过在一定范围内进行该课题的相关文献检索 ( 可以根据项目委托人的具体要求&#xff0c;进行国内检索或者进行国外检索 ) &#x…

深度学习之基于Matlab的BP神经网络交通标志识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 随着智能交通系统&#xff08;ITS&#xff09;的快速发展&#xff0c;交通标志识别&#xff0…

1941springboot VUE 服务机构评估管理系统开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 springboot VUE服务机构评估管理系统是一套完善的完整信息管理类型系统&#xff0c;结合springboot框架和VUE完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用springboot框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代…

Python | Leetcode Python题解之第108题将有序数组转换为二叉搜索树

题目&#xff1a; 题解&#xff1a; class Solution:def sortedArrayToBST(self, nums: List[int]) -> TreeNode:def helper(left, right):if left > right:return None# 选择任意一个中间位置数字作为根节点mid (left right randint(0, 1)) // 2root TreeNode(nums…

linux命令中arj使用

arj 用于创建和管理.arj压缩包 补充说明 arj命令 是 .arj 格式的压缩文件的管理器&#xff0c;用于创建和管理 .arj 压缩包。 语法 arj(参数)参数 操作指令&#xff1a;对 .arj 压缩包执行的操作指令&#xff1b;压缩包名称&#xff1a;指定要操作的arj压缩包名称。 更多…

基于Matlab实现声纹识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 声纹识别&#xff0c;也称为说话人识别&#xff0c;是一种通过声音判别说话人身份的生物识别技…

不闭合三维TSP:蛇优化算法SO求解不闭合三维TSP(起点固定,终点不定,可以更改数据集),MATLAB代码

旅行商从城市1出发&#xff0c;终点城市由算法求解而定 部分代码 close all clear clc global data load(data.txt)%导入TSP数据集 Dimsize(data,1)-1;%维度 lb-100;%下界 ub100;%上界 fobjFun;%计算总距离 SearchAgents_no100; % 种群大小&#xff08;可以修改&#xff09; …