【C++】priority_queue使用模拟实现

news2024/11/16 7:44:36

priority_queue使用

http://www.cplusplus.com/reference/queue/priority_queue/ 文档介绍

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

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

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

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

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

priority_queue介绍

优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue,

注意:默认情况下priority_queue是大堆,其使用的仿函数是less


定义方式

image-20220221215043963

第一个参数:类型 第二个参数:容器适配器,默认是vector 第三个参数:比较器(仿函数) 默认是less 对应是大根堆结构

如果想要实现成小根堆结构 ->需要自己传入仿函数greater->需要引用头文件functional


方式1:容器适配器和仿函数都使用默认值

priority_queue<int> q; //默认是vector作为容器适配器,大根堆结构
//相当于:priority_queue<int, vector<int>, ::less<int>> q; 要指定在哪个域中找仿函数,否则报错

方式2:自己给容器适配器和仿函数

priority_queue<int, vector<int>, ::greater<int>> q2; //是vector作为容器适配器,小根堆结构

注意:默认容器适配器是vector,默认的仿函数是less 对应大根堆结构


可以使用vector的容器的迭代器区间构造优先级队列:

int main()
{
	int a[] = { 3,7,6,5,4 };
	int sz = sizeof(a) / sizeof(a[0]);
	vector<int> v(a, a + sz);//用数组的一段区间初始化vector容器,指针是原生迭代器
	priority_queue<int> q(v.begin(), v.end());//使用迭代器区间初始化优先级队列
	while (!q.empty())
	{ 
		cout << q.top() << endl; // 7 6 5 4 3  大根堆
		q.pop();
	}
	cout << endl;
	return 0;
}

函数接口

成员函数功能
push在优先级队列中插入元素x(保持堆的结构)
pop删除优先级队列中最大(最小)元素,即堆顶元素
top返回优先级队列中最大(最小元素),即堆顶元素
size获取队列中有效元素个数
empty检测优先级队列是否为空,是返回true,否则返回false
swap交换两个优先级队列的内容
priority_queue()构造一个空的优先级队列

注意:

默认情况下priority_queue是大堆,


#include<functional>
int main()
{
	priority_queue<int> q; //默认是vector作为容器适配器,大根堆结构
	q.push(3);
	q.push(2);
	q.push(7);
	q.push(0);
	while (!q.empty())
	{ 
		cout << q.top() << endl; // 7 3 2 0   大根堆
		q.pop();
	}
	cout << endl;
	return 0;
}

如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供> 或者< 的比较方式的重载,

class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
        : _year(year)
            , _month(month)
            , _day(day)
        {}
    bool operator<(const Date& d)	const
    {
        return (_year < d._year) ||
            (_year == d._year && _month < d._month) ||
            (_year == d._year && _month == d._month && _day < d._day);
    }
    bool operator>(const Date& d)	const
    {
        return (_year > d._year) ||
            (_year == d._year && _month > d._month) ||
            (_year == d._year && _month == d._month && _day > d._day);
    }
    //重载<<运算符用于打印, 友元函数
    friend ostream& operator<<(ostream& _cout, const Date& d)
    {
        _cout << d._year << "-" << d._month << "-" << d._day;
        return _cout;
    }
private:
    int _year;
    int _month;
    int _day;
};
void TestPriorityQueue()
{
    // 大堆,需要用户在自定义类型中提供<的重载
    priority_queue<Date> q1;
    q1.push(Date(2018, 10, 29));
    q1.push(Date(2018, 10, 28));
    q1.push(Date(2018, 10, 30));
    cout << q1.top() << endl;
    // 如果要创建小堆,需要用户提供>的重载
    priority_queue<Date, vector<Date>, greater<Date>> q2;
    q2.push(Date(2018, 10, 29));
    q2.push(Date(2018, 10, 28));
    q2.push(Date(2018, 10, 30));
    cout << q2.top() << endl;
}

当然了,如果自定义类型的类里面不提供的话,我们可以手动实现一个仿函数进行比较

如果要让仿函数类LessPDate能访问Date的私有成员,此时需要定义LessPDate类为Date的友元类

