STL—stack/queue/priority_queue_/deque

news2024/11/26 22:37:05

STL—stack和queue

之前我们学了string和vector和list,并且完成了它们的模拟实现,他们都是很重要的容器。

这次要学习的stack和queue不是容器,是——容器适配器

stack和queue

1. stack

1.1 stack的介绍

stack 的文档介绍

翻译:

  1. stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。

  2. stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。

  3. stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:

  • empty:判空操作

  • back:获取尾部元素操作

  • push:栈顶插入元素操作

  • pop:栈顶弹出元素操作

  1. 标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque。

image-20240816171026504

1.2 stack的使用

image-20240816171157541

stack的使用还是比较简单的。接口本身也不多,知道基本接口的功能就行了。

下面这个代码实现了遍历一个stack

void test_stack1()
{
	stack<int> st;
	st.push(1);
	st.push(2);
	st.push(3);
	st.push(4);

	// stack是没有迭代器的
	while (!st.empty())
	{
		cout << st.top() << " ";
		st.pop();
	}
	cout << endl;

}

还可以通过做一些stack的oj题来熟悉其使用

最小栈

class MinStack {
public:
    MinStack()
    {
        // 这里不用实现也是没关系的,因为我们的两个成员变量是自定义类型,会调用其系统默认的构造函数。
    }

    void push(int val)
    {
        _st.push(val);
        // 要判断_min是否为空,并且该val是否是目前所存储数据的最小值
        if (_min.empty() || _min.top() >= val)
            _min.push(val); // 是的话就要插入到_min中
    }

    void pop()
    {
        int n = _st.top();
        _st.pop();
        // 判断弹出去的元素是否是最小值
        if (_min.top() == n)
            _min.pop();
    }

    int top()
    {
        return _st.top();
    }

    int getMin()
    {
        // 返回存储最小值的栈的栈顶元素
        return _min.top();
    }

private:
    stack<int> _st; // 这个用来存放数据
    stack<int> _min; // 这个用来存放最小值
};

栈的压入、弹出序列

class Solution {
public:

    bool IsPopOrder(vector<int>& vpush, vector<int>& vpop)
    {
        // 首先判断两个序列的元素个数是否相同
        if(vpush.size() != vpop.size())
            return false;

        int outi = 0; 
        int ini = 0;
        stack<int> st; // 用st来模拟入栈出栈的过程。

        // 判断vpop的弹出顺序是否合理
        while(outi < vpop.size())
        {
            // 只要st的栈顶元素,不是弹出顺序的第outi个,那就插入压入顺序的元素,然后再判断,循环。
            while(st.empty() || st.top() != vpop[outi])
            {
                if(ini < vpush.size())
                    st.push(vpush[ini++]);
                else // 如果ini已经大于等于vpush.size()了,说明弹出顺序不合法
                    return false;
            }
            // 走到这里说明栈顶元素和弹出顺序的第outi个元素相同,那就弹出
            st.pop();
            outi++;
        }

        // 走到这里就说明st已经按照vpop的弹出顺序清空了,那说明vpop合法
        return true;
    }

};

逆波兰表达式求值

class Solution {
public:
    int evalRPN(vector<string>& tokens) 
    {
        stack<int> st;
        // 先把后缀算术表达式(逆波兰表达式)进行拆分
        // 1.如果是操作符,那就去栈里的两个数字进行计算
        // 2.如果是数字就入栈
        for(auto& str : tokens)
        {
            // 操作符就取两个数字进行运算
            if(str == "+" || str == "-" || str == "/" || str == "*")
            {
                // 要注意取出来的第一个数字在中缀表达式中是放在右边的.
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();

                // 根据操作符,来进行相应的计算
                switch(str[0]) // switch无法处理自定义类型。
                {
                    case '+':
                        st.push(left + right);
                        break;
                    case '-':
                        st.push(left - right);
                        break;
                    case '*':
                        st.push(left * right);
                        break;
                    case '/':
                        st.push(left / right);
                        break;
                }
            }
            else
            {
                // stoi是c++11支持的接口。可以将string类转化为int类
                st.push(stoi(str));
            }
        }
        
    // 最后栈里剩下的最后一个数字就是计算之后的结果
    return st.top();
    }
};

