list的介绍及模拟实现

news2024/11/18 0:15:19

🌈感谢阅读East-sunrise学习分享——list的介绍及模拟实现

博主水平有限,如有差错,欢迎斧正🙏感谢有你

码字不易,若有收获,期待你的点赞关注💙我们一起进步


今天想分享介绍一下STL的容器之一list,以及进行模拟实现📌

目录

  • 一、list的介绍
  • 二、list的模拟实现(简易版--过渡)
  • 三、迭代器
    • 1.迭代器的定义
    • 2.迭代器的功能分类
    • 3.迭代器失效
    • 4.list迭代器的模拟实现
      • 1.普通迭代器
      • 2.const迭代器
    • 5.迭代器operator->的重载
    • 6.迭代器的价值
  • 四、vector和list的优缺点
    • 1.vector
    • 2.list
  • 五、代码实现

一、list的介绍

  • list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代
  • list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
  • list的底层结构即是链表,这里附上博主之前关于【数据结构】链表的博客【数据结构-链表】有助于熟悉list的底层结构
  • 关于list的使用并不复杂,相信学到这的兄弟以及拥有了查文档的能力,博主就不再赘述list各种接口的具体用法,附上文档介绍list使用文档

有了之前对数据结构——链表的知识基础,其实list的底层结构也并不神秘了,而list相较于前面的string、vector容器特别的地方就在于其迭代器,今天我们重点放在list的模拟实现及迭代器的介绍


二、list的模拟实现(简易版–过渡)

list底层结构是带头双向链表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iCkjCIlz-1672726780572)(C:\Users\DongYu\AppData\Roaming\Typora\typora-user-images\image-20221225161843143.png)]

template<class T>
struct list_node
{
	list_node<T>* _next;//指向下一个节点
	list_node<T>* _prev;//指向前一个节点
	T _data;//节点数据
    //构造函数
	list_node(const T& x)
		:_next(nullptr)
		, _prev(nullptr)
		, _data(x)
	{}
};

定义完list的节点,接下来即是list的主要结构

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

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

	template<class T>
	class list
	{
		typedef list_node<T> node;
	public:
		list()
		{
			_head = new node(T());
			_head->_next = _head;
			_head->_prev = _head;
            _size = 0;
		}
        
		//尾插
		void push_back(const T& x)
		{
			node* newnode = new node(x);
			node* tail = _head->_prev;
			// _head         tail   newnode
			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;
		}

	private:
		node* _head;//哨兵位的头节点
		size_t _size;//记录节点个数
	};

以上便是list的结构框架(简易版实现),目前仅有尾插的接口,用于对list容器中添加数据✏️

添加数据后我们想要遍历打印怎么办呢?那就需要用到STL六大组件之一的迭代器🧮


三、迭代器

1.迭代器的定义

iterator的模式定义:“提供一种方法,使之能够依序巡访某个聚合物(容器)所含的各个元素,而又无需暴露该聚合物的内部表述方式。”

——《STL源码剖析》

🎈通俗理解:容器用于存放数据,存放数据便有访问数据读写数据的需求,STL六大组件之一的迭代器,便是给每个容器提供一个便于读取数据的方法

2.迭代器的功能分类

  1. 单向迭代器:只能++,不能–。例如单链表、哈希表
  2. 双向迭代器:既能++也能–。例如双向链表
  3. 随机访问迭代器:能+±-,也能+和-。例如vector和string

迭代器是内嵌类型,通常定义为内部类或者直接定义在类中

3.迭代器失效

对于list,迭代器在进行insert操作后不失效,在进行erase操作后失效

前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点无效,即该节点被删除了。因为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())
 	{
 		it = l.erase(it);
        //为了避免迭代器失效的问题,erase接口提供了返回值可接收,该返回值为删除后的下一节点
 	}
}

4.list迭代器的模拟实现

1.普通迭代器

在对迭代器模拟实现之前,我们要搞清楚list迭代器要有什么功能?

  1. 支持解引用(读取数据)
  2. 支持++和–(访问上一个or下一个节点)

回顾之前string和vector迭代器的模拟实现,我们是直接将指针typedef为迭代器使用,因为string和vector的底层结构是顺序表,是一段连续的物理空间,所以通过使用原生指针便能符合其迭代器的需求了✔️