class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
        : _year(year)
        , _month(month)
        , _day(day)
    {}
    //重载<<运算符用于打印, 友元函数
    friend ostream& operator<<(ostream& _cout, const Date& d)
    {
        _cout << d._year << "-" << d._month << "-" << d._day;
        _cout << endl;
        return _cout;
    }
    friend struct LessPDate;//定义LessPDate为Date的友元类,让LessPDate也能访问Date的私有成员
private:
    int _year;
    int _month;
    int _day;
};

struct LessPDate
{
    bool operator()(const Date* d1, const Date* d2) const
    {
        return (d1->_year < d2->_year) ||
            (d1->_year == d2->_year && d1->_month < d2->_month) ||
            (d1->_year == d2->_year && d1->_month == d2->_month && d1->_day < d2->_day);
    }
};
void TestPriorityQueue()
{
    //	priority_queue<Date*> pq; //此时默认是比较地址的大小
    //如果需要比较日期的大小,需要自己实现仿函数
	priority_queue<Date*, vector<Date*>, LessPDate> pq;

	pq.push(new Date(2022, 3, 26));
	pq.push(new Date(2021, 10, 26));
	pq.push(new Date(2023, 3, 26));
    while (!pq.empty())
    {
        cout << *pq.top();
        pq.pop();
    }
    cout << endl;
}
int main()
{
    TestPriorityQueue();
    return 0;
}

OJ练习题:

数组中第k大的元素

215. 数组中的第K个最大元素 - 力扣(LeetCode) (leetcode-cn.com)

image-20220221221012294


方法1:使用优先级队列->大根堆, 用数组nums迭代器区间构造优先级队列,然后弹出堆中的前k-1个元素, 此时堆顶的元素就是第k大的元素

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        //默认的仿函数就是less->大根堆
        priority_queue<int> q(nums.begin(),nums.end());//用vector的迭代器区间初始化q
        for(int i = 0;i<k-1;i++)
        {
            q.pop();//弹出前k-1个元素
        }
        return q.top();//返回此时堆顶元素
    }
};

时间复杂度:O(N+K*logN) 建N个数的堆 + k个数调堆 空间复杂度:O(N)


方法2:直接排序 sort默认排的是升序, sort的底层是快排 ->时间复杂度:O(N*logN)

升序:下标为 size() - k 的元素就是第k大的元素

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        sort(nums.begin(),nums.end());//升序
        return nums[nums.size()-k];
    }
};

降序:下标为:k-1就是第k大的元素

传反向迭代器进行排序->就是降序

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        sort(nums.rbegin(),nums.rend());//降序
        return nums[k-1];
    }
};

仿函数传greater就是降序 需要注意的是:排序函数sort的第三个参数:这里传的是匿名对象! greater<int>()

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        sort(nums.begin(),nums.end(),greater<int>());//升序
        return nums[k-1];
    }
};

priority_queue<int, vector<int>, ::greater<int>> q2; ->此时传的是类型,因为它是类模板的参数

而优先级队列的第三个参数需要传类型,为它是类模板的参数


最优解 :方法3:先建一个k个数的**小堆**

后面的元素一次和堆顶元素比较,如果当前元素>堆顶元素,就弹出堆顶元素,然后当前数进堆, 整个数组处理完后,堆顶的元素就是第k大的元素

注意:这里的greater传的是模板参数, 传的是类型!

class Solution {	
public:
    int findKthLargest(vector<int>& nums, int k) {
    //建一个k个数的小堆->仿函数是greater
    //把vector的前k个数放到堆中
    priority_queue<int,vector<int>,greater<int>> q(nums.begin(),nums.begin()+k);
    //vecotr后面的数依次和堆顶元素比较
    for(size_t i = k;i<nums.size();i++)
    {
        //如果当前数> 堆顶,就替换它,进堆
        if(nums[i] > q.top())
        {
            q.pop();
            q.push(nums[i]);
        }
    }
        return q.top();//此时堆顶的元素就是第k大的
    }
};

时间复杂度: O(K+(N-K)logK) 空间复杂度:O(K)

  • 建k个数的小堆 O(K) + 剩余的N-K个数调堆O(logK *(N-K)), 调一次堆时间复杂度:logK (K为堆的结点个数)

关于仿函数

