【C++】STL之list深度剖析及模拟实现

news2025/1/13 2:47:55

目录

前言

一、list 的使用

 1、构造函数

2、迭代器

3、增删查改

4、其他函数使用

二、list 的模拟实现

 1、节点的创建

 2、push_back 和 push_front

 3、普通迭代器

 4、const 迭代器

 5、增删查改(insert、erase、pop_back、pop_front)

 6、构造函数和析构函数

  6.1、默认构造

  6.2、构造 n 个 val 的对象

  6.3、拷贝构造

  6.4、迭代器区间构造

  6.5、 赋值运算符重载

  6.6、析构函数

三、list 模拟实现源代码

四、list 的迭代器失效

五、list 和 vector的对比


前言

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个元素和后一个元素。
  3. list 与 forward_list 非常相似:最主要的不同在于 forward_list 是单链表,只能朝前迭代,已让其更简单高效。
  4. 与其他的序列式容器相比(array,vector,deque),list 通常在任意位置进行插入、移除元素的执行效率更好。
  5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问.

一、list 的使用

 1、构造函数

构造函数接口说明
list (size_type n, const value_type& val = value_type())构造的list中包含n个值为val的元素
list()构造空的list
list (const list& x)拷贝构造函数
list (InputIterator first, InputIterator last)用[first, last)区间中的元素构造list
int main()
{
	// 默认构造
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	// 拷贝构造
	list<int> lt2(lt);
	// 构造 n 个节点
	list<int> lt3(5, 1);
	// 迭代器区间构造
	list<int> lt4(lt.begin(), lt.end());

	return 0;
}

2、迭代器

函数声明接口说明
begin + end返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin + rend返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的 reverse_iterator,即begin位置
int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> lt(a, a + 9);
	auto it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	return 0;
}

迭代器一般是用来遍历和查找的; 

而反向迭代器的使用是类似的,只不过调用的函数换成了 rbegin 和 rend 。

注意:反向迭代器的迭代使用的也是++。但迭代器区间一样是[rbegin, rend);

3、增删查改

函数声明接口说明
push_front在list首元素前插入值为 val 的元素
pop_front删除 list 中第一个元素
push_back在list尾部插入值为 val 的元素
pop_back删除 list 中最后一个元素
insert在list position 位置中插入值为 val 的元素
erase删除list position 位置的元素
swap交换两个 list 中的元素
clear清空 list 中的有效元素
int main()
{
	vector<int> v = { 1,2,3,4,5,6,7,8,9 };
	list<int> lt(v.begin(), v.end());
	for (auto e : lt) cout << e << " ";
	cout << endl;

	lt.push_front(10);
	lt.push_back(20);
	for (auto e : lt) cout << e << " ";
	cout << endl;

	lt.pop_front();
	lt.pop_back();
	for (auto e : lt) cout << e << " ";
	cout << endl;

	auto pos = find(lt.begin(), lt.end(), 5);
	lt.insert(pos, 50);
	for (auto e : lt) cout << e << " ";
	cout << endl;

	pos = find(lt.begin(), lt.end(), 8);
	lt.erase(pos);
	for (auto e : lt) cout << e << " ";
	cout << endl;

	return 0;
}

4、其他函数使用

函数声明接口说明
empty检测 list 是否为空,是返回 true ,否则返回 false
size返回 list 中有效节点的个数
front返回 list 的第一个节点中值的引用
back返回 list 的最后一个节点中值的引用

二、list 的模拟实现

 1、节点的创建

template<class T>
struct list_node//节点
{
	list_node<T>* _next;
	list_node<T>* _prev;
	T _data;
    // 构造函数
	list_node(const T& x = T())
		:_next(nullptr)
		, _prev(nullptr)
		, _data(x)
	{}
};

   由于节点存储的数据可能是任意类型,所以我们需要将将节点定义为模板类。这里我们需要写一个给缺省值的默认构造函数,便于之后在主类中new一个新节点时直接初始化,同时将两个指针置为空,将数据写入数据域中。

 2、push_back 和 push_front