💥但是list的底层结构是链表,链表是按需开辟空间,并不是一段连续的物理空间,每个节点的物理地址并不连续,我们无法直接使用原生指针去+±-来遍历来访问节点。我们回顾刚开始接触C++学习的日期类,日期类中的“日期”是我们自己定义的一种自定义类型,无法使用内置操作符直接对日期进行运算操作(编译器又不认识那么多)所以我们是通过自己再去重定义日期的操作类,去重载操作符来满足需求。

🚩类和对象说:该我上场表演了
既然原生指针已经无法满足list迭代器的需求,那么我们可以自己定义一个迭代器,然后将节点指针封装起来,然后再根据我们具体的需求情况去重载各种运算符实现迭代器功能。

//用类封装迭代器
template<class T>
struct __list_iterator
{
	typedef list_node<T> node;
	node* _pnode;//封装一个节点的指针
    
	//用节点指针进行构造
	__list_iterator(node* p)
		:_pnode(p)
	{}
	//迭代器运算符的重载
	T& operator*()
	{
		return _pnode->_data;
	}

	__list_iterator<T>& operator++()
	{
		_pnode = _pnode->_next;
		return *this;//返回的是迭代器
	}

	bool operator!=(const __list_iterator<T>& it)
	{
		return _pnode != it._pnode;
	}
};

定义完迭代器后便能对我们添加了数据的list进行遍历打印了
1.迭代器遍历 2.范围for遍历(底层也是调用了迭代器)

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);
		
	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
}

定义完迭代器后,通过迭代器对容器数据进行访问,实际上是一种函数调用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pyU84UfV-1672726780573)(C:\Users\DongYu\AppData\Roaming\Typora\typora-user-images\image-20221226124335878.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rUjQ5NTn-1672726780573)(C:\Users\DongYu\AppData\Roaming\Typora\typora-user-images\image-20221226124607339.png)]

2.const迭代器

❌const迭代器的错误写法

typedef __list_iterator<T> iterator;
const list<int>::iterator cit = lt.begin();

const之前我们修饰指针时有两种方法

const T* p1;
T* const p2;

正确的const迭代器应该是像p1的行为,保护指向的对象不被修改,但是迭代器本身是可以修改的

但是上述的const迭代器写法是保护了迭代器本身不能被修改,那么我们就不能++迭代器了

✔️正确写法:想实现const迭代器,无法对普通迭代器直接加const,需要再写一个const版本迭代器的类

template<class T>
struct __list_const_iterator
{
	typedef list_node<T> node;
	node* _pnode;

	__list_const_iterator(node* p)
		:_pnode(p)
	{}

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

	__list_const_iterator<T>& operator++()
	{
		_pnode = _pnode->_next;
		return *this;
	}

	__list_const_iterator<T>& operator--()
	{
		_pnode = _pnode->_prev;
		return *this;
	}

	bool operator!=(const __list_const_iterator<T>& it)
	{
		return _pnode != it._pnode;
	}
};

typedef __list_iterator<T> const_iterator;

但是如果像上述一样,写一个普通迭代器再写一个const迭代器,代码看起来就十分的冗长。那么我们可以利用好类模板,类模板即是能根据模板和调用时的参数,根据实参类型推演产生函数的特定类型版本。这样,我们根据传入参数的不同,可以使得一份类模板生成两种类型的迭代器🧮

template<class T, class Ref>
struct __list_iterator
{
	typedef list_node<T> node;
	typedef __list_iterator<T, Ref> Self;
	node* _pnode;

	__list_iterator(node* p)
		:_pnode(p)
	{}
    
	Ref operator*()
	{
		return _pnode->_data;
	}
	
	Self& operator++()
	{
		_pnode = _pnode->_next;
		return *this;
	}

	Self& operator--()
	{
		_pnode = _pnode->_prev;
		return *this;
	}

	bool operator!=(const Self& it)
	{
		return _pnode != it._pnode;
	}
};

typedef __list_iterator<T, T&> iterator;
typedef __list_iterator<T, const T&> const_iterator;

5.迭代器operator->的重载

我们调用对象的成员变量成员函数是用 . 实现,对指针解引用取其值是用 * 实现,而当结构体要解引用是使用 -> ,再用 -> 取其成员变量。而假如现在list中就存放的是结构体类型的数据✏️

所以我们有必要对->也进行重载

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

重载完之后我们要怎么使用呢❓一共有3种方法

//坐标类
struct Pos
{
	int _row;
	int _col;