仿函数比起一般函数具有很多优点:

  • 仿函数是模板函数,可以根据不同的类型代表不同的状态
    • 在同一时间里,由某个仿函数所代表的单一函数,可能有不同的状态
  • 仿函数是模板函数,可以有不同类型
    • 仿函数即使定义相同,也可能有不同的类型
  • 仿函数在一定程度上使代码更通用,本质上简化了代码
    • 仿函数使程序代码变简单

缺点:仿函数是模板函数,其速度比一般函数要慢

image-20220222205604911

在优先级队列中,默认使用less作为仿函数 -> 大堆结构 greater->小堆结构


用一个类重载operator () ,让这个类实例化的对象可以像函数一样使用

例子:

#include<iostream>
using namespace std;
//仿函数  函数对象 这个类的对象可以像函数一样去使用
struct LessInt
{
	//比较两个int,重载operator()运算符
	bool operator()(int l, int r)
	{
		return l < r;
	}
};

//普通函数
bool LessFun(int l, int r)
{
	return l < r;
}
int main()
{
	cout << LessFun(1, 2) << endl;
	LessInt f1;//使用类创建出的对象
	cout << f1(2, 3) << endl;
	//会转化成下面的调用:f1(2, 3) ==>f1.operator()(2,3)
	//cout<<f1.operator()(2,3)<<endl;
}

为什么要有仿函数 -> 因为函数指针用起来不好用 bool(*f1)(int,int)


仿函数要比较任意类型:->写成模板 写成struct更好,因为struct的默认权限是公有public的

#include<iostream>
using namespace std;
template<class T>
struct Less
{
	bool operator()(const T& l, const T& r)
	{
		return l < r;
	}
};
int main()
{
	Less<double> less; //给一个模板参数
	cout << less(3.3, 4.4) << endl;
}

priority_queue的模拟实现

priority_queue的底层结构就是堆,因此只需对其进行通用的封装即可,

堆:物理上是数组,逻辑上:完全二叉树

image-20220221215043963

需要三个模板参数: 参数类型T 默认容器适配器;vector<T> 仿函数:默认是less<T>


需要实现的接口:


成员函数实现方法
push在容器尾部插入元素后,从最后一个位置进行向上调整
pop先将容器堆顶元素和最后一个位置的元素交换,再将最后一个元素删除,最后从根结点0位置进行向下调整
top返回堆顶元素 -> 即返回容器下标为0的位置
size返回堆中元素个数 -> 即容器的元素个数size()
empty判断堆是否为空->即判断容器是否为空 empty()

image-20220223135504113

成员变量

//三个模板参数:参数类型T   默认容器适配器;vector<T> 仿函数:默认是less<T>  大堆
template<class T, class Container = vector<T>, class Compare = less<T>>
private:
	Container _con;//容器适配器
	Compare   _cmp;//仿函数

建大堆还是建小堆,本质就是向上调整算法和向下调整算法的比较符号不相同,我们可以通过仿函数来控制

image-20220702214735682

向上调整算法

以大堆为例子

循环结束条件:两个满足其中一个即跳出循环

1.插入的结点的值比从它到根结点的所有结点的值都要,则要一直向上调整,最坏情况下调整到根结点(堆顶0位置)

2.插入的结点 比 从它到根结点的部分结点的值大,当调整到某一个位置,孩子结点的值比父亲结点的值小,就不用调整了,走else跳出循环

//向上调整算法
void AdjustUp(size_t child)
{
    size_t parent = (child - 1) / 2;//算出父节点所在位置
    //从孩子位置一直往上调
    //结束条件:调整到根节点 / 不满足if条件
    while (child > 0)
    {
        // if(_con[parent]>_con[child])
        if (_cmp(_con[parent], _con[child]))
        {
            swap(_con[parent], _con[child]);
            //继续下一轮向上调整
            child = parent;
            parent = (child - 1) / 2;
        }
        else
        {
            break;
        }
    }
}

向下调整算法

以大堆为例子, 向下调整的时候, 没有固定的路径可循,和左孩子交换还是和右孩子交换?

由于是大堆, 向下调整,所以和左右孩子中较大的孩子交换

循环结束条件

  • 1.最坏情况下:要调整到叶子结点位置(度为0的结点)
  • 2.调整到某一个节点的时候, 该结点的值比其孩子中值最大的节点的值大