用栈实现队列

class MyQueue {
public:
    MyQueue() 
    {
        // stack类型可以调用系统默认的构造函数
    }
    
    void push(int x) 
    {
        // 插入数据,插入到_stpush中
        _stpush.push(x);
    }
    
    int pop() 
    {
        // pop要将队头的元素移除并返回
        // 首先要判断_stpop是否为空,空的就要将_stpush的数据弹出,插入_stpop
        if(_stpop.empty())
        {
            // 这个while循环和范围for都可以
            while(!_stpush.empty())
            {
                _stpop.push(_stpush.top());
                _stpush.pop();
            }
        }
        int ret = _stpop.top();
        _stpop.pop();

        return ret;
    }
    
    int peek() 
    {
        // 首先要判断_stpop是否为空,空的就要将_stpush的数据弹出,插入_stpop
        if(_stpop.empty())
        {
            // 这个while循环和范围for都可以
            while(!_stpush.empty())
            {
                _stpop.push(_stpush.top());
                _stpush.pop();
            }
        }

        return _stpop.top();
    }
    
    bool empty() 
    {
        // 两个栈都为空,该队列才为空
        return _stpush.empty() && _stpop.empty();
    }
private:
    // 用两个栈去实现队列
    // 由于栈是先进后出,队列是先进先出
    stack<int> _stpush; // 这个栈用于插入·数据
    stack<int> _stpop; // 这个栈用于出数据
};

1.3stack的模拟实现

前面我们说了stack是一个容器适配器,我们可以借助其他容器作为实现stack的底层。

不论是list还是vector都可以实现stack。因为stack是一个特殊的vector和list。

借助模版我们就可以借助其他容器。

namespace wzf
{
	// 栈是容器适配器,可以借助vector或者list作为底层来实现
	template<class T, class container>
	class stack
	{
	public:

		void pop()
		{
			_con.pop_back();
		}

		T& top()
		{
			return _con.back();
		}

		void push(const T& x)
		{
			_con.push_back(x);
		}

		size_t size()
		{
			return _con.size();
		}

		bool empty()
		{
			return _con.empty();
		}
	private:
		container _con;
	};
}

测试代码:

	void test1()
	{
		stack<int, vector<int>> st;		
        //stack<int, list<int>> st; // list容器作为底层也可以
		st.push(1);
		st.push(2);
		st.push(3);
		st.push(4);

		while (!st.empty())
		{
			cout << st.top() << " ";
			st.pop();
		}
		cout << endl;
	}

这里这个测试我们传的是vector容器,传list也可以。

image-20240825165249634

2.queue

2.1queue的介绍

queue 的文档介绍

  1. 队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。

  2. 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。

  3. 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:

  • empty:检测队列是否为空

  • size:返回队列中有效元素的个数

  • front:返回队头元素的引用

  • back:返回队尾元素的引用

  • push_back:在队列尾部入队列

  • pop_front:在队列头部出队列

  1. 标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。

2.2queue的使用

image-20240826154743614

OJ题目:

用队列实现栈

2.3queue的模拟实现

queue和stack一样属于容器适配器,但是vector并不太能作为实现queue的底层容器,因为vector的头删和头插效率比较低,但是queue又需要存在头删这个接口,因此最好用list来做queue的底层。

代码如下:

namespace wzf
{
	template<class T, class container>
	class queue
	{
	public:

		queue()
		{}

		void push(const T& x)
		{
			_con.push_back(x);
		}

		void pop()
		{
			_con.pop_front();
		}

		size_t size()
		{
			return _con.size();
		}

		T& front()
		{
			return _con.front();
		}

		T& back()
		{
			return _con.back();
		}

		bool empty()
		{
			return _con.empty();
		}


	private:
		container _con;
	};
}

