C++:list

一,list的介绍

1,list初步

(1)list是 C++ 标准模板库 (STL) 中的一个双向链表容器。它允许在常数时间内进行任意位置的插入和删除操作,但不支持随机访问。

(2)list容器的底层数据结构为带头双向循环链表,list的元素可以存储在非相邻的内存中,所以list在插入和删除元素中有着独特的优势,其时间复杂度仅为常数。

注意:list还需要额外的存储空间来存储前一个元素和后一个元素

头文件:

#include<list>

2,list的特点

优点:(1)list在插入和删除操作过程中不会使得迭代器失效,仍旧可以对迭代器遍历和操作。

(2)list可以动态的存储数据,可以有效地处理大量数据并且不会浪费过多的空间

补充:list和数据结构中的快速排序算法的平均时间复杂度是一样的,都是0\left ( n\log n \right ),但是快速排序的最坏情况的时间复杂度为o\left (n ^{2} \right ),比list要快。

缺点:(1)list在访问元素的时候需要从头遍历链表,时间复杂度为O(n),因此在随机访问元素时效率低下

(2)内存占用相比其他容器更大,list需要额外的指针维护链表

(3)list不支持指针算术运算,无法像指针那样给进行加减操作

3,list的底层

我们想要观察list的源代码,可以通过everything这个小软件打开

通过将这个文件拖拽到VS上就可以观察到list头文件的源代码

通过观察list头文件中的一些函数,我们可以发现list的底层,这里拿clear举例

我们通过这种方法,可以发现list的底层是一个带头双向循环链表

使用结点指针达不到迭代器的功能:当一个it是指向结点的指针的时候,对it解引用得到的是这个结点而不是值,同时it++也不可以保证到下一个位置,因为下一个位置我们无法确定其地址与本结点的大小

解决方案:用一个类型对结点指针进行封装,再对结点指针和++两个操作进行重载

insert的源代码

创建一个tmp新的结点,tmp结点中next指针指向pos结点,tmp结点中的pre指针指向pos结点中的pre指针(即前一个结点),同时将pos前一个结点的next指针指向tmp,pos处结点的pre指针指向tmp,完成插入一个新的元素。

这与链表中插入思想是一样的