	Pos(int row = 0, int col = 0)
		:_row(row)
		,_col(col)
	{}
};
void test()
{
	list<Pos> lt;
   	lt.push_back(Pos(1,1));
	lt.push_back(Pos(2,2));
	lt.push_back(Pos(3,3));
    
	// int* p  ---> *p
	// Pos* p  --->  p->
	list<Pos>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << (&(*it))->_row;
        //*it取出容器数据(POS类) -- 再取地址访问解引用得到_row
		cout << it.operator->()->_row;
        //it.operator->()是显式调用,然后再->解引用得到_row
        cout << it->_row;
        //同第二种写法,编译器为了可读性,省略了一个->
        
        ++it;
	}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KhMzOZsV-1672726780574)(C:\Users\DongYu\AppData\Roaming\Typora\typora-user-images\image-20221226133619031.png)]

💥但是当实际使用时,会发现有问题,那就是->的重载返回值为T*,这样一来无论是普通迭代器或const迭代器都能对其值进行修改,所以我们需要将operator->返回值改为泛型,然后针对不同的迭代器给不同的返回参数以示区分,如此一来,我们的迭代器模板又得再多一个参数啦📈

template<class T, class Ref, class Ptr>
Ptr operator->()
{
    return &_pnode->_data;
}

typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;

6.迭代器的价值

  1. 封装底层实现,不暴露底层实现的细节
  2. 多种容器提供统一的访问方式,降低容器使用成本
  3. C语言没有运算符重载和引用等语法,是实现不了迭代器的

四、vector和list的优缺点

vector和list就像左右手一样,是互补配合的关系

vector的优点即是list的缺点,list的优点也是vector的缺点,实际使用时可按照需求择优选用或者结合使用

1.vector

vector的优点

  1. 支持下标的随机访问
  2. 尾插尾删效率高(但是扩容的那一次尾插会较慢)
  3. CPU高速缓存命中高(得益于vector的结构是一段连续的物理空间,数据从缓存加载到CPU时,是会加载连续的一段数据,而不是一个个加载,这样一来在加载vector时高速缓存命中就很高)

🎈综上所述vector的优点都得益于其结构优势

vector的缺点

  1. 非尾插尾删效率极低O(N)
  2. 扩容有消耗,还存在一定的空间浪费

🎈迭代器失效

insert和erase均会导致迭代器失效

2.list

list的优点

  1. 按需申请释放,无需扩容
  2. 任意位置插入删除效率高O(1)

list的缺点

  1. 不支持下标的随机访问
  2. CPU高速缓存命中率低

🎈迭代器失效

insert不失效,erase失效


五、代码实现

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

		list_node(const T& x)
			:_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* _pnode;

		__list_iterator(node* p)
			:_pnode(p)
		{}

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

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

		Self& operator++()
		{
			_pnode = _pnode->_next;
			return *this;
		}

		Self& operator--()
		{
			_pnode = _pnode->_prev;
			return *this;
		}

		Self operator--(int)
		{
			Self tmp(*this);
			_pnode = _pnode->_prev;
			return tmp;
		}

		bool operator!=(const Self& it)
		{
			return _pnode != it._pnode;
		}

		bool operator==(const Self& it) const
		{
			return _pnode == it._pnode;
		}
	};

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


		//构造函数
		list()
		{
			empty_initialize();
		}

		~list()
		{
			clear();

			delete _head;
			_head = nullptr;
		}

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}

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

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

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

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

		void empty_initialize()
		{
			_head = new node(T());
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}

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

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

		// 现代写法
		// lt2(lt1)
		list(const list<T>& lt)
			//list(const list& lt) // 不建议
		{
			empty_initialize();

			list<T> tmp(lt.begin(), lt.end());
			swap(tmp);
		}

		// lt3 = lt1
		list<T>& operator=(list<T> lt)
			//list& operator=(list lt) // 不建议
		{
			swap(lt);
			return *this;
		}

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

			insert(end(), x);
		}

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

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

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

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

			//prev  newnode  cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			++_size;
			return iterator(newnode);
		}

		iterator erase(iterator pos)
		{
			assert(pos != _head);

			node* prev = pos._pnode->_prev;
			node* next = pos._pnode->_next;

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

			delete pos._pnode;
			--_size;

			return iterator(next);
		}

		size_t size()const
		{
			return _size;
		}

		bool empty()const
		{
			return _size == 0;
		}

	private:
		node* _head;
		size_t _size;
	};
}