测试代码:

	void test1()
	{
		queue<int, list<int>> q;
        //queue<int, deque<int>> q; //deque也可以
		q.push(1);
		q.push(2);
		q.push(3);
		q.push(4);

		while (!q.empty())
		{
			cout << q.front() << " ";
			q.pop();
		}
		cout << endl;
	}

3.priority_queue

3.1priority_queue的介绍

优先级队列文档查阅

  1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。

  2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。

  3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。

  4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:

  • empty:检测队列是否为空
  • size:返回队列中有效元素的个数
  • front:返回容器中第一个元素的引用
  • push_back:在容器尾部插入元素
  • pop_front:删除容器尾部元素
  1. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。
  2. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作

3.2priority_queue的使用

image-20240829173615604

优先级队列的使用也不难,我们来看一段代码就行了:

void test_priority_queue1()
{
	// priority也是一个容器适配器.不支持迭代器
	priority_queue<int> pq; // 默认大的优先级高
	pq.push(1);
	pq.push(3);
	pq.push(9);
	pq.push(5);
	// 这些数据插入的时候会根据优先级排序,最大的会在队头。类似堆,其实优先级队列的底层就是一个堆

	while (!pq.empty())
	{
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;
}

并且优先级队列的默认优先级是大的优先,那我们如果想修改它的优先级也是可以做到的。

void test_priority_queue2()
{
	// 如果我们想改变其优先级,让优先级变成小的优先,我们要怎么做呢?
	priority_queue<int, vector<int>, greater<int> > pq; // greater<int> 让优先级变成小的优先了
	pq.push(1);
	pq.push(8);
	pq.push(5);
	pq.push(2);

	while (!pq.empty())
	{
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;
}

这个代码会更加直观:

void TestPriorityQueue()
{
 	// 默认情况下,创建的是大堆,其底层按照小于号比较
 	vector<int> v{3,2,7,6,0,4,1,9,8,5};
 	priority_queue<int> q1;
 	for (auto& e : v)
 		q1.push(e);
 	cout << q1.top() << endl;
 	// 如果要创建小堆,将第三个模板参数换成greater比较方式
 	priority_queue<int, vector<int>, greater<int>> q2(v.begin(), 	 v.end());
 	cout << q2.top() << endl;
}

3.3oj题目应用

数组中的第K个最大元素

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k)
    {
        // 优先级队列解决
        // 时间复杂度是 O(N * logN)
        // 空间复杂度是 O(N)
        /*
        priority_queue<int> pq(nums.begin(), nums.end());

        for(int i = 0; i < k - 1; ++i)
        {
            pq.pop();
        }

        return pq.top();
        */

        /*
        // 快排 【sort底层就是快排】
        // 时间复杂度是 O(N * logN)
        // 空间复杂度是 O(1)
        sort(nums.begin(), nums.end());

        return nums[nums.size() - k];
        */

        // TopK问题解法 【找大建小堆,找小建大堆】【适合解决海量数据的问题】
        //时间复杂度是 O(N * logk)
        // 空间复杂度 O(1)
        priority_queue<int, vector<int>, greater<int>> smallhead;
        // 先往小堆里面放k个元素,保持k个元素的小堆存在、
        for (int i = 0; i < k; i++)
        {
            smallhead.push(nums[i]);
        }
        // 让剩下的数据去和堆顶做比较,只要比堆顶大,就入堆
        for (int i = k; i < nums.size(); i++)
        {
            if (nums[i] > smallhead.top())
            {
                smallhead.pop(); // 把堆顶弹出
                smallhead.push(nums[i]);
            }
        }

        // 此时的小堆,存放着k个最大元素,堆顶的元素恰好是第k大的数。
        return smallhead.top();

    }
};

TOPK解法使用于数据量很大的时候,当N趋向很大的时候,logk可以忽略不计,时间复杂度就是O(N)。

3.4priority_queue的模拟实现

优先级队列priority_queue的模拟实现。

优先级队列的底层数据结构其实就是一个堆(大堆小堆都有可能,根据需求进行选择)