补充:在链表底层中_prev和_next被定义为void*类型的,所以取节点指针时要先强转一下,所以才会出现(link_type(.....)这种情况。

4,迭代器分类

迭代器分类依据之一是其移动方式,与容器的底层结构有关。

迭代器正常下有五种:输入迭代器,输出迭代器,前向迭代器,双向迭代器,随机访问迭代器

这里我们有另一种迭代器分类方式:单向,双向和随机

单向迭代器(Forward Iterator):仅仅支持++操作,不支持--和随机访问,同时单向迭代器是可读可写的(常量是只读)

双向迭代器(Bidirectional Iterator):支持++和--操作,但是不支持随机访问,同时双向迭代器是可读可写的(常量是只读)

随机访问迭代器(Random Access Iterator):支持+,-,+=,-=等算术运算,支持下标访问和双向迭代器的所有功能,同时还支持距离运算和比较运算

一些STL库的迭代器类型:

那么我们怎样知道这些容器的迭代器类型的呢?

string

list

vector

我们可以发现算法的迭代器参数类型,暗示了要求的迭代器类型。

二,list的定义

我们可以发现list的定义要求:

template <class T, class Allocator = allocator<T>>

T是指存储的元素类型,如int,char,结构体或者STL容器等

allocator是内存分配器

代码例子:

list<int>lt;

我们再通过调试观察其结构:

三,list容器的接口

1,list的构造

在标准库中定义图

(1)构造一个类型的空容器

list<int> lt; //构造int类型的空容器

(2)构造一个含有n个val值的一个类型容器

list<int>lt(7,7)//构造含有7个元素为7的int类型容器

(3)拷贝构造某类型容器

list<int>lt(lt1);//拷贝构造int类型的lt1容器

(4)迭代器拷贝构造某一段数据

string v("Manchester United");
list<char> lt(v.begin(),v.end()); //构造string对象某段迭代器区间的内容

(5)构造数组某段区间内容

int arr[] = { 1, 2, 3, 4, 5 };
int sz = sizeof(arr) / sizeof(arr[1]);
list<int> lt5(arr, arr + sz); //构造数组某一段数据

(6)使用花括号构造内容

list<int> lt{ 1,2,3,4,5 };  // 直接使用括号进行构造

在C++11中引入使用

2,list遍历

(1)迭代器

除此以外还有cbegin,drend等等,这些在vector中我们已经做过接触,这里就不再细述了

正向迭代器

反向迭代器

我们可以借用rbegin和rend来实现

(2)for循环

迭代器是一定支持范围for操作的

3,list的容量操作

(1)我们可以发现list容器中没有capacity()操作和reserve()操作,这是因为链表不需要提前分配内存

(2)list容量操作中resize时间复杂度为O(n),其余操作时间复杂度为O(1)

size

empty

resize

resize 有两种重载方式

代码展示:

clear

4,list的成员接口

这些操作大家可以自己课下一一尝试,这里就不给大家挨个展示了,只展示重点接口

insert

我们可以发现这里insert的接口很多,这里只介绍三种经典的接口即第一个,第二个和第四个

(1)在指定位置插入单个元素

iterator insert(const_iterator pos, const T& value);

代码展示:

(2)在指定位置插入多个相同元素

iterator insert(const_iterator pos, size_type count, const T& value);

代码展示:

(3)在指定位置插入另一个容器的元素范围

template<class InputIt>
iterator insert(const_iterator pos, InputIt first, InputIt last);

代码展示:

erase

我们可以看到erase有两种接口

(1) 删除单个元素(通过迭代器)

iterator erase(const_iterator pos);

代码展示:

(2)删除元素范围(通过迭代器范围)

iterator erase(const_iterator first, const_iterator last);

代码展示:

注意:类似于begin和end这类区间都是左闭右开的

5,list容器的特殊操作

splice

(1)移动单个元素

void splice(const_iterator pos, list& other, const_iterator it);

代码展示:

(2)移动元素范围 

void splice(const_iterator pos, list& other, const_iterator first, const_iterator last);

代码展示:

(3)移动整个链表 

void splice(const_iterator pos, list& other);

代码展示:

remove

(1)删除所有等于指定值的元素

void remove(const T& value);

代码展示:

remove_if

(1)删除满足条件的元素

template <class Predicate>
void remove_if(Predicate pred);

代码展示:

unique

(1)删除连续重复元素

void unique();

代码展示:

(2)自定义去重规则

template <class BinaryPredicate>
void unique(BinaryPredicate pred);

代码展示:

merge

(1)默认升序合并

void merge(list& other);

代码示例:

(2)自定义排序规则合并

template <class Compare>
void merge(list& other, Compare comp);

代码示例:

sort

(1)默认升序排序

void sort();

代码展示:

(2)自定义排序规则

template <class Compare>
void sort(Compare comp);

代码展示:

reverse

(1)反转链表元素顺序

void reverse();

代码展示:

四,list的模拟实现

带头双向循环链表结点

一般定义一个类都用class,但是当我们不用访问限定符进行限制成员的时候,用struct

那么为什么?

因为在链表中我们在模拟实现中其实用到的不是结点的指针,而是通过一个迭代器访问这个链表的。

之所以不用指针,是因为不同平台对list中的链表规定不同

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

链表的初始化

class list
{typedef list_node<T> node;//将结点私有化,外界访问结点的时候只能通过上面的list_node访问
public:list(){_head = new node;_head->_next = _head;_head->_prev = _head;}
private:node* _head;//哨兵位头结点};

构造函数

list_node(const T& x=T())//要注意哨兵位初始化时的值的类型是不确定的,所以这里可以写成匿名对象或者全缺省: _next(nullptr), _prev(nullptr), _data(x)
{ }

链表迭代器模板类型框架

通过迭代器结构体模拟目标指针行为,从而遍历链表,再定义类型别名方便后续使用,随后node*_node来保存当前指向的链表节点的指针

template<class T>
struct list_iterator
{typedef list_node<T>node;node* _node;
};

链表迭代器

template<class T>                  //模板声明,支持任意数据类型
struct list_iterator {              //迭代器类型(结构体实现)typedef list_node<T> node;      //节点类型别名typedef list_iterator<T> self;  //迭代器自身类型别名node* _node;                   //存储指向当前链表节点地址的指针// 构造函数 可以通过当前已有链表节点指针创建迭代器list_iterator(node* node)       //通过节点指针初始化:_node(node) {}// 重载解引用操作符  使得迭代器可以直接访问数据T& 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) const{return _node != s.node;}//重载相等运算符bool operator==(const self& s) const{return _node == s._node;}
};
typedef list_iterator<T>iterator;//将底层迭代器起别名,这说明上层的迭代器相似,但其实底层迭代器实现方式是不同的iterator begin()
{return iterator(_head->_next);
}
iterator end()
{return iterator(_head);
}

测试用例:

list<int>::iterator it = lt.begin();  //获取指向链表首元素的迭代器
while (it != lt.end()) {              //循环直到链表末尾*it += 10;                        //修改当前元素值cout << *it << " ";               //输出当前元素++it;                             //移动到下一个元素
}

图解:

这里的拷贝就是浅拷贝,不需要写析构函数了:

(1)链表迭代器本质就是对原生指针_node的封装,不能影响链表本身结点生命周期

(2)链表节点的创建和销毁由链表容器管理,如果迭代器释放会出现释放两次的情况,影响链表本身内存

push-back实现

思路:

找到尾结点,新增一个尾结点,将newnode的prev指向tail,next指向_head,同时tail的_next指向newnode,_head的prev指向newnode

这个方法优势是可以不必区分链表空或者非空

当链表为空的时候

按上述思路仍可以进行

代码展示:

提示:这串代码是不完整的,库函数,类的定义等等作者都忽略了,大家自己补充或者看后文的完整版代码即可。

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;
}

测试用例

list<int>lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);