🌈🌈写在最后
我们今天对list的分享就到此结束了
对于这篇博客最精华的部分便是迭代器的实现,迭代器在各种容器中是不可或缺的一部分🚩
🎈感谢能耐心地阅读到此
🎈码字不易,感谢三连
🎈关注博主,我们一起学习、一起进步

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

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

相关文章

openharmony GPIO 驱动开发

openharmony GPIO 驱动开发GPIO 基础知识GPIO 基础知识——概念GPIO 基础知识——IO 复用GPIO 基础知识——GPIO 分组和编号GPIO 基础知识——用户态测试HDF 框架下 GPIO 驱动HDF 框架下的 GPIO 驱动——案例描述(以 HI3516DV300 平台为例&#xff0c;提供代码)HDF 框架下的 GP…

为什么jvm需要有栈协程?

旧有的servlet生态的线程模型 首先我们先要聊一聊现在我们用的最多的servlet的执行模型是什么&#xff1a; 这个dispatch其实就是一个EventLoop或者说是一个selector来检测注册到其上的链接状态发生的变化 以Tomcat为例子&#xff0c;当这个selector发现存在一个链接可读时&…

【node.js】fs\path\http模块的使用

&#x1f973;博 主&#xff1a;初映CY的前说(前端领域) &#x1f31e;个人信条&#xff1a;想要变成得到&#xff0c;中间还有做到&#xff01; &#x1f918;本文核心&#xff1a;Node.js的fs\path\http模块的使用&#xff0c;模块化开发概念 目录 一、node.js概念与作…

一个曾经分享动态(2021)的回顾和解释-2023-

虽然看过一些典故&#xff0c;里面有名言道&#xff1a; 解释永远是多余的&#xff0c;理解的人不需要&#xff0c;不理解的更不需要。 但是&#xff0c;误会还是需要沟通来消除的。 例如&#xff0c;曾经分享过&#xff1a; 如下都是误会 ↓↓↓↓↓↓↓↓↓ 有朋友联系我&a…

解决东方财富数据接口激活后仍显示reactive的问题

首先确保代码可以在python中导入这个包&#xff1a; from EmQuantAPI import c如果无法导入&#xff0c;就是python没有配置好东方财富的接口&#xff0c;可以参考&#xff1a; Mac版本&#xff1a;Mac使用Python接入东方财富量化接口Choice&#xff0c;调试与获取数据Window…

北京智和信通:信创运维自动化,全栈适配国产软硬件环境

近年来&#xff0c;新基建和信创产业政策东风席卷神州&#xff0c;国产CPU、操作系统、关键应用软件等核心技术步入发展快车道&#xff0c;一批优秀软硬件产品走进政府机关、国企事业单位。在国产软硬件核心技术崛起的过程中&#xff0c;如何迅速搭建起成熟的生态环境是行业面临…

LeetCode135之分发糖果(相关话题:数组,贪心思想)

题目描述 n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。 你需要按照以下要求&#xff0c;给这些孩子分发糖果&#xff1a; 每个孩子至少分配到 1 个糖果。相邻两个孩子评分更高的孩子会获得更多的糖果。 请你给每个孩子分发糖果&#xff0c;计算并返回需…

【node.js】跨域的解决办法(CORS方法、同源策列的理解)

&#x1f973;博 主&#xff1a;初映CY的前说(前端领域) &#x1f31e;个人信条&#xff1a;想要变成得到&#xff0c;中间还有做到&#xff01; &#x1f918;本文核心&#xff1a;面对cors跨域、同源策略的处理 下图为本文的核心 目录 一、 跨域介绍 二、同源策略 三…

正态分布与numpy.random.normal函数

文章目录1. 正态分布2. numpy.random.normal函数3. 示例在Numpy中&#xff0c;有一个专门用于生成符合正态分布的随机数函数&#xff1a;numpy.random.normal&#xff0c;本文我们梳理一下它的使用方法&#xff0c;在梳理前&#xff0c;需要先了解一下什么是正态分布。 1. 正态…

黑马Hive+Spark离线数仓工业项目-任务流调度工具AirFlow(1)

任务流调度工具AirFlow 1. AirFlow介绍【了解】 - 功能、特点 - 架构角色、安装部署 2. **AirFlow使用【掌握】** - 核心&#xff1a;调度脚本【Python | Shell】 - 定时调度&#xff1a;Linux Crontab表达式 - 邮件告警&#xff1a;配置 3. 回顾Spark核心概念 - 存…

【10个基本网络故障排查工具-每个IT专业人员应了解】

网络故障排除工具是每个网络管理员的必需品。 在网络领域入门时&#xff0c;重要的是要积累一些可用于解决各种不同网络状况的工具。 虽然特定工具的使用确实是主观的并且由工程师自行决定&#xff0c;但本文中的工具选择是基于它们的一般性和通用性。 本文回顾了可帮助您解决大…

聊聊业务项目如何主动感知mysql是否存活

前言 先前写过一篇文章聊聊如何利用redis实现多级缓存同步,里面讲到业务部门因数据库宕机&#xff0c;有技术提出当数据库宕机&#xff0c;切换到redis&#xff0c;今天我们就来聊聊如何触发这个切换动作&#xff1f; 1、方案一&#xff1a;利用异常机制 伪代码如下&#xf…

大三寒假人生第一次面试失败

2022/12/28&#xff0c;今天是人生第一次面试。坐了2个小时的地铁去面试结果却很惨。一开始进门就笔试&#xff0c;当看到笔试题时发现很多基础&#xff0c;平时耳熟能详的词汇却怎么样也回答不出来。做了一个多小时&#xff0c;当面试官把题改了以后一句笔试没过。说真的在出门…

RocketMQ消息队列的下载、配置、启动、测试

目录 下载 环境变量的配置 新建一个变量 配置path 新建变量 启动 命名服务器 启动broker 测试是否启动成功 下载 地址&#xff1a;RocketMQ 官方网站 | RocketMQ 切换到中文模式很容易看的 下载那一列就行了 安装很容易的。 环境变量的配置 新建一个变量 就是你的bin文…

No.181# 点直播简要架构梳理走查

引言直播带货、潮流电商、短视频不断融合&#xff0c;本文走查下音视频直播的简要架构和角色。选择UDP&#xff0c;注重传输实时性&#xff0c;在线教育、音视频会议等。选择TCP&#xff0c;注重画面质量、是否卡顿等&#xff0c;娱乐直播、直播带货等。本文主要内容有&#xf…

RPA:帮助企业完成财务数字化转型

为什么要做财务的数字化转型 a. 传统企业财务的现状 “重复性强、耗时耗力、效率低下”是目前大家对传统企业财务的固有印象。很多企业的财务部门仍然采用传统的手工操作模式&#xff0c;财务流程繁琐分散&#xff0c;且财务部门缺乏获取、处理数据的工具。绝大部分的人力都投…

2022LOL微博杯模糊问题,1080p高清看微博杯the shy比赛直播

2022LOL微博杯的直播模糊&#xff0c;看着不爽 观看方法 1.打开下面在线播放m3u8文件的地址 http://www.m3u8.zone/ 如图 2.输入播放地址 微博杯的播放地址&#xff1a; &#xff08;1月三号的地址 如果失效往下看解决方法&#xff09; https://plwb01.live.weibo.com/ali…

前端数据结构与算法

前端数据结构与算法 文章宝典 链表 可以快速删除和插入节点&#xff0c;只用修改节点的引用 实例 队列 实例 栈 实例 树 并且左节点的值和后续节点的值都要小于等于该节点的值 图 根据图的节点之间的边是否有方向&#xff0c;可以分为有向图和无向图。 在有向图…

数字调制系列:如何理解IQ ?

最近在筹划写一系列关于数字IQ 调制的短文&#xff0c;以帮助初学者能够更好地理解和掌握。虽然IQ 调制技术已经非常广泛地应用于各种无线通信应用中&#xff0c;但是究其细节&#xff0c;仍有很多人存在疑惑&#xff0c;尤其对于初学者。作者从事测试工作多年&#xff0c;对IQ…

强化学习的Sarsa与Q-Learning的Cliff-Walking对比实验

强化学习的Sarsa与Q-Learning的Cliff-Walking对比实验Cliff-Walking问题的描述Sarsa和Q-Learning算法对比代码分享需要改进的地方引用和写在最后Cliff-Walking问题的描述 悬崖行走&#xff1a;从S走到G&#xff0c;其中灰色部分是悬崖不可到达&#xff0c;求可行方案 建模中&am…