priority_queue的模拟实现的代码

模拟实现过程中有几个要注意的点:

  1. 首先要熟悉堆的性质和结构

有点遗忘的话要及时复习二叉树专题

  1. 其次要熟悉堆向上调整算法堆向下调整算法
		// 堆向上调整算法
		void AdjustUp(int child)
		{
			// 从孩子的位置向上调整。
			while (child > 0)
			{
				int parent = (child - 1) / 2;
				// 判断此时父亲是否小于孩子(大堆),大于孩子(小堆)
				if (compare()(_con[parent], _con[child])) // // compare()的意思是调用默认构造函数,创建一个compare结构体的对象
				{
					swap(_con[parent], _con[child]);
					child = parent;
					parent = (child - 1) / 2;
				 }
				else
				{
					// 走到这里就说明虽然没有向上调整到底,但是此时的结构已经符合堆的结构了。不在需要调整了
					return; // break也可以
				}
			}
		
		}

		// 堆向下调整算法
		void AdjustDown(int parent)
		{
			// 这个插入的时候要符合堆的性质(优先级队列默认是大堆)
			int child = parent * 2 + 1; //默认最大的孩子是左孩子(大堆),默认最小的孩子是左孩子(小堆)

			while (child < _con.size())
			{
				// 选出左右孩子更大的那个(大堆),要注意右孩子是否越界
				if (child + 1 < _con.size() && compare()(_con[child], _con[child + 1]))
					child += 1;

				// 跟父亲节点去比较
				if (compare()(_con[parent], _con[child])) // compare()的意思是调用默认构造函数,创建一个compare结构体的对象
				{
					swap(_con[parent], _con[child]);

					// 更新父节点和孩子节点的位置
					parent = child;
					child = 2 * parent + 1;
				}
				else
				{
					// 走到这里说明符合堆的结构,不在需要向下调整了。
					return; // break也可以
				}

			}
		}
  1. 优先级队列插入数据之前,已经是个堆了,插入数据之后对新插入的数据用堆向上调整算法
void push(const T& x)
{
	_con.push_back(x);
	// 插入x这个数据之前,原本就是一个堆,因此用堆向上调整算法
	AdjustUp(size() - 1);
}
  1. 优先级队列pop的时候要注意不能直接头删,如果底层容器是顺序表的话,直接头删的效率就低了,可以先让其头尾交换,再尾删,再对交换上来的头用堆向下调整算法
	void pop() 
	{
		// 容器已经空了,就不能再删除了
		assert(!_con.empty());

		// 如果底层容器是顺序表的话,直接头删的效率就低了
		// 可以先让其头尾交换,再尾删,再对头用堆向下调整算法
		//swap(_con[0], _con[size() - 1]);
		swap(_con.front(), _con.back()); // 和上面那句等价
		_con.pop_back();
		AdjustDown(0);
	}
  1. 要理解仿函数的出现(用起来和函数一样,实际上并不是函数)。
// 仿函数 (用起来和函数一样,实际上并不是函数)
template<class T>
struct less
{
	bool operator() (const T& left, const T& right)
	{
		return left < right;
	}
};

// 仿函数
template<class T>
struct greater
{
	bool operator() (const T& left, const T& right)
	{
		return left > right;
	}
};

仿函数其实就是结构体或者类里面重载了一个(),导致用起来和函数一样。

image-20240902004245685

具体思路如下图所示:

image-20240902175458084

4.容器适配器

4.1什么是适配器

适配器是一种设计模式该种模式是将一个类的接口转换成客户希望的另外一个接口

4.2STL库中的stack和queue

前面我们模拟实现的stack和queue,都存在说要传一个容器作为模版参数的情况,但是实际上我们使用库里的stack和queue,并不存在说要传一个容器才能运行的现象,我们只需要传这个T,即数据的类型。

这是因为——库里的模版参数有缺省值

image-20240827161051745

image-20240827161100769

image-20240827162933149

我们发现缺省值有deque<T>,我们就要学习deque<T>是什么