insert实现

void insert(iterator pos, const T& x) {node* cur = pos._node;      //获取当前位置对应的节点指针node* prev = cur->_prev;    //找到前驱节点node* newnode = new node(x); //创建新节点(假设node构造函数接受T值)// 调整指针关系:prev->_next = newnode;      //前驱节点的next指向新节点newnode->_prev = prev;      //新节点的prev指向前驱newnode->_next = cur;       //新节点的next指向原节点cur->_prev = newnode;       //原节点的prev指向新节点
}

insert不需要检查迭代器,因为所有结点都可以插入

借用insert实现头插,尾插等

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

erase实现

void erase(iterator pos) {assert(pos != end());      //确保不删除尾哨兵节点node* cur = pos._node;    //获取要删除的节点node* prev = cur->_prev;  //前驱节点node* next = cur->_next;  //后继节点// 调整指针:prev->_next = next;       //前驱节点直接指向后继next->_prev = prev;       //后继节点直接指向前驱delete cur;               //释放目标节点内存
}

借用erase实现头删和尾删

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

上述的测试用例大家自己写自己测试即可。

那么list的插入和删除是否有迭代器失效的情况呢?

list的insert不存在迭代器失效,但是list的erase存在迭代器失效的问题。

因为insert只是进行链表的插入操作,修改局部结点的指针不会使得内存重新分配,因而不存在迭代器失效的问题。

erase会使被删除元素的迭代器失效,因为erase操作会删除结点,使得其前后结点的next和prev指针变成野指针,但是其仅仅影响被删除元素结点,不影响其他结点的地址和其他迭代器。

析构函数

~list()
{clear();delete _head;//哨兵位头结点也要释放掉_head = nullptr;
}void clear()
{auto it = begin();while (it != end())//哨兵位的头结点不清除{it = erase(it);}
}

size

size_t size()
{return _size;
}

这里要补充一个私有成员变量size,来完成size函数

同时要在insert和erase中对size进行更改

深拷贝构造函数

初始化链表

void empty_init() {_head = new Node;       //创建头哨兵节点(不存储有效数据)_head->_next = _head;   //next指向自己_head->_prev = _head;   //prev指向自己_size = 0;              //大小置零
}