class list 
{
public:
	typedef list_node<T> node;
	
private:
	node* _head;
}
//尾插
void push_back(const T& x) const
{
	node* new_node = new node(x);
	node* tail = _head->_prev;
	//链接节点之间的关系
	tail->_next = new_node;
	new_node->_prev = tail;
	new_node->_next = _head;
	_head->_prev = new_node;
}
//头插
void push_front(const T& x)
{
	node* head = _head->_next;
	node* new_node = new node(x);

	_head->_next = new_node;
	new_node->_prev = _head;
	new_node->_next = head;
	head->_prev = new_node;
}

 这里模拟的头插和尾插也很简单,因为和我们之前在数据结构时候的双向循环链表是一样的,只需要找到头或者尾,然后链接四个节点间的关系即可。

 3、普通迭代器

注意:list 的迭代器是自定义类型,不是原生指针node*。

迭代器为自定义类型,其中*,++等都是通过运算符重载来完成的。

所以我们需要重载的符号:*,->,前置++,后置++,前置--,后置--,!=,==

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

	//构造函数
	__list_iterator(node* n)
		:_node(n)
	{}
	//重载*运算符
	T& operator*()
	{
		return _node->_val;
	}
	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)
	{
		return _node != s._node;
	}
	//重载==运算符
	bool operator==(const self& s)
	{
		return _node == s._node;
	}
};

 此处我实现了一个简单的正向迭代器,使用一个模板参数T表示类型。

 当普通迭代器封装好了之后,我们需要在list类中来实现它的 begin() 和 end() 方法。由于迭代器的名字一般都是 iterator,而且对于范围for来说,也只能通过 iterator 来转换为迭代器进行遍历。所以这里我们将其typedef为iterator。

template<class T>
class list//链表
{
	typedef list_node<T> node;
public:
	typedef __list_iterator<T> iterator;

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

	iterator end()
	{
		return iterator(_head);
	}
private:
	node* _head;
};

 4、const 迭代器

  const迭代器与普通迭代器的区别在于const迭代器指向的内容是不能修改的,但是它的指向是可以修改的。

template<class T>
class list//链表
{
	typedef list_node<T> node;
public:
	typedef __list_const_iterator<T> const_iterator;

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

	const_iterator end()
	{
		return const_iterator(_head);
	}
private:
	node* _head;
};

  我们最好的做法就是在__list_iterator 的类模板中的添加两个模板参数,然后再 list 类中 typedef 两份分别将第二个参数分别改成 T& 和 const T& 的类型,本质上就是让编译器根据传入的 Ref 的不同来自动示例化出 const 迭代器类,而我们还需要重载一个->运算符,因为list中可能存储的是自定义类型,这个自定义类型如果要是有多个成员变量的话,我们就需要使用->来解引用访问成员变量,同样还是要区分普通迭代器和const 迭代器,所以就增加了另一个模版参数 Ptr。具体的解决做法如下:

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* n)
		:_node(n)
	{}

	Ref operator*()//解引用
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	...
};

然后,最终在链表类中使用如下:

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;//const迭代器

	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);
	}
private:
	node* _head;
};

 5、增删查改(insert、erase、pop_back、pop_front)

// 指定位置插入
void insert(iterator pos, const T& x)
{
	node* cur = pos._node;
	node* prev = cur->_prev;
	node* new_node = new node(x);

	prev->_next = new_node;
	new_node->_prev = prev;
	new_node->_next = cur;
	cur->_prev = new_node;
}
// 指定位置删除
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);
}
// 尾删
void pop_back()
{
	erase(--end());
}
// 头删
void pop_front()
{
	erase(begin());
}

 6、构造函数和析构函数

  6.1、默认构造

  由于后面会频繁对空进行初始化,所以在这里对它进行了封装,方便后面的调用。