4.3deque的介绍

deque是一个双端队列,他可以在队头和队尾都进行插入和删除的操作,并且时间复杂度都是O(1),并且它还能支持随机访问。

vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高

image-20240827234858830

deque的使用代码:

void test_deque1()
{
	//deque是双端队列,队列两段都可以进行插入和删除的操作。并且时间复杂度都是O(1),并且支持随机访问。
	deque<int> d;
	d.push_back(1);
	d.push_back(2);
	d.push_back(3);
	d.push_back(5);
	d.push_front(0);

	for (int i = 0; i < d.size(); ++i)
	{
		cout << d[i] << " ";
	}
	cout << endl;

}

deque看似结合了vector和list的优点,但是并不意味着它就是完美的

4.4deque的缺陷

deque的效率其实并没有那么理想,因为deque的随机访问其实是虚假的,是通过迭代器去实现的。这个可以通过了解deque的原理去理解。

我们可以来看一段代码来看vector和deque之间的随机访问的效率差距

在这段代码中,我们会让deque和vector的对象分别插入10w个数据,并且将其排序。我们来看看他们分别用了多长的时间。

void test_deque2()
{
	// deque看似非常完美将vector和list的优点结合到了一起,但是deque的缺点就是效率不够优秀
	// 下面我们就让deque和vector两个容器进行排序,看看效率的差距
	vector<int> v;
	deque<int> d;
	const int n = 100000;
	srand(time(0)); // 给个种子才是随机值

	for (int i = 0; i < n; ++i)
	{
		// 给v和d两个容器插入同样的10w个随机值
		int x = rand();
		v.push_back(x);
		d.push_back(x);
	}
	
	// 对两个容器的10w个数据进行排序
	size_t begin1 = clock();
	sort(v.begin(), v.end());
	size_t end1 = clock();

	size_t begin2 = clock();
	sort(d.begin(), d.end());
	size_t end2 = clock();

	cout << end1 - begin1 << endl; // 25
	cout << end2 - begin2 << endl; // 111
	// 可以看到deque和vector的效率大概 差了4倍之多
}

我们可以看到deque的随机访问效率比起vector低了不少。

4.5为什么栈和队列用deque作为默认的底层容器

为什么库里的stack和queue都用的是deque这个容器来作为底层呢

因为——stack和queue都存在头或者尾的插入和删除数据,而deque的双端插入删除数据的效率是O(1),并且stack和queue是容器适配器,不存在随机访问的使用情况,这样deque的缺陷就被避开了。因此deque可以完美适配stack和queue。

4.6deque的原理

我们说deque不仅支持两端的插入和删除数据,时间复杂度是O(1),并且他还支持随机访问。

这是否意味着deque和vector一样是类似数组一样的连续的空间呢?但是如果是连续的空间,挪动和删除数据必然需要耗费较大的代价

因此deque并不是真正的连续的空间,是由一段段连续的小空间组成的。

实际deque类似于一个动态的二维数组

deque是通过一个个数组buffer来存储数据的,每个数组buffer都可以存储着数据,这样deque就不会有增容的操作,因为可以直接多开一个buffer数组来存储数据

image-20240828162325322

如果是头插,也是往上面多开一个数组,并且从尾部开始插入数据。每个数组存储的元素数量是固定的,不够了直接多开一个数组。

但是这样似乎和list<vector>有点相似,但是deque还可以支持随机访问,list<vector>并不行,那deque是如何做到的呢?

答案是——中控映射

deque有一个中控数组,这个数组是一个指针数组,用来管理每个小数组之间的关系,里面存放着每个buffer数组的地址。

这样我们通过这个指针数组,就可以知道每个buffer之间的关系了。

image-20240828162718613

要注意。这个中控数组是从数组的中间位置开始记载第一个buffer数组的地址的,因为头插开辟的数组是在第一个buffer的前面的,这样每个小数组之间的关系才是正确的。