拷贝构造函数

// lt2(lt1)
//list(const list<T>& lt)  这里const对象要用const对象的迭代器
list(list<T>& lt)
{empty_init();          //初始化空链表for (auto& e : lt) {   //遍历源链表push_back(e);      //逐个元素尾插}
}



初始化列表构造链表

list(initializer_list<T> il)  //初始化列表参数
{empty_init();             //初始化空链表结构for (auto& e : il)        //遍历初始化列表{push_back(e);         //将每个元素添加到链表尾部}
}

swap

void swap(list<T>& lt) {std::swap(_head, lt._head);  // 交换头指针std::swap(_size, lt._size);  // 交换元素计数
}

交换的是当前链表*this和目标链表lt

拷贝赋值

list<T>& operator=(list<T> lt)  //参数为值传递(会调用拷贝构造)
{swap(lt);                  //交换当前对象与副本的内容return *this;              //返回当前对象的引用
}

参数创建时会隐式调用拷贝构造函数创建lt

const对象

注意const迭代器不要在最前面加上const,这样修饰的是整个迭代器,但是我们要求的是const迭代器不是本身不能被修改,而是指向的内容不能被修改

所以我们要对operator*进行操作,使得其解引用不能被修改就可以达到目的

typedef list_const_iterator<T> const_iterator;

同样在struct中也要随之改变

template<class T>
struct list_const_iterator
{typedef list_node<T> node;typedef list_const_iterator<T> self;node* _node;list_const_iterator(node* node):_node(node){}const T& 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) const{return _node != s._node;}bool operator==(const self& s) const{return _node == s._node;}
};

但是我们发现两个类是有点冗余的,而且我们发现这个类只有个别的类型不同,所以我们可以考虑复用,借用Ref模板参数完成这项工作

同时,我们也有疑惑,就是箭头操作怎么重载?这里可以再添加一个模板参数,来接收和生成

// typedef list_iterator<T, T&, T*> iterator;// typedef list_iterator<T, const T&, const T*> const_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* node):_node(node){}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) const{return _node != s._node;}bool operator==(const self& s) const{return _node == s._node;}};

通过复用我们就完成了一个通用的双向链表迭代器模板,实现了普通迭代器和常量迭代器的统一处理。

五,list失效问题

迭代器失效即迭代器所指向的节点的无 效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入 时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭 代器,其他迭代器不会受到影响。

void TestListIterator1()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array+sizeof(array)/sizeof(array[0]));auto it = l.begin();while (it != l.end()){// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给
其赋值l.erase(it);  ++it;}
}
// 改正
void TestListIterator()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array+sizeof(array)/sizeof(array[0]));auto it = l.begin();while (it != l.end()){l.erase(it++);    // it = l.erase(it);}
}

六,list的反向迭代器

反向迭代器的++就是正向迭代器的--,反向迭代器的--就是正向迭代器的++, 因此反向迭代器的实现可以借助正向迭代器,即:反向迭代器内部可以包含一个正向迭代器,对 正向迭代器的接口进行包装即可。

template<class Iterator>
class ReverseListIterator
{// 注意:此处typename的作用是明确告诉编译器,Ref是Iterator类中的类型,而不是静态
成员变量// 否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量// 因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的
public:typedef typename Iterator::Ref Ref;typedef typename Iterator::Ptr Ptr;typedef ReverseListIterator<Iterator> Self;
public:// 构造ReverseListIterator(Iterator it): _it(it){}// 具有指针类似行为Ref operator*(){Iterator temp(_it);--temp;return *temp;}Ptr operator->(){    return &(operator*());}// 迭代器支持移动Self& operator++(){--_it;return *this;}Self operator++(int){Self temp(*this);--_it;return temp;}Self& operator--(){++_it;return *this;}Self operator--(int){Self temp(*this);++_it;return temp;}// 迭代器支持比较bool operator!=(const Self& l)const{ return _it != l._it;}bool operator==(const Self& l)const{ return _it != l._it;}Iterator _it;
};

七,list和vector的比较

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

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