image-20220223135156038

//向下调整算法
void AdjustDown(size_t parent)
{
    //从父节点位置一直往上调
    size_t child = parent * 2 + 1;//默认指向左孩子
    //结束条件:调整到叶子结点 / 不满足if条件
    while (child < _con.size())
    {
        //大堆:选出较大的孩子,要防止没有右孩子导致越界
        if (child + 1 < _con.size() && _cmp(_con[child], _con[child + 1]))
        {
            ++child;//指向右孩子
        }
        //较大的孩子和父节点比较
        //if(_con[parent] > _con[child])
        if (_cmp(_con[parent], _con[child]))
        {
            swap(_con[parent], _con[child]);
            //继续下一轮向下调整
            parent = child;//父亲来到孩子位置
            child = parent * 2 + 1;//算出此时父亲的孩子的位置
        }
        else
        {
            break;
        }
    }
}

push

先插入一个数据到容器的尾上,再进行向上调整算法,直到满足堆结构

image-20220223135231558

void push(const T& x)
{
    _con.push_back(x);//将数据尾插到容器的尾部
    AdjustUp(_con.size() - 1);//在插入位置进行向上调整算法
}

pop

将堆顶的数据跟最后一个数据交换,然后删除容器的最后一个数据(即:删除掉了原来的堆顶元素),再从根节点位置进行向下调整算法

image-20220223135245961

void pop()
{
    swap(_con[0], _con[_con.size() - 1]);//将容器下标为0的元素和容器下标为size()-1的元素进行交换
    _con.pop_back();//然后弹出最后一个元素
    AdjustDown(0);//然后从0位置开始向下调整
}

size

获取堆中的元素个数

size_t size()
{
    return _con.size();//返回容器中的元素个数
}

top

返回堆顶元素 ,注意:不能返回引用,否则修改之后可能就不符合堆的结构了

T top()
{
    return _con[0];//返回容器的第一个元素
}

empty

判断堆是否为空

bool empty()
{
    return _con.empty();//返回容器是否为空
}

priority_queue.h代码:

#include<iostream>
using namespace std;
#include<vector>

namespace Mango
{
//仿函数
template<class T>
struct less
{
	bool operator()(const T& l, const T& r)
	{
		return l < r;
	}
};

template<class T>
struct greater
{
	bool operator()(const T& l, const T& r)
	{
		return l > r;
	}
};

//三个模板参数:参数类型T   默认容器适配器;vector<T> 仿函数:默认是less<T>  默认是大堆
template<class T, class Container = vector<T>, class Compare = less<T>>
class priority_queue
{
public:
	//向上调整算法
	void AdjustUp(size_t child)
	{
		size_t parent = (child - 1) / 2;//算出父节点所在位置
		//从孩子位置一直往上调
		//结束条件:调整到根节点 / 不满足if条件
		while (child > 0)
		{
			// if(_con[parent]>_con[child])
			if (_cmp(_con[parent], _con[child]))
			{
				swap(_con[parent], _con[child]);
				//继续下一轮向上调整
				child = parent;
				parent = (child - 1) / 2;
			}
			else
			{
				break;
			}
		}
	}
	//向下调整算法
	void AdjustDown(size_t parent)
	{
		//从父节点位置一直往上调
		size_t child = parent * 2 + 1;//默认指向左孩子
		//结束条件:调整到叶子结点 / 不满足if条件
		while (child < _con.size())
		{
			//选出较大的孩子,要防止没有右孩子导致越界
			if (child + 1 < _con.size() && _cmp(_con[child], _con[child + 1]))
			{
				++child;//指向右孩子
			}
			//和父节点比较
			//if(_con[parent] > _con[child])
			if (_cmp(_con[parent], _con[child]))
			{
				swap(_con[parent], _con[child]);
				//继续下一轮向下调整
				parent = child;//父亲来到孩子位置
				child = parent * 2 + 1;//算出此时父亲的孩子的位置
			}
			else
			{
				break;
			}
		}
	}
	void push(const T& x)
	{
		_con.push_back(x);
		AdjustUp(_con.size() - 1);//从最后一个元素位置向上调整
	}
	void pop()
	{
		swap(_con[0], _con[_con.size() - 1]);
		_con.pop_back();
		AdjustDown(0);//从根节点向下调整
	}