void empty_init()//空初始化
{
	_head = new node;
	_head->_next = _head;
	_head->_prev = _head;
}
list()
{
	empty_init();
}

  6.2、构造 n 个 val 的对象

//用n个val构造对象
list(int n, const T& val = T())
{
	empty_init();
	for (int i = 0; i < n; i++)
	{
		push_back(val);
	}
}

  6.3、拷贝构造

//拷贝构造传统写法
list(const list<T>& lt)
{
	empty_init();
	for (auto e : lt)
	{
		push_back(e);
	}
}
//拷贝构造现代写法
list(const list<T>& lt)
{
	empty_init();
	list<T> tmp(lt.begin(), lt.end());
	swap(tmp);
}

  6.4、迭代器区间构造

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

  6.5、 赋值运算符重载

//赋值运算符重载
list<T>& operator=(list<T> lt)//注意这里不能用引用
{
	swap(lt);
	return *this;
}

  6.6、析构函数

//要全部清理掉
~list()
{
	clear();
	delete _head;
	_head = nullptr;
}
//不释放头结点
void clear()
{
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);
		//这样也可以
		//erase(it++);
	}
}

三、list 模拟实现源代码

template<class T>
struct list_node//节点
{
	list_node<T>* _next;
	list_node<T>* _prev;
	T _data;

	list_node(const T& x = T())
		:_next(nullptr)
		, _prev(nullptr)
		, _data(x)
	{}
};
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* n)
		:_node(n)
	{}

	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;//const迭代器

	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);
	}
	void empty_init()//空初始化
	{
		_head = new node;
		_head->_next = _head;
		_head->_prev = _head;
	}
	list()
	{
		empty_init();
	}
	//迭代器区间构造
	template <class Iterator>
	list(Iterator first, Iterator last)
	{
		empty_init();
		while (first != last)
		{
			push_back(*first);//push_back使用的前提是要有哨兵位的头结点
			++first;
		}
	}
	// 交换函数
	void swap(list<T>& tmp)
	{
		std::swap(_head, tmp._head);
	}
	//现代拷贝构造
	list(const list<T>& lt)
	{
		list<T> tmp(lt.begin(), lt.end());
		swap(tmp);
	}
	//现代赋值写法
	list<T>& operator=(list<T> lt)
	{
		swap(lt);
		return *this;
	}
	~list()//要全部清理掉
	{
		clear();
		delete _head;
		_head = nullptr;
	}
	void clear()//不释放头结点
	{
		iterator it = begin();
		while (it != end())
		{
			it = erase(it);
			//这样也可以
			//erase(it++);
		}
	}
	void insert(iterator pos, const T& x)
	{
		node* cur = pos._node;
		node* prev = cur->_prev;
		node* new_node = new node(x);

		prev->_next = new_node;
		new_node->_prev = prev;
		new_node->_next = cur;
		cur->_prev = new_node;
	}
	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);
	}
	//尾插
	void push_back(const T& x) const
	{
		//node* new_node = new node(x);
		//node* tail = _head->_prev;
		链接节点之间的关系
		//tail->_next = new_node;
		//new_node->_prev = tail;
		//new_node->_next = _head;
		//_head->_prev = new_node;
		insert(end(), x);
	}
	//头插
	void push_front(const T& x)
	{
		//node* head = _head->_next;
		//node* new_node = new node(x);

		//_head->_next = new_node;
		//new_node->_prev = _head;
		//new_node->_next = head;
		//head->_prev = new_node;
		insert(begin(), x);
	}
	//尾删
	void pop_back()
	{
		erase(--end());
	}
	//头删
	void pop_front()
	{
		erase(begin());
	}
private:
	node* _head;
};

四、list 的迭代器失效

  当我们使用 erase 进行删除后,此时指向删除位置的迭代器就失效了,再次使用就会令程序崩溃。

  因此若要多次删除,则需要在使用后利用 erase 的返回值更新迭代器,这样使用才不会出现错误。