但是这样就会造成一个问题,随着buffer数组数量的增多。中控数组的容量会不足,这个时候中控数组就会进行增容操作,因此deque并不是不进行增容操作,还是有增容操作的,但是只对一个指针数组进行增容操作的代价要比对buffer数组进行增容的代价要小的多

**那operator[i]是如何实现的呢?**既然我们有了中控数组的存在,就可以计算第i个数据在那个buffer数组中,因此当数据量变得很大的时候,效率就会变得很低。

deque的底层结构如下图所示:

image-20240828163146013

**deque底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,这个任务落在了deque的迭代器身上,**因此deque的迭代器设计就比较复杂,如下图所示:

image-20240828165101388

cur是真正遍历的指针,first指向当前buffer数组的头,last指向当前buffer数组的最后一个数据的下一个位置,node指向当前buffer的地址,即中控数组的某一个元素的地址。

下面的图可以很好解释deque的迭代器是如何工作的。

一共有三个buffer数组存储数据。

image-20240828165249809

4.7对STL中的stack和queue的模拟实现

STL中的stack和queue和我们之前的模拟实现的stack和queue的最大区别就是——

传容器的模版参数是否是deque<T>作为缺省值

4.7.1stack
#pragma once
# include<iostream>
# include<deque>
using namespace std;

namespace wzf
{
	template<class T, class container = deque<T>>
	class stack
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}

		void pop()
		{
			_con.pop_back();
		}

		T& top()
		{
			return _con.back();
		}

		bool empty()
		{
			return _con.empty();
		}

		size_t size()
		{
			return _con.size();
		}

	private:
		container _con;
	};

测试代码:

	void test_stack1()
	{
		stack<int> st;
        // stack<int, vector<int>> st;
        // stack<int, list<int>> st;
		st.push(1);
		st.push(2);
		st.push(3);
		st.push(4);

		while (!st.empty())
		{
			cout << st.top() << " ";
			st.pop();
		}
		cout << endl;
	}

4.7.2queue
namespace wzf
{
	template<class T, class container = deque<T>>
	class queue
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}

		void pop()
		{
			_con.pop_front();
		}

		bool empty()
		{
			return _con.empty();
		}

		size_t size()
		{
			return _con.size();
		}

		T& front()
		{
			return _con.front();
		}

		T& back()
		{
			return _con.back();
		}

	private:
		container _con;
	};
}

测试代码:

void test_queue()
{
	queue<int> q;
    queue<int, list<int>> q;
	q.push(1);
	q.push(2);
	q.push(3);
	q.push(4);

	while (!q.empty())
	{
		cout << q.front() << " ";
		q.pop();
	}
	cout << endl;
}

5.总结

学到这里,目前STL中的6大组件,我已经学了5个了。

  • **容器:**string/vector/list/deque…
  • 适配器: stack/queue/priority_queue…
  • **迭代器:**iterator/const_iterator || reverse_iterator/const_reverse_iterator
  • **算法:**find/sort…
  • **仿函数:**less/greater…

还剩下空间配置器等着后面学。如果对着前面五个板块的学习内容有些许遗忘,可以看之前的博客进行复习

并且随着对c++的深入学习。还要学习一些更加复杂的序列式容器:如红黑树,哈希表等等。

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

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

相关文章

AI人数智能统计监测摄像头

随着人工智能技术的不断发展&#xff0c;AI人数智能统计监测摄像头 在各个领域得到了广泛应用。这种摄像头结合了图像识别技术和智能算法&#xff0c;旨在实现对人群数量的准确统计和监测。通过高清晰度的摄像头捕捉到场景中的人群图像&#xff0c;并通过人工智能技术进行快速准…

手机删除的短信怎么恢复?学会这4招,短信恢复不是梦!

手机短信在今天作为我们沟通的重要桥梁&#xff0c;承载着无数珍贵的瞬间与关键信息。然而&#xff0c;不小心误删或者系统的故障&#xff0c;可能会把重要短信删除了。手机删除的短信怎么恢复&#xff1f;您是否还在焦虑又无助地寻找答案&#xff1f; 看这里&#xff01;其实…