	T top()	//注意:此处不能返回引用
	{
		return _con[0];
	}
	size_t size()
	{
		return _con.size();
	}
	bool empty()
	{
		return _con.empty();
	}
private:
	Container _con;//容器适配器
	Compare   _cmp;//仿函数
};
}

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

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

相关文章

应用系统基于OAuth2实现单点登录的解决方案

1、OAuth2单点认证原理 基于OAuth2的认证方式包含四种&#xff0c;其中单点登录最常用的是授权码模式&#xff0c;其基本的认证过程如下&#xff1a; 用户访问业务应用&#xff0c;业务应用进行登录检查&#xff1b;业务应用重定向到OAuth2认证服务器&#xff0c;调用获取授权…

米哈伊年终奖是32万,我的年终奖是彩虹屁!

数据来源沉默王二 | 数据报表小熊绘制 年都过完了&#xff0c;年终奖结果也都出来了&#xff0c;我这个年没有过好&#xff0c;每次想到就难受&#xff0c;在看王二整理出来的年终奖&#xff0c;整个人都不好了。 本次统计基于49条数据的不准确统计&#xff0c;仅抽取部分公司部…

Lesson 4.4 随机梯度下降与小批量梯度下降

文章目录一、损失函数理论基础二、随机梯度下降&#xff08;Stochastic Gradient Descent&#xff09;1. 随机梯度下降计算流程2. 随机梯度下降的算法特性3. 随机梯度下降求解线性回归4. 随机梯度下降算法评价三、小批量梯度下降&#xff08;Mini-batch Gradient Descent&#…

SpringMVC执行流程和原理

1、用户发送出请求到前端控制器DispatcherServlet。 2、DispatcherServlet收到请求调用HandlerMapping&#xff08;处理器映射器&#xff09;。 3、HandlerMapping找到具体的处理器(可查找xml配置或注解配置)&#xff0c;生成处理器对象及处理器拦截器 (如果有)&#xff0c;再…

51单片机学习笔记-3模块化编程

3 模块化编程 [toc] 注&#xff1a;笔记主要参考B站江科大自化协教学视频“51单片机入门教程-2020版 程序全程纯手打 从零开始入门”。 3.1 模块化编程 传统方式编程&#xff1a;所有的函数均放在main.c里&#xff0c;若使用的模块比较多&#xff0c;则一个文件内会有很多的…

1604_linux环境下使用命令行把网页转换成pdf

全部学习汇总&#xff1a; GreyZhang/toolbox: 常用的工具使用查询&#xff0c;非教程&#xff0c;仅作为自我参考&#xff01; (github.com) 使用的工具很容易在彼此之间产生隔离性障碍&#xff0c;比如我最近使用的墨水屏阅读的最合适的文件格式我觉得是pdf&#xff0c;但是我…

路由工具之路由策略router-policy、acl列表与ip-prefix前缀列表的区别、过滤列表filter-policy

3.0.0 路由工具之路由策略router-policy、acl列表与ip-prefix前缀列表的区别、过滤列表filter-policy 目录IP-Prefix前缀列表前缀列表与ACLrouter-policy路由策略应用路由策略过滤路由1、环境介绍2、配置OSPF3、过滤路由&#xff08;1&#xff09;ACL匹配路由方式过滤&#xff…

带死区的PID控制算法及仿真

在计算机控制系统中&#xff0c;某些系统为了避免控制作用过于频繁&#xff0c;消除由于频繁动作所引起的振荡&#xff0c;可采用带死区的PID控制算法&#xff0c;控制算式为&#xff1a;式中&#xff0c;e(k)为位置跟踪偏差;e为一个可调参数&#xff0c;其具体数值可根据实际控…

软件测试职场六年,一个女测试工程师的自我认知

微软自动化测试二年&#xff0c;而后转入阿里做自动化测试三年&#xff0c;经历了入行时的迷茫&#xff0c;而后的笃定&#xff0c;转入移动后对自身定位和价值的怀疑&#xff0c;继而对自动化测试的重新认识&#xff0c;职场六年&#xff0c;终于敢对自动化测试有所论述了。 先…

五个好用的PDF软件推荐!