相关文章

深入理解Collections.addAll方法

文章目录深入理解Collections.addAll方法概述方法定义基本用法1. 向List添加元素2. 向Set添加元素3. 添加数组元素与传统add方法的比较使用传统add方法使用Collections.addAll性能考虑注意事项实际应用场景与Collection.addAll的区别最佳实践总结深入理解Collections.addAll方法…

CISP-PTE 练习题(完整一套)

目录 1、SQL注入 2、文件上传 3、文件包含 4、代码审计 5、命令执行 6、端口扫描 7、sql 写 webshell 8、3389 远程桌面利用 1、SQL注入 sqllabs-less-24 二次注入 2、文件上传 没有对文件后缀进行检测&#xff0c;但是对文件类型有检测&#xff0c;需要使用图片头绕…

Vue3入门-计算属性+监听器

&#x1f3e0;个人主页&#xff1a;Yui_ &#x1f351;操作环境&#xff1a;vscode\node.js &#x1f680;所属专栏&#xff1a;Vue3 文章目录1. 计算属性1.1 computed函数1.2 计算属性VS普通函数1.3 计算属性的完整写法2. 监听器3.总结1. 计算属性 计算属性&#xff08;compu…

Linux Swap区深度解析:为何禁用?何时需要?

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、Swap区&#xff1a;Linux的"内存救生圈"二、为什么要禁用Swap&#xff1f;性能的隐形杀手三、何时应该使用Swap&#xff1f;不可或缺的场景四、如…

用TensorFlow进行逻辑回归(三)

逻辑回归Logistic regression这个脚本展示如何用TensorFlow求解逻辑回归。 ()ysigmoid(Axb)我们使用低出生重量数据,特别地:y 0 or 1 low birth weightx demographic and medical history dataimport matplotlib.pyplot as pltimport numpy as npimport tensorflow as tfimp…

mingw 编译 assimp v6.0.2 解决编译报错

mingw 编译 assimp v6.0.2 理论上看这个就能满足&#xff1a;在Windows下使用CMakeMinGW64编译Assimp库 环境变量问题 i386 architecture of input file CMakeFiles\assimp.dir/objects.a(assimp.rc.obj)’ is incompatible with i386:x86-64 output collect2.exe: error: ld r…

Windows 11清理C盘方法大全:磁盘清理/禁用休眠/系统还原点/优化大师使用教程

Windows 11清理C盘方法1. 使用磁盘清理工具步骤&#xff1a;按 Win S 搜索“磁盘清理”&#xff0c;打开工具。选择C盘&#xff0c;点击“确定”。勾选需要清理的文件类型&#xff08;如临时文件、系统错误内存转储等&#xff09;&#xff0c;点击“确定”。确认删除操作&…

Rabbitmq Direct Exchange(直连交换机)多个消费者,配置相同的key ,队列,可以保证只有一个消费者消费吗

思考可以保证消费不被重复消费&#xff0c;因为通过轮询一个消息只会投递给一个消费者。但是不是一个消费者消费&#xff0c;而是多个轮询消费在 RabbitMQ 中&#xff0c;如果多个消费者&#xff08;Consumers&#xff09;同时订阅 同一个队列&#xff08;Queue&#xff09;&am…

设计模式是什么呢?

1.掌握设计模式的层次第一层&#xff1a;刚刚学编程不久&#xff0c;听说过什么是设计模式。第二层&#xff1a;有很长时间的编程经验&#xff0c;自己写过很多代码&#xff0c;其中用到了设计模式&#xff0c;但是自己不知道。第三层&#xff1a;学习过设计模式&#xff0c;发…

ThreadLocal使用详解-从源码层面分析

从demo入手看效果 代码Demostatic ThreadLocal tl1 new ThreadLocal();static ThreadLocal tl2 new ThreadLocal();static ThreadLocal tl3 new ThreadLocal();public static void main(String[] args) {tl1.set("123");tl2.set("456");tl3.set("4…

CPO:对比偏好优化—突破大型语言模型在机器翻译中的性能边界

温馨提示&#xff1a; 本篇文章已同步至"AI专题精讲" CPO&#xff1a;对比偏好优化—突破大型语言模型在机器翻译中的性能边界 摘要 中等规模的大型语言模型&#xff08;LLMs&#xff09;&#xff0c;如参数量为 7B 或 13B 的模型&#xff0c;在机器翻译&#xff0…

执行shell 脚本 如何将日志全部输出到文件

在执行 Shell 脚本时&#xff0c;如果需要将 所有输出&#xff08;包括标准输出 stdout 和错误输出 stderr&#xff09; 重定向到日志文件&#xff0c;可以使用以下方法&#xff1a;方法 1&#xff1a;直接重定向&#xff08;推荐&#xff09; /appdata/mysql_backup_dump.sh &…

Postman接口测试实现UI自动化测试

Selenium底层原理 3天精通Postman接口测试&#xff0c;全套项目实战教程&#xff01;&#xff01;运行代码&#xff0c;启动浏览器后&#xff0c;webdriver会将浏览器绑定到特定的端口&#xff0c;作为webdriver的remote server&#xff08;远程服务端&#xff09;&#xff0c;…

CSS动画与变换全解析:从原理到性能优化的深度指南

引言&#xff1a;现代Web动画的技术革命 在当今的Web体验中&#xff0c;流畅的动画效果已成为用户交互的核心要素。根据Google的研究&#xff0c;60fps的动画可以使用户参与度提升53%&#xff0c;而卡顿的界面会导致跳出率增加40%。本文将深入剖析CSS动画&#xff08;animation…

NPM组件 @ivy-shared-components/iconslibrary 等窃取主机敏感信息

【高危】NPM组件 ivy-shared-components/iconslibrary 等窃取主机敏感信息 漏洞描述 当用户安装受影响版本的 ivy-shared-components/iconslibrary 等NPM组件包时会窃取用户的主机名、用户名、工作目录、IP地址等信息并发送到攻击者可控的服务器地址。 MPS编号MPS-zh19-e78w…

Fail2ban防止暴力破解工具使用教程

Fail2ban防止暴力破解工具使用教程场景Fail2ban安装和配置安装配置原理遇到的问题以及解决办法问题1&#xff1a;设置的策略是10分钟内ssh连接失败2次的ip进行封禁&#xff0c;日志中实际却出现4次连接。问题2&#xff1a;策略设置为1分钟内失败两次&#xff0c;封禁ip。但通过…

亚远景科技助力长城汽车,开启智能研发新征程

亚远景科技助力长城汽车&#xff0c;开启智能研发新征程在汽车智能化飞速发展的当下&#xff0c;软件研发管理成为车企决胜未来的关键。近日&#xff0c;亚远景科技胡浩老师应邀为长城汽车开展了一场主题深刻且极具实用价值的培训。本次培训聚焦软件研发管理导论 - 建立机器学习…

图算法在前端的复杂交互

引言 图算法是处理复杂关系和交互的强大工具&#xff0c;在前端开发中有着广泛应用。从社交网络的推荐系统到流程图编辑器的路径优化&#xff0c;再到权限依赖的拓扑排序&#xff0c;图算法能够高效解决数据之间的复杂关联问题。随着 Web 应用交互复杂度的增加&#xff0c;如实…

Prometheus Operator:Kubernetes 监控自动化实践

在云原生时代&#xff0c;Kubernetes 已成为容器编排的事实标准。然而&#xff0c;在高度动态的 Kubernetes 环境中&#xff0c;传统的监控工具往往难以跟上服务的快速变化。Prometheus Operator 应运而生&#xff0c;它将 Prometheus 及其生态系统与 Kubernetes 深度融合&…

一种融合人工智能与图像处理的发票OCR技术,将人力从繁琐的票据处理中解放

在数字化浪潮席卷全球的今天&#xff0c;发票OCR技术正悄然改变着企业财务流程的运作模式。这项融合了人工智能与图像处理的前沿技术&#xff0c;已成为财务自动化不可或缺的核心引擎。核心技术&#xff1a;OCR驱动的智能识别引擎发票OCR技术的核心在于光学字符识别&#xff08…