电路分析 ---- 反相比例器

1 基本反向比例器 分析过程 根据虚断可知经过运算放大器正负输入端的电流为0&#xff0c;即 i P i N 0 i_{P}i_{N}0 iP​iN​0故有 u P 0 u_{P}0 uP​0&#xff0c;根据虚短可知 u P u N 0 u_{P}u_{N}0 uP​uN​0 i R u I − u N R u I R i_{R}\cfrac{u_{I}-u_{N}}{R}\…

【C语言必学知识点六】自定义类型——内存对齐与位段

内存对齐与位段 导读一、内存对齐1.1 对齐规则1.1.1 内存对齐中的名词1.1.2 内存对齐规则的理解1.1.3 宏offsetof 1.2 内存对齐存在的原因1.3 修改默认对齐数 二、位段2.1 什么是位段2.1.1 个人理解 2.2 位段的内存分配2.2.1 VS中的位段内存分配2.2.2 VS位段分配方式的验证2.2.…

评价决策类——层次分析法+数学建模+实战分析

目录 一、前言 二、历年题型分析 2.1 常用算法归纳 2.1.1 优化类算法 2.1.2 预测类算法 2.1.3 评价决策类 2.1.4 NP-hard类 2.2 评价类模型求解 2.2.1 层次分析法&#xff08;AHP&#xff09; 2.2.2 多指标评价法&#xff08;MCDA&#xff09; 2.2.3 算法区别 三、层…

浅谈人工智能之基于AutoGen Studio+语聚AI API构建智能体技能

浅谈人工智能之基于AutoGen Studio语聚AI API构建智能体技能 使用AutoGen Studio与语聚AI API&#xff1a;高效自动化代码生成与语言处理的融合实践 概述 在快速迭代的软件开发环境中&#xff0c;高效、准确的代码生成和语言处理能力成为了提升开发效率的关键因素。本文档旨…

SuperMap GIS基础产品FAQ集锦(20240902)

一、SuperMap iDesktopX 问题1&#xff1a;请问一下这个重建指数&#xff0c;怎么理解呢&#xff1f; 11.1.1 【解决办法】重建指数是用于设置根节点合并次数&#xff0c;系统会根据数据自动计算一个数值n&#xff0c;即模型根节点将进行n次合并处理。 问题2&#xff1a;11…

Datawhale X 李宏毅苹果书AI夏令营深度学习详解入门Task02

本文了解深度学习详解中的线性模型 本文了解深度学习详解中的线性模型将围绕梯度下降优化、线性模型的局限性、改进模型以及深度学习模型等关键要点展开讨论。 一、梯度下降优化 梯度下降是深度学习中常用的优化算法&#xff0c;它通过不断调整模型的参数&#xff0c;使得损失函…

【优质源码】3D多人在线游戏,前端ThreeJS,后端NodeJS

3D多人在线游戏 【源码】3D多人在线游戏源码&#xff0c;前端ThreeJS&#xff0c;后端NodeJS&#xff0c;完整源码。 游戏画面 启动方法 先启动服务器端。 在目录&#xff0c;3D-multi-player-main\3D-multi-player-main\nodeapps\blockland 中&#xff0c;运行&#xff1a…

讯飞星火版「Her」正式上线!成立仅16月的无问芯穹完成近5亿元A轮融资|AI日报

文章推荐 8款国内外免费AI生成视频工具对比实测&#xff01;我们真的可以做到“一人搞定一部影视作品“吗&#xff1f; AI真“卷出天际”&#xff01;我国发布全球首个月球专业大模型&#xff1b;0代码可做游戏&#xff0c;谷歌发布世界首个AI游戏引擎&#xff5c;AI日报 今…

基于树莓派的儿童音频播发器—Yoto

Raspberry Pi 的开发可能性使吸引人的、以儿童为中心的音频播放器得以成型 Yoto Player 为孩子们提供了拥有和控制的绝佳体验&#xff0c;同时不会增加屏幕时间。得益于 Raspberry Pi 以及我们认可的经销商提供的支持和专业知识&#xff0c;Yoto Player 在英国取得了成功。 Yo…