我们在工作中经常需要选择一款好用的办公软件来转换PDF文件&#xff0c;如果选择的软件不好用&#xff0c;那就回影响我们工作的效率&#xff0c;如果选对了软件&#xff0c;就可以让我们的效率越来越高&#xff0c;足以证明软件的在我们办公中的重要性&#xff0c;下面小编就来…

win远程桌面连接无显示器Ubuntu(22.04.1 LTS)

1、安装ssh server 安装虚拟显示器会导致物理显示器无法使用&#xff0c;为防止虚拟显示出现问题无法连接Ubuntu&#xff0c;在必要时可以使用SSH连接系统。 # Ubuntu Terminal sudo apt-get install openssh-server在Windows中尝试连接 # Windows PowerShell ssh UsernameU…

C语言递归函数(递归调用)详解

一个函数在它的函数体内调用它自身称为递归调用&#xff0c;这种函数称为递归函数。执行递归函数将反复调用其自身&#xff0c;每调用一次就进入新的一层&#xff0c;当最内层的函数执行完毕后&#xff0c;再一层一层地由里到外退出。递归函数不是C语言的专利&#xff0c;Java、…

css背景

1、背景颜色&#xff1a;半透明 <style>div{width: 1000px;height: 100px;/* 背景颜色半透明&#xff0c;其他文字不受影响 */background: rgba(0 ,0 ,0 ,0.3 );}</style> </head> <body><div></div> </body>2、背景图 属性名: ba…

vue前端框架课程笔记(一)

目录初识Vue演示代码模板语法插值语法指令语法使用举例数据绑定两种数据绑定方式示例el和data的两种写法el的两种写法data的两种写法MVVM模型数据代理Object.defineProperty示例参数说明关于getter和setter使用举例泛化的数据代理举例vue中的数据代理原理事件处理指令methods配…

word样式管理:如何对样式进行修改删除

在前面的图文中简单为大家介绍了Word文本样式和表格样式的创建技巧。但对于已经创建好的样式&#xff0c;有时会对样式中的格式进行再次修改&#xff1b;或者是当拿到某个Word文档&#xff0c;它的样式经历多次或多人不断修改&#xff0c;可能导致样式库中的样式混乱不堪&#…

Dockerfile构建Tomcat镜像

Dockerfile构建Tomcat镜像构建步骤1 编写Dockfile文件2 dockcer build构建镜像3 docker run运行容器4 使用容器卷挂载搜索镜像的个数 docker search tomcat |wc -l因此&#xff0c;需要根据自己的项目需求来针对性的构建镜像 构建步骤 1 编写Dockfile文件 看成在编写一个纯净…

React 项目 黑马极客园

React 项目 黑马极客园git地址视频地址项目准备1.项目介绍2.项目搭建3.使用scss预处理器4.配置基础路由5.组件库antd使用6.配置别名路径8.别名路径提示9.安装dev-tools调试工具登录模块1.基本结构搭建2. 创建表单结构3. 表单校验实现4. 获取登录表单数据5. 封装http工具模块6. …

Android Studio Electric Eel | 2022.1.1 版本

前言 各位读者&#xff0c;新年快乐&#xff0c;现在是2023年了&#xff0c;新的一年&#xff0c;我们的开发也会焕然一新&#xff0c;Android开发的工具Android Studio&#xff0c;也能更新到Android Studio Electric Eel | 2022.1.1 版本了&#xff0c;短期内我将使用此版本学…

Delphi 中.deployproj文件结构

Delphi中&#xff0c;如果生成Android或者IOS等APP&#xff0c;需要在Project -> Deployment中按下Deploy图标 &#xff0c;然后就会生成和项目工程文件名称一致&#xff0c;后缀是.deployproj的文件。当然如果直接按下运行图标(RUN)也会自动生成这个文件。一、.deployproj文…

【自然语言处理】情感分析(五):基于 BERT 实现

情感分析&#xff08;五&#xff09;&#xff1a;基于 BERT 实现本文是 情感分析 系列的第 555 篇&#xff0c;也是本系列的收官之作。前四篇分别是&#xff1a; 【自然语言处理】情感分析&#xff08;一&#xff09;&#xff1a;基于 NLTK 的 Naive Bayes 实现【自然语言处理…