08--深入解析C++ list:高效操作与实现原理

1. list介绍

1.1. list概述

template < class T, class Alloc = allocator<T> > class list;Lists are sequence containers that allow constant time insert 
and erase operations anywhere within the sequence, and iteration in both directions.

  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. 相关接口的介绍

1.2.1. insert

作用:在指定位置之前插入结点

iterator insert (iterator position, const value_type& val);

注释: An iterator that points to the first of the newly inserted elements.

问题:我们vector中insert会引发迭代器失效问题,我们list中的insert会有迭代器失效问题吗?为什么?

答:不会。这是由底层结构决定的。

同理,erase也会引起vector失效,但是对于list就不会引起迭代器失效问题。

1.2.2. splice转移

作用:把指定位置转移到指定位置。

前提:转移的数据元素必须一致!

1.2.3. remove

作用:移除链表中是val的值,没有则不做处理

注意,会全部删除,而不仅仅是删除一个

1.2.4. unique去重

作用:排序后,去重复结点

前提:是排序之后才行。

1.2.5. merge合并

作用:合并两个有序链表

前提:两个链表必须排序, 如果不是有序的, 则会断言报错, 并且sort和merge使用的比较函数必须要一致!

1.2.6. sort排序

排序效率一般:这里需要重点强调一下list中的sort排序和vector中的sort排序效率差距还是挺大的,建议数据量比较大的话有条件就用vector进行排序,即使是从list把数据拷贝到vector再拷贝回list.

2. list -- 简单模拟实现

2.1. 实现要点分析

ListNode的成员变量为什么是prev和next两个指针呢?

因为我们要写的是双向链表,对于每个结点而言都需要去存储前一个结点的地址和后一个结点的地址,结点本身还要存储上自己的数据val这样才可以。

list::迭代器的设计(为什么不用原生指针设计迭代器?)

但是这里有个问题:迭代器怎么写???用原生指针typedef一下吗?
当然不行。
在vector中,用原生指针充当迭代器是完全可以的,但是对于list,原生指针++或者--操作之后,会指向内存的下一块区域,问题就是list在内存中实际的存储是不确定的。


重要的原因在于,这个原生指针++、--操作之后的行为不是我们想要的,如果我们可以修改他的++、--行为岂不是很好吗?
于是,我们将原生指针封装为一个类,重载他的运算符,就解决了这个问题。本质上,封装原生指针就是为了扩大我们对指针的控制权限。

总结一下: 原生指针迭代器运算不符合我们预期, 因此我们封装为一个类, 掌控迭代器的行为.

为了便于大家理解,我在这里提出下面几个问题帮助大家进一步理解上面代码:

  • 为什么list的迭代器需要对原生指针进行封装?答:为了重载他的操作符, 掌控迭代器的行为.
  • 迭代器需要写构造函数和拷贝构造函数吗?为什么?构造函数需要写,拷贝构造不用写,因为编译器自动浅拷贝, list迭代器浅拷贝就足够用了。
  • 迭代器需要写析构函数吗?不用,因为迭代器不用考虑结点的释放,释放结点属于list的工作
  • 迭代器什么操作符需要进行重载?根据需要和实际意义,比如在当前场景下迭代器重载大于和小于就没有什么实际意义,可以选择不重载。而需要重点重载的是++, --, 解引用, ... 这些运算符.
  • 上面我们写的iterator类准确来说是类型还是迭代器?是迭代器类型,迭代器是在list中实现的。

请思考一下为什么这个begin()和end()的返回类型是iterator,而不能是iterator&,为什么是迭代器值返回,而不是引用返回呢,毕竟引用返回效率更高啊?

因为迭代器如果返回引用,就会造成很大的问题,毕竟外界可以修改begin和end,这也就会造成begin/end的指向错误。
我用下面例子来进行说明:

operator->的理解

对于自定义类型

struct A
{int _a;int _b;//构造函数A(const int& a = 0, const int& b = 0){_a = a;_b = b;}
};

我想弄个list<A>请问此时应该怎么进行数据遍历呢?
可能你会写出下面的代码:

szg::list<A> la;
A a(1, 1);
la.push_back(a); // 有名对象尾插
la.push_back(A(2,2)); // 匿名对象尾插
la.push_back({3,3}); // CPP11新语法,多参数隐式类型转换//访问
szg::ListIterator<A> itA = la.begin();
while (itA != la.end())
{std::cout << (*itA)._a << " ";std::cout << (*itA)._b << " ";itA++;
}
std::cout << std::endl;