echart vue3 柱状图 自定义柱子颜色和文字颜色

目录 需求&#xff1a; 效果&#xff1a; ​编辑数据格式&#xff1a;series 需求&#xff1a; 自定义echart柱状图的柱子颜色 并且每根柱子上数字的颜色要跟柱状图的颜色保持一致 效果&#xff1a; 数据格式&#xff1a;series [{"name": "预算",&…

WSL 在 Windows 上删除已经安装的 Ubuntu | WSL 再次重装 Ubuntu | cv2.imshow() 弹窗支持

本博文主要参考官网&#xff1a;https://learn.microsoft.com/zh-cn/windows/wsl/install 记录解决 WSL 创建和删除 Ubuntu 子系统的一些细微问题的 解决方案 &#x1f947; 版权: 本文由【墨理学AI】原创首发、各位读者大大、敬请查阅、感谢三连 &#x1f389; 声明: 作为全网…

【AI大模型】近100页的LLaMA 3技术报告:模型结构及影响解析

LLama 3 405B模型效果已经赶上目前最好的闭源模型比如GPT 4o和Claude 3.5&#xff0c;这算是开源届的大事&#xff0c;技术报告接近100页&#xff0c;信息很丰富&#xff0c;粗略看了一下&#xff0c;很有启发。这里就LLaMA 3的模型结构、训练过程做些解读&#xff0c;并对其影…

9行代码开发一个基于ollama的私有化RAG

前言 OpenAI&#xff08;LLM Embedding&#xff09;是使用LiteLLM ollama模拟&#xff0c;具体做法如下&#xff0c; Llamaindex OpenAI LLM 模型默认使用的是gpt-3.5-turbo&#xff0c; embedding 模型默认使用的是text-embedding-ada-002&#xff0c; 所以这里使用litell…

数据结构详解---顺序表

&#x1f30f;个人博客主页&#xff1a;意疏-CSDN博客 希望文章能够给到初学的你一些启发&#xff5e; 如果觉得文章对你有帮助的话&#xff0c;点赞 关注 收藏支持一下笔者吧&#xff5e; 阅读指南&#xff1a; 开篇说明线性表的定义线性表的顺序存储结构&#xff08;顺序表…

ozon本土店和跨境店什么区别

Ozon 本土店和跨境店有以下区别&#xff1a; 运营模式&#xff1a;本土店&#xff1a;主要针对俄罗斯国内买家&#xff0c;商品来源于俄罗斯国内供应商。跨境店&#xff1a;针对俄罗斯的海外买家&#xff0c;商品主要来源于海外供应商。物流管理&#xff1a;本土店&#xff1a…

unity GridLayoutGroup真正的居中

GridLayoutGroup默认的居中效果: 不是真正的居中 加上代码: namespace UnityEngine.UI {/// <summary>/// GridLayoutGroup拓展&#xff0c;使支持自定义内容/// </summary>internal class GridLayoutGroupEx : GridLayoutGroup{/// <summary>/// 启用居中/…

将语义分割的标签转换为实例分割(yolo)的标签

语义分割的标签&#xff08;目标处为255&#xff0c;其余处为0&#xff09; 实例分割的标签&#xff08;yolo.txt&#xff09;,描述边界的多边形顶点的归一化位置 绘制在原图类似蓝色的边框所示。 废话不多说&#xff0c;直接贴代码&#xff1b; import os import cv2 imp…

【高阶数据结构】二叉树的创建、存储方式(顺序与链式)、遍历方法(递归与非递归)(精美图解+完整代码)

&#x1f921;博客主页&#xff1a;醉竺 &#x1f970;本文专栏&#xff1a;《高阶数据结构》 &#x1f63b;欢迎关注&#xff1a;感谢大家的点赞评论关注&#xff0c;祝您学有所成&#xff01; ✨✨&#x1f49c;&#x1f49b;想要学习更多《高阶数据结构》点击专栏链接查看&a…