int main()
{
	vector<int> v = { 1, 2,3,5,4,6 };
	list<int> lt(v.begin(), v.end());
	list<int>::iterator pos = find(lt.begin(), lt.end(), 3);
	for (int i = 0; i < 3; i++)
	{
		pos = lt.erase(pos);   //利用erase的返回值更新迭代器
	}
	for (auto e : lt) cout << e << " ";
	cout << endl;
	return 0;
}

五、list 和 vector的对比

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


本文要是有不足的地方,欢迎大家在下面评论,我会在第一时间更正。

 

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

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

相关文章

BGP-IBGP(第九课)

BGP-IBGP(第九课) 0 IBGP 水平分割的概念 1 BGP 反射器 2 IBGP 互联不了的原因 3 BGP 通告原则 4 IBGP 互通 方案一 静态路由 5 IBGP 互通 方案二 OSPF 中引入BGP 6 IBGP 互通 方案三 全网互通 7 IBGP 互通 方案四 反射器 0 IBGP 水平分割的概念 通过IBGP获得的最优路由不会发…

网页文本编辑器

<!DOCTYPE html> <html><head><meta charset"utf-8"><title>文本编辑器</title><style>.box {height: 500px;width: 1000px;margin: auto;background-color: navajowhite;}.box_h {/* font-weight: normal; */text-al…

基于springboot小区疫情防控系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

前端性能优化汇总

1.减少HTTP请求次数和请求的大小 &#xff08;三大类&#xff09; 文件的合并和压缩&#xff1a;&#xff08;1&#xff09;&#xff08;6&#xff09; 延迟加载&#xff1a;&#xff08;3&#xff09;&#xff08;4&#xff09; 用新的文件格式代替传统文件格式&#xff1a;&a…

百度SEO优化基本原理(掌握SEO基础,提高网站排名)

随着互联网的迅速发展&#xff0c;越来越多的企业开始意识到网站优化的重要性&#xff0c;其中百度SEO优化是企业不可忽视的一项工作。本文将介绍百度SEO优化的基本概念、步骤、原理、解决方法和提升网站标题优化的方法。蘑菇号-www.mooogu.cn 百度SEO优化是指针对百度搜索引擎…

[源码系列:手写spring] AOP第一节:切点表达式

在本专栏之前的文章中已经带大家熟悉了Spirng中核心概念IOC的原理以及手写了核心代码&#xff0c;接下来将继续介绍Spring中另一核心概念AOP。 AOP即切面编程是Spring框架中的一个关键概念&#xff0c;它允许开发者在应用程序中优雅地处理横切关注点&#xff0c;如日志…

c++程序内存区域划分

目录 内存区域划分 例题&#xff1a; malloc&#xff0c;calloc和realloc的区别 new和delete 申请空间并初始化 申请多个空间&#xff1a; new和delete对于自定义类型的处理&#xff1a; new和delete一定要匹配 new和malloc以及delete和free的区别 抛异常的使用方法&…

ubuntu 开启笔记本摄像头并修复画面颠倒问题

文章目录 基本环境状况&#xff1a; 没找到摄像头检查 opencv检查系统应用 键盘右侧&#xff0c;硬件层面开启摄像头画面镜像问题 基本环境 笔记本&#xff1a; 联想拯救者 系统&#xff1a; ubuntu 22.04 状况&#xff1a; 没找到摄像头 检查 opencv 使用 cv::VideoCaptu…

vue elemnt封装文件上传 +根据后台接口来上传文件

1.创建一个新的子页面,放文件上传 <template> <div><el-uploadaction"https://jsonplaceholder.typicode.com/posts/"list-type"picture-card":on-preview"handlePictureCardPreview":on-remove"handleRemove">&l…

7.网络原理之TCP_IP

文章目录 1.网络基础1.1认识IP地址1.2子网掩码1.3认识MAC地址1.4一跳一跳的网络数据传输1.5总结IP地址和MAC地址1.6网络设备及相关技术1.6.1集线器&#xff1a;转发所有端口1.6.2交换机&#xff1a;MAC地址转换表转发对应端口1.6.3主机&#xff1a;网络分层从上到下封装1.6.4主…

交换机技术综述(第十一课)

交换机技术综述基础 1 Vlan技术的学习 三大接口 Vlan技术实操(第四课)_IHOPEDREAM的博客-CSDN博客https://drean.blog.csdn.net/article/details/132455765?spm=1001.2014.3001.5502

论文阅读:DisCO Portrait Distortion Correction with Perspective-Aware 3D GANs

论文阅读&#xff1a;DisCO Portrait Distortion Correction with Perspective-Aware 3D GANs 今天介绍一篇比较有趣的文章&#xff0c;通过 3D GAN inversion 来解决成像的透视畸变问题 Abstract 文章的摘要&#xff0c;一开始就介绍说&#xff0c;近距离成像的时候&#x…

【3维视觉】20230922_网格编码最新进展

网格编码技术研究进展 1. VDMC编码技术 1.1 VDMC介绍 1.1.1 编码对象 具有时变拓扑的动态网格 1.1.2 技术细节 VDMC的编码和解码过程的高层框图如图2所示[4][5]。预处理模块提供了更好的率失真( Rate-Distortion&#xff0c;RD )性能&#xff0c;支持可伸缩解码和渐进传输…

Minecraft 1.20.x Forge模组开发 06.建筑生成

我们本次尝试在主世界生成一个自定义的建筑。 效果展示 效果展示 效果展示 由于版本更新缘故,1.20的建筑生成将不涉及任何Java包的代码编写,只需要在数据包中对建筑生成进行自定义。 1.首先我们要使用游戏中的结构方块制作一个建筑,结构方块使用教程参考1.16.5自定义建筑生…

记一次hyperf框架封装swoole自定义进程

背景 公司准备引入swoole和rabbitmq来处理公司业务。因此&#xff0c;我引入hyperf框架&#xff0c;想用swoole的多进程来实现。 自定义启动服务封装 <?php /*** 进程启动服务【manager】*/ declare(strict_types1);namespace App\Command;use Swoole; use Swoole\Proce…

软考知识产权基础知识

商标权可以根据需要无限延长 根据《商标法》的规定&#xff0c;商标的有效期为10年&#xff0c;自商标注册之日起计算。有效期届满后&#xff0c;可以递交商标续展申请。每次续展的有效期为10年。但是&#xff0c;商标续展仅限于最后一年有效期也就是期满前六个月内提交申请。…

服务注册发现_actuator微服务信息完善

SpringCloud体系里的&#xff0c;服务实体向eureka注册时&#xff0c;注册名默认是IP名:应用名:应用端口名。 问题&#xff1a; 自定义服务在Eureka上的实例名怎么弄呢 在服务提供者pom中配置Actuator依赖 <!-- actuator监控信息完善 --> <dependency><groupId…

011_第一代软件开发(三)

第一代软件开发(三) 文章目录 第一代软件开发(三)项目介绍带下知识点系统日志滤波器陷波滤波器带通滤波器 打印初始化调用打印机打印文件保存到PDF 总结一下 关键字&#xff1a; Qt、 Qml、 日志、 打印、 滤波器 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这…

排序算法:非比较排序(计数排序)

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关排序算法的相关知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通…

2018 国际AIOps挑战赛单指标数据集分析

关于数据集 2018年国际AIOps 由中国建设银行、清华大学以及必示科技公司联合举办&#xff0c;尽管已经过去了这么长时间&#xff0c;其提供的比赛数据依然被用于智能运维相关算法的研究。这里我们对此数据集做简单的分析&#xff0c;把一些常用的数据分析方法在这里进行略微地…