总感觉很奇怪,但是这是正确的。

倘若这不是struct A,而是class A呢???有人可能会说提供Get_A和Get_B函数,一般CPP中不习惯写Get函数,这种JAVA是常用这样的方法的。

CPP会重载一个operator->去解决这个问题。
在ListIterator类中,我们可以写下下面代码:

T* operator->()//返回对应val值的地址
{return &_iterator->_val;
}

之后,我们可以这样写Test:

struct A
{int _a;int _b;//构造函数A(const int a = 0, const int b = 0){_a = a;_b = b;}
};
szg::list<A> la;
A a(1, 1);
la.push_back(a); // 有名对象尾插
la.push_back(A(2,2)); // 匿名对象尾插
la.push_back({3,3}); // CPP11新语法,多参数隐式类型转换//访问
szg::list<A>::iterator it = la.begin();
while (it != la.end())
{/*std::cout << (*itA)._a << " ";std::cout << (*itA)._b << " ";*///std::cout << (*it)._a << " ";std::cout << it->_a << " ";std::cout << it->_b << " ";it++;
}
std::cout << std::endl;

这里需要注意的是,在我们调用operator的时候,编译器对其做了优化,按照逻辑我们需要写为it.operator->()->_a,这里我们可以少写一个->,更加符合我们的使用习惯。

const迭代器问题

不知道大家发现了没有,一直以来都没用过const迭代器去遍历,实际上,在上面所写的代码中就完全没有const的影子,如果用const迭代器就会报语法错误,因为压根没有实现。

倘若你认为这样写:


那我可以告诉你这样的const迭代器跟非const是一样的行为,一样可以修改迭代器指向的内容。

辨析:
const迭代器和非const迭代器的区别???
const迭代器不可修改迭代器指向的内容,非const迭代器可以修改迭代器指向元素的内容。

倘若你灵机一动,说改成这样:


那么你这样就是让迭代器本身不可更改,而不是迭代器指向的内容不可更改

实际上,想要正确写出const迭代器有两种方法:一是再写一个const_iterator迭代器类,再一个就是把const作为一个参数传入ListIterator中,让其根据模板自动生成一份const迭代器类。

下面来依次介绍两种方法:
对于两种方法,都是重新写一个类而已,只不过前者是自己写,后置式让编译器根据模板进行推导罢了。

template<class T>
struct ConstListIterator
{typedef ListNode<T> node;const node* _iterator;//itrator构造ConstListIterator(const node* node):_iterator(node){}//解引用重载const T& operator* ()const{return _iterator->_val;}const T* operator-> ()const//返回对应val值的地址{return &_iterator->_val;}//前置++重载ConstListIterator<T>& operator++(){_iterator = _iterator->_next;return *this;}//后置++重载//ConstListIterator<T> operator++(int)//{//	ListIterator<T> temp(*this); // 构建一个临时对象//	_iterator = _iterator->_next; // 让当前this指向下一个结点//	return temp; // 但是返回临时变量//} //error:temp类型错误。ConstListIterator<T> operator++(int){ConstListIterator<T> tmp(*this);_iterator = _iterator->_next;return tmp;}//!=重载bool operator!=(const ConstListIterator<T>& l){//return this->_iterator != l._iterator;return _iterator != l._iterator;}//==重载bool operator==(const ListIterator<T>& l){return this->_iterator == l._iterator;}
};

上面就是一个写好的const迭代器类,请注意:在list中使用的时候也要用typedef更改一下名字,这样struct ConstListIterator就成为了List类中的内部类,自由使用了。

简单测试:

//test
szg::list<int> li;
li.push_back(1);
li.push_back(2);
li.push_back(3);
li.push_back(4);
li.push_back(5);const szg::list<int> li2 = li;szg::list<int>::const_iterator it = li2.begin();
while (it != li2.end())
{std::cout << *it << " ";it++;
}
std::cout << std::endl;


当然,我们也可以用第二种方式让编译器为我们写const迭代器:

template<class T, class Ref, class Pon>
struct ListIterator
{
public:typedef ListNode<T> node;node* _iterator;
public://itrator构造ListIterator(node* node):_iterator(node){}//解引用重载Ref operator* (){return _iterator->_val;}Pon operator-> ()//返回对应val值的地址{return &_iterator->_val;}//前置++重载ListIterator<T, Ref, Pon>& operator++(){_iterator = _iterator->_next;return *this;}//后置++重载ListIterator<T, Ref, Pon> operator++(int){ListIterator<T, Ref, Pon> temp(*this); // 构建一个临时对象_iterator = _iterator->_next; // 让当前this指向下一个结点return temp; // 但是返回临时变量}//!=重载bool operator!=(const ListIterator<T, Ref, Pon>& l){return this->_iterator != l._iterator;}//==重载bool operator==(const ListIterator<T, Ref, Pon>& l){return this->_iterator == l._iterator;}
};

CPP11快捷构造函数 -- 初始化列表构造

list(std::initializer_list<T> il)
{empty_initialization();//申请一个哨兵位for (auto& node : il)//然后一直尾插数据{push_back(node);}
}

2.2. 代码示例

接口实现清单

typedef ListNode<T> Node;
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;Node* _head;
size_t _size;

  1. 迭代器
    • iterator begin()
    • iterator end()
    • const_iterator begin() const
    • const_iterator end() const
  1. 构造
    • void empty_init()
    • list()
    • list(const list<T>& lt)
    • list<T>& operator=(list<T> lt)
    • ~list()
  1. 增删查改
    • void push_back(const T& x)
    • void push_front(const T& x)
    • void insert(iterator pos, const T& val)
    • void pop_back()
    • void pop_front()
    • iterator erase(iterator pos)
    • void clear()
  1. 其他
    • size_t size() const
    • bool empty()
#pragma once
#include<assert.h>namespace bit
{template<class T>struct ListNode{ListNode<T>* _next;ListNode<T>* _prev;T _data;ListNode(const T& x = T()):_next(nullptr),_prev(nullptr),_data(x){}};// typedef ListIterator<T, T&, T*> iterator;// typedef ListIterator<T, const T&, const T*> const_iterator;template<class T, class Ref, class Ptr>// T: list里面装的数据类型// Ref: 迭代器解引用返回值类型 T& or const T&// Ptr: 迭代器箭头运算符返回值类型 T* or const T*struct ListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;Node* _node;ListIterator(Node* node):_node(node){}// *it//T& operator*()Ref operator*(){return _node->_data;}// it->//T* operator->()Ptr operator->(){return &_node->_data;}// ++itSelf& 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& it){return _node != it._node;}bool operator==(const Self& it){return _node == it._node;}};//template<class T>//struct ListConstIterator//{//	typedef ListNode<T> Node;//	typedef ListConstIterator<T> Self;//	Node* _node;//	ListConstIterator(Node* node)//		:_node(node)//	{}//	// *it//	const T& operator*()//	{//		return _node->_data;//	}//	// it->//	const T* operator->()//	{//		return &_node->_data;//	}//	// ++it//	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& it)//	{//		return _node != it._node;//	}//	bool operator==(const Self& it)//	{//		return _node == it._node;//	}//};template<class T>class list{typedef ListNode<T> Node;public://typedef ListIterator<T> iterator;//typedef ListConstIterator<T> const_iterator;typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, const T*> const_iterator;//iterator begin()//{//	//return iterator(_head->_next);//	iterator it(_head->_next);//	return it;//}iterator begin(){return _head->_next;}iterator end(){return _head;}// const����������Ҫ�ǵ����������޸ģ����ǵ�����ָ������ݣ�// ������ָ������ݲ����޸ģ�const iterator����������Ҫconst������// T* const p1// const T* p2const_iterator begin() const{return _head->_next;}const_iterator end() const{return _head;}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_init();}// lt2(lt1)list(const list<T>& lt){empty_init();for (auto& e : lt){push_back(e);}}// ��Ҫ������һ�����Ҫ�Լ�д���// ����Ҫ������һ��Ͳ���Ҫ�Լ�д�����Ĭ��dz�����Ϳ���void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}// lt1 = lt3list<T>& operator=(list<T> lt){swap(lt);return *this;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}~list(){clear();delete _head;_head = nullptr;}/*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 push_back(const T& x){insert(end(), x);}void push_front(const T& x){insert(begin(), x);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}void insert(iterator pos, const T& val){Node* cur = pos._node;Node* newnode = new Node(val);Node* prev = cur->_prev;// prev newnode cur;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;_size++;}iterator erase(iterator pos){Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;_size--;return iterator(next);}size_t size() const{return _size;}bool empty(){return _size == 0;}private:Node* _head;size_t _size;};
#pragma once
#include <algorithm>
#include <iostream>
#include <cassert>
// #include <list>
using namespace std;namespace zzg
{template<class T>class ListNode{public:T _data;ListNode<T>* _prev;ListNode<T>* _next;public:ListNode(const T& val = T()):_data(val), _prev(nullptr), _next(nullptr){}~ListNode(){// _data = 0; // 模板参数, 不一定可以赋值为0_prev = _next = nullptr;}};template<class T, class Ref, class Ptr>class ListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;public:Node* _ptr;public:ListIterator(Node* ptr = nullptr):_ptr(ptr){}~ListIterator(){_ptr = nullptr;}Self& operator++ (){_ptr = _ptr->_next;return *this;}Self operator++ (int){Self t(*this);_ptr = _ptr->_next;return t;}Self& operator-- (){_ptr = _ptr->_prev;return *this;}Self operator-- (int){Self t(*this);_ptr = _ptr->_prev;return t;}Ref operator* (){return _ptr->_data;}Ptr operator-> (){return &(_ptr->_data);}bool operator != (const Self& it){return _ptr != it._ptr;}bool operator== (const Self& it){return _ptr == it._ptr;}};template <class T>class list{private:typedef ListNode<T> Node;Node* _head;size_t _size;void empty_init(){_head = new Node();_head->_next = _head;_head->_prev = _head;}public: typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, const T*> const_iterator;iterator begin(){return _head->_next;}iterator end(){return _head;}const_iterator begin() const{return _head->_next;}const_iterator end() const{return _head;}public:// 构造 与 析构list() // 默认构造:_head(nullptr), _size(0){empty_init();}list(const list<T>& lt) // 拷贝构造:_head(nullptr), _size(0){empty_init(); // 初始化节点for(Node * cur = lt._head->_next; cur != lt._head; cur = cur->_next){push_back(cur->_data); // 逐个添加元素}}list<T>& operator=(list<T> lt) // 赋值构造{swap(lt);return *this;}~list(){clear();delete _head;_head = nullptr;_size = 0;}// 其他void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}size_t size() const{return _size;}bool empty() const{return _size == 0;}// 增删改查void push_back(const T& x){// Node* ptail = _head->_prev; // 指向最后一个节点// Node* newNode = new Node(x); // 构造新节点//newNode->_prev = ptail; // 新节点的前驱指向尾节点//newNode->_next = _head; // 新节点的后继指向头节点//ptail->_next = newNode; // 尾节点的后继指向新节点//_head->_prev = newNode; // 头节点的前驱指向新节点//_size++; // 更新大小insert(end(), x);}void push_front(const T& x){insert(begin(), x);}void insert(iterator pos, const T& val){Node* cur = pos._ptr;Node* prev = cur->_prev;Node* newNode = new Node(val);newNode->_next = cur;newNode->_prev = prev;cur->_prev = newNode;prev->_next = newNode;_size++;}void pop_back(){erase(--end());}void pop_front(){erase(begin());}iterator erase(iterator pos){assert(begin() != end());Node* cur = pos._ptr;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;_size--;return iterator(next);}void clear(){iterator it = begin();while (it != end()){// cout << it._ptr->_data << endl;/*erase(it);it++;*/ // bug: 如果这样写, 就等于是先把一个东西删除, 再用删除的哪个东西再去取下一个节点的指针it = erase(it);}}// debug函数void print_list(){cout << "-----print_list-----" << endl;for (Node* cur = _head->_next; cur != _head; cur = cur->_next){cout << cur->_data << " ";}cout << endl << "size: " << _size << endl;cout << "-----print_end------" << endl;cout << endl;}};// 构造函数测试void test1(){list<int> l1; // 默认构造l1.push_back(1);l1.push_back(2);l1.print_list();list<int> l2(l1); // 拷贝构造l2.print_list();list<int> l3; // 默认构造l3 = l1; // 赋值构造l3.push_back(7);l3.push_back(9);l3.print_list();}// 增删 测试void test2(){list<int> l1;l1.insert(l1.begin(), 1); // 头插 l1.insert(l1.begin(), 2); // 头插l1.insert(l1.begin(), 3); // 头插l1.insert(l1.end(), 4); // 尾插l1.insert(l1.end(), 5); // 尾插l1.print_list();l1.push_front(0); // 头插l1.push_back(10); // 尾插l1.print_list();// 0 3 2 1 4 5 10l1.pop_back(); // 0 3 2 1 4 5l1.print_list();l1.pop_front(); // 3 2 1 4 5l1.print_list();l1.erase(l1.begin()); // 2 1 4 5l1.print_list();l1.clear();l1.print_list();}void test(){// test1();test2();}
};

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

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

相关文章

GraphQL从入门到精通完整指南

目录 什么是GraphQLGraphQL核心概念GraphQL Schema定义语言查询(Queries)变更(Mutations)订阅(Subscriptions)Schema设计最佳实践服务端实现客户端使用高级特性性能优化实战项目 什么是GraphQL GraphQL是由Facebook开发的一种API查询语言和运行时。它为API提供了完整且易于理…

使用 Dockerfile 与 Docker Compose 结合+Docker-compose.yml 文件详解

使用 Dockerfile 与 Docker Compose 结合的完整流程 Dockerfile 用于定义单个容器的构建过程&#xff0c;而 Docker Compose 则用于编排多个容器。以下是结合使用两者的完整方法&#xff1a; 1. 创建 Dockerfile 在项目目录中创建 Dockerfile 定义应用镜像的构建过程&#xff1…

15 ABP Framework 开发工具

ABP Framework 开发工具 概述 该页面详细介绍了 ABP Framework 提供的开发工具和命令行界面&#xff08;CLI&#xff09;&#xff0c;用于创建、管理和定制 ABP 项目。ABP CLI 是主要开发工具&#xff0c;支持项目脚手架、模块添加、数据库迁移管理及常见开发任务自动化。 ABP …

力扣top100(day02-01)--链表01

160. 相交链表 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode(int x) {* val x;* next null;* }* }*/ public class Solution {/*** 查找两个链表的相交节点* param headA 第一个…

LLM 中 语音编码与文本embeding的本质区别

直接使用语音编码,是什么形式,和文本的区别 直接使用语音编码的形式 语音编码是将模拟语音信号转换为数字信号的技术,其核心是对语音的声学特征进行数字化表征,直接承载语音的物理声学信息。其形式可分为以下几类: 1. 基于波形的编码(保留原始波形特征) 脉冲编码调制…

模型选择与调优

一、模型选择与调优在机器学习中&#xff0c;模型的选择和调优是一个重要的步骤&#xff0c;它直接影响到最终模型的性能1、交叉验证在任何有监督机器学习项目的模型构建阶段&#xff0c;我们训练模型的目的是从标记的示例中学习所有权重和偏差的最佳值如果我们使用相同的标记示…

vue+Django农产品推荐与价格预测系统、双推荐+机器学习预测+知识图谱

vueflask农产品推荐与价格预测系统、双推荐机器学习价格预测知识图谱文章结尾部分有CSDN官方提供的学长 联系方式名片 文章结尾部分有CSDN官方提供的学长 联系方式名片 关注B站&#xff0c;有好处&#xff01;编号: D010 技术架构: vueflaskmysqlneo4j 核心技术&#xff1a; 基…

数据分析小白训练营:基于python编程语言的Numpy库介绍(第三方库)(下篇)

衔接上篇文章&#xff1a;数据分析小白训练营&#xff1a;基于python编程语言的Numpy库介绍&#xff08;第三方库&#xff09;&#xff08;上篇&#xff09;&#xff08;十一&#xff09;数组的组合核心功能&#xff1a;一、生成基数组np.arange().reshape() 基础运算功能&…

负载因子(Load Factor) :哈希表(Hash Table)中的一个关键性能指标

负载因子&#xff08;Load Factor&#xff09; 是哈希表&#xff08;Hash Table&#xff09;中的一个关键性能指标&#xff0c;用于衡量哈希表的空间利用率和发生哈希冲突的可能性。一&#xff1a;定义负载因子&#xff08;通常用希腊字母 λ 表示&#xff09;的计算公式为&…

监控插件SkyWalking(一)原理

一、介绍 1、简介 SkyWalking 是一个 开源的 APM&#xff08;Application Performance Monitoring&#xff0c;应用性能监控&#xff09;和分布式追踪系统&#xff0c;主要用于监控、追踪、分析分布式系统中的调用链路、性能指标和日志。 它由 Apache 基金会托管&#xff0c;…

【接口自动化测试】---自动化框架pytest

目录 1、用例运行规则 2、pytest命令参数 3、pytest配置文件 4、前后置 5、断言 6、参数化---对函数的参数&#xff08;重要&#xff09; 7、fixture 7.1、基本用法 7.2、fixture嵌套&#xff1a; 7.3、请求多个fixture&#xff1a; 7.4、yield fixture 7.5、带参数…

Flink Stream API 源码走读 - socketTextStream

概述 本文深入分析了 Flink 中 socketTextStream() 方法的源码实现&#xff0c;从用户API调用到最终返回 DataStream 的完整流程。 核心知识点 1. socketTextStream 方法重载链 // 用户调用入口 env.socketTextStream("hostname", 9999)↓ 补充分隔符参数 env.socket…

待办事项小程序开发

1. 项目规划功能需求&#xff1a;添加待办事项标记完成/未完成删除待办事项分类或标签管理&#xff08;可选&#xff09;数据持久化&#xff08;本地存储&#xff09;2. 实现功能添加待办事项&#xff1a;监听输入框和按钮事件&#xff0c;将输入内容添加到列表。 标记完成/未完…

【C#】Region、Exclude的用法

在 C# 中&#xff0c;Region 和 Exclude 是与图形编程相关的概念&#xff0c;通常在使用 System.Drawing 命名空间进行 GDI 绘图时出现。它们主要用于定义和操作二维空间中的区域&#xff08;几何区域&#xff09;&#xff0c;常用于窗体裁剪、控件重绘、图形绘制优化等场景。 …

机器学习 - Kaggle项目实践(3)Digit Recognizer 手写数字识别

Digit Recognizer | Kaggle 题面 Digit Recognizer-CNN | Kaggle 下面代码的kaggle版本 使用CNN进行手写数字识别 学习到了网络搭建手法学习率退火数据增广 提高训练效果。 使用混淆矩阵 以及对分类出错概率最大的例子单独拎出来分析。 最终以99.546%正确率 排在 86/1035 …

新手如何高效运营亚马逊跨境电商:从传统SP广告到DeepBI智能策略

"为什么我的广告点击量很高但订单转化率却很低&#xff1f;""如何避免新品期广告预算被大词消耗殆尽&#xff1f;""为什么手动调整关键词和出价总是慢市场半拍&#xff1f;""竞品ASIN投放到底该怎么做才有效&#xff1f;""有没有…

【论文阅读 | CVPR 2024 | UniRGB-IR:通过适配器调优实现可见光-红外语义任务的统一框架】

论文阅读 | CVPR 2024 | UniRGB-IR&#xff1a;通过适配器调优实现可见光-红外语义任务的统一框架​1&&2. 摘要&&引言3.方法3.1 整体架构3.2 多模态特征池3.3 补充特征注入器3.4 适配器调优范式4 实验4.1 RGB-IR 目标检测4.2 RGB-IR 语义分割4.3 RGB-IR 显著目…

Hyperf 百度翻译接口实现方案

保留 HTML/XML 标签结构&#xff0c;仅翻译文本内容&#xff0c;避免破坏富文本格式。采用「HTML 解析 → 文本提取 → 批量翻译 → 回填」的流程。百度翻译集成方案&#xff1a;富文本内容翻译系统 HTML 解析 百度翻译 API 集成 文件结构 app/ ├── Controller/ │ └──…

字节跳动 VeOmni 框架开源:统一多模态训练效率飞跃!

资料来源&#xff1a;火山引擎-开发者社区 多模态时代的训练痛点&#xff0c;终于有了“特效药” 当大模型从单一语言向文本 图像 视频的多模态进化时&#xff0c;算法工程师们的训练流程却陷入了 “碎片化困境”&#xff1a; 当业务要同时迭代 DiT、LLM 与 VLM时&#xff0…

配置docker pull走http代理

之前写了一篇自建Docker镜像加速器服务的博客&#xff0c;需要用到境外服务器作为代理&#xff0c;但是一般可能没有境外服务器&#xff0c;只有http代理&#xff0c;所以如果本地使用想走代理可以用以下方式 临时生效&#xff08;只对当前终端有效&#xff09; 设置环境变量…