线性规划单纯形法原理及实现

news2024/10/4 6:37:48

欢迎关注更多精彩
关注我,学习常用算法与数据结构,一题多解,降维打击。

本期话题:线性规划单纯形法原理及实现

标准化及单纯形方法

相关学习资料
https://www.bilibili.com/video/BV168411j7XL/?spm_id_from=333.788&vd_source=fb27f95f25902a2cc94d4d8e49f5f777

标准化形式

在这里插入图片描述
标准化数学表示如下:

m a x     f ( x ) = c T x s . t .     A x = b ( b ≥ 0 ) x ≥ 0 \begin {array}{c}max \ \ \ f(x)=c^Tx\\ s.t.\ \ \ Ax=b(b\ge 0)\\ x\ge0\end{array} max   f(x)=cTxs.t.   Ax=b(b0)x0

标准化方法

在这里插入图片描述

单纯形法

https://www.bilibili.com/video/BV1xr4y1V7Ae/?spm_id_from=333.788&vd_source=fb27f95f25902a2cc94d4d8e49f5f777
在这里插入图片描述

例题演示

整个过程本质上是一个矩阵行变换过程

练习1 有解
在这里插入图片描述

在这里插入图片描述

大M法

大M法是在标准化以后无法找到一组单位向量作为基变量,不能直接进行单纯形法求解。
需要添加人工变量构造单位向量,同时要对函数添加惩罚项。

m a x Z = 3 x 1 + 4 x 2 { x 1 + x 2 ≤ 6 x 1 + 2 x 2 ≥ 8 x i ≥ 0 ( i = 1 , 2 ) \begin{array}{l} maxZ = 3x_1+4x_2 \\ \left \{\begin{array}{l} x_1+x_2 \le6 \\ x_1+2x_2 \ge 8 \\ x_i \ge 0 (i=1,2) \end{array} \right .\end {array} maxZ=3x1+4x2 x1+x26x1+2x28xi0(i=1,2)

对上式进行标准化

m a x Z = 3 x 1 + 4 x 2 { x 1 + x 2 + x 3            = 6 x 1 + 2 x 2          − x 4 = 8 x i ≥ 0 ( i = 1 , 2 , 3 , 4 ) \begin{array}{l} maxZ = 3x_1+4x_2 \\ \left \{\begin{array}{l} x_1+x_2 + x_3 \ \ \ \ \ \ \ \ \ \ =6 \\ x_1+2x_2 \ \ \ \ \ \ \ \ - x_4 = 8 \\ x_i \ge 0 (i=1,2,3,4) \end{array} \right .\end {array} maxZ=3x1+4x2 x1+x2+x3          =6x1+2x2        x4=8xi0(i=1,2,3,4)

上式中最后2个变量组成的矩阵是

[ 1 0 0 − 1 ] \begin {bmatrix} 1&0 \\ 0&-1 \end {bmatrix} [1001]

不是单位矩阵,已经有1列是单位向量,需要再加1列人工变量。
M是一个很大的正数。

m a x Z = 3 x 1 + 4 x 2 − x 5 M { x 1 + x 2 + x 3                     = 6 x 1 + 2 x 2          − x 4 + x 5 = 8 x i ≥ 0 ( i = 1 , 2 , 3 , 4 , 5 ) \begin{array}{l} maxZ = 3x_1+4x_2 - x_5M \\ \left \{\begin{array}{l} x_1+x_2 + x_3 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ =6 \\ x_1+2x_2 \ \ \ \ \ \ \ \ - x_4 +x_5 = 8 \\ x_i \ge 0 (i=1,2,3,4,5) \end{array} \right .\end {array} maxZ=3x1+4x2x5M x1+x2+x3                   =6x1+2x2        x4+x5=8xi0(i=1,2,3,4,5)

添加人加变量后,就可以按照单纯形法来求解了。

解的情况

  • 唯一解:当所有解检验数σ都小于0时。
  • 多个解:当所有解检验数σ都小于0且至少有1个检验数=0时。
  • 无上界解:不管取到多大的值,总有更大的,也是无解的一种情况。条件是存在1个检验数大于0且列向量系数都小于0的列。
  • 无解:当所有解检验数σ都小于等于0且存在人工变量为基变量。

算法实现

算法步骤

  • 对问题进行标准化
  • 添加松驰变量
  • 检查是否有单位矩阵,若没有则添加相应的人工变量
  • 按照单纯形法进行迭代

核心代码设计

class LPS
		{
		public:
			/*
			* 初始化
			* 设定变量个数,目标函数系数,优化类型
			*/
			void InitProb(int n, std::vector<double> &c, OptimizationType ot);
			/*
			* 添加条件
			* 向量x前面是x系数, b代表右边常数,ct代表符号
			*/
			void AddCondition(std::vector<double> &x, double b, CmpType ct);

			/*
			* 求解规划
			*/
			Result solve();

		private:
			OptimizationType OT;
			int varNums; 
			std::vector<double> originCeff;
			std::vector<std::vector<double>> conditionsCeff;
			std::vector<double> conditionsB;
			std::vector<CmpType> conditionsCmpType;


		private:
			// 运算过程变量
			std::vector<double> ceff; // 标准化后的系数
			std::vector<std::unordered_map<int , double>> equaltations;// 等式
			std::vector<VarType> varType;
			int sysRelaxNumMark;// 松驰变量+原始变量个数
			Result res;
			Eigen::Matrix<double, -1, -1> determinant; // 行列式
			std::vector<int> baseVarInd; // 基变量编号

			/*
			* 标准化
			*/
			void normalize();

			/*
			* 添加松驰变量,构造等式
			*/
			void addRelaxVar();

			/*
			* 添加人工变量, 获得初始基
			*/
			void addManualVar();

			/*
			* 迭代
			*/
			void iteration();

			/*
			* 汇总结果
			*/
			void genResult();


			/*
			* 中间过程
			*/
			void printFram(const std::vector<double>& sigma, const std::vector<double>& theta);
		};

代码实现

https://gitcode.com/chenbb1989/3DAlgorithm/blob/master/commonFunc/BasicTools/Math/Simplex.cpp
#pragma once

#include "../include/Math/Simplex.h"
#include <iostream>

#define SIMPLEXDEBUG

using namespace BasicTools;
using namespace Simplex;

int BasicTools::Simplex::CmpDouble(double a)
{
	if (abs(a) < EPS)return 0;
	if (a > 0)return 1;
	return -1;
}

void LPS::InitProb(int n, std::vector<double> &c, OptimizationType ot)
{
	assert(ceff.size() == n);
	std::cout << "InitProb" << std::endl;
	varNums = n;
	OT = ot;
	originCeff = c;
	conditionsCeff.clear();
	conditionsB.clear();
	conditionsCmpType.clear();
}

void LPS::AddCondition(std::vector<double> &x, double b, CmpType ct)
{
	assert(x.size() == varNums);
	conditionsCeff.push_back(x);
	conditionsB.push_back(b);
	conditionsCmpType.push_back(ct);
}

Result LPS::solve()
{
	normalize();
	addRelaxVar();
	addManualVar();
	iteration();
	genResult();
	return res;
}

LPS::~LPS()
{
}

void BasicTools::Simplex::LPS::normalize()
{
	ceff = originCeff;
	// 优化类型检查
	if (OT == MIN)for (auto& c : ceff)c *= -1;
	varType.assign(varNums, Sys);

	// 对条件检查bi是否都大于0
	for (int i = 0; i < conditionsCeff.size(); ++i) {
		if (conditionsB[i] < 0) {
			for (auto& c : conditionsCeff[i])c *= -1;
			conditionsB[i] *= -1;
			if (conditionsCmpType[i] != EQ)conditionsCmpType[i] =CmpType(int(conditionsCmpType[i]) ^1);
		}
	}

}

void BasicTools::Simplex::LPS::addRelaxVar()
{
	// 利用map存储等式
	equaltations.resize(conditionsCmpType.size());
	for (int i = 0; i < equaltations.size(); ++i) 
		for (int j = 0; j < conditionsCeff[i].size(); ++j) equaltations[i][j] = conditionsCeff[i][j];

	for (int i = 0; i < conditionsCmpType.size();++i) {
		auto t = conditionsCmpType[i];
		if (t != EQ) {
			varType.push_back(Relax);
			ceff.push_back(0);
		}

		if (t == GE) {
			equaltations[i][ceff.size() - 1] = -1;
		}
		else if(t==LE)
		{
			equaltations[i][ceff.size() - 1] = 1;
		}
	}
	sysRelaxNumMark = varType.size();
}

double getCeff(std::unordered_map<int, double>& m, int j) {
	if (m.count(j) == 0)return 0;
	return m[j];
}

void BasicTools::Simplex::LPS::addManualVar()
{
	baseVarInd.resize(equaltations.size());
	for (int i = 0; i < equaltations.size(); ++i) {
		// 先从松驰变量中找基变量
		baseVarInd[i] = -1;
		for (int j = 0; j < sysRelaxNumMark; ++j) {
			// 判断是否为单位向量且当前行为1
			if (CmpDouble(getCeff(equaltations[i],j)-1))continue;

			// 判断其余行为0
			bool find = true ;
			for (int k = 0; k < equaltations.size() && find; ++k) {
				if (k == i)continue;
				if (CmpDouble(getCeff(equaltations[k], j))) find = false;
			}

			if (find) {
				baseVarInd[i] = j;
				break;
			}

		}

		// 没找到,添加人工变量,优化函数系数中加入大-M
		if (baseVarInd[i] == -1) {
			baseVarInd[i] = varType.size();
			equaltations[i][varType.size()] = 1;
			ceff.push_back(-BIGM);
			varType.push_back(Manual);
		}
	}
}

void BasicTools::Simplex::LPS::iteration()
{	
	std::vector<double> baseCeff(baseVarInd.size()); // CBi
	std::vector<bool> isBaseVar(varType.size(), false);
	for (int i = 0; i < baseVarInd.size(); ++i) {
		baseCeff[i] = ceff[baseVarInd[i]];
		isBaseVar[baseVarInd[i]] = true;
	}
	int n = baseVarInd.size(), m = ceff.size()+1;
	// 构造行列式
	determinant.resize(baseVarInd.size(), m);
	for (int i = 0; i < n; ++i) {
		for (int j = 0; j < m - 1; ++j)
			determinant(i, j) = getCeff(equaltations[i], j);
		determinant(i, m - 1) = conditionsB[i];
	}	

	// 单纯形迭代
	while (1) {
		int inBase = -1, outBase=-1;
		double sigma = 0, theta=0;
		std::vector<double> sigmas(ceff.size(), 0), thetas(n, BIGM);

		// 计算sigma确定进基
		for (int i = 0; i < varType.size(); ++i) {
			// 基变量不用算肯定是0
			if (isBaseVar[i])continue;

			double t = ceff[i];
			bool unbound = true;
			for (int j = 0; j < n; ++j) {
				t -= baseCeff[j] * determinant(j, i);
				if (unbound && determinant(j, i) > 0)unbound = false;
			}
			sigmas[i] = t;
			if (unbound && t > 0) {
				std::cout << "NoUpBound" << std::endl;
				res.rt = NoUpBound;
#ifdef SIMPLEXDEBUG
				printFram(sigmas, thetas);
#endif
				return;
			}
			if (sigma < t) {
				inBase = i;
				sigma = t;
			}
			if (CmpDouble(t) == 0 && inBase == -1) {
				inBase = -2;
			}
		}

		// 迭代结束,先判断是否无解,查看基变量是否有人工变量		
		for (int i = 0; i < baseVarInd.size() && inBase<0;++i) {
			if (varType[baseVarInd[i]] == Manual) {
				res.rt = NoSolution;
				std::cout << "NoSolution" << std::endl;
#ifdef SIMPLEXDEBUG
				printFram(sigmas, thetas);
#endif
				return;
			}
		}

		// 有解, 结束
		if (inBase == -1) {
			res.rt = OnlyOne;
#ifdef SIMPLEXDEBUG
			printFram(sigmas, thetas);
#endif
			return;
		}
		if(inBase==-2)
		{
			res.rt = More;
#ifdef SIMPLEXDEBUG
			printFram(sigmas, thetas);
#endif
			return;
		}

		// 还没结束,继续找退基
		for (int i = 0; i < equaltations.size(); ++i) {
			if(CmpDouble(determinant(i, inBase))<=0)continue;
			auto t = determinant(i, m - 1) / determinant(i, inBase);
			thetas[i] = t;
			if (outBase == -1 || t < theta) {
				theta = t;
				outBase = i;
			}
		}

#ifdef SIMPLEXDEBUG
		printFram(sigmas, thetas);
#endif
		//换基
		isBaseVar[baseVarInd[outBase]] = false;
		baseVarInd[outBase] = inBase;
		baseCeff[outBase] = ceff[inBase];
		isBaseVar[inBase] = true;

		// 变换行列式
		double v = determinant(outBase, inBase);
		for (int j = 0; j < m; ++j)determinant(outBase, j) /= v;

		for (int i = 0; i < n; ++i) {
			if (i == outBase)continue;
			v= determinant(i, inBase);
			for (int j = 0; j < m; ++j)determinant(i, j) -= determinant(outBase,j)*v;
		}
	}
}

void BasicTools::Simplex::LPS::genResult()
{
	if (res.rt == NoUpBound || res.rt == NoSolution)return;
	res.Z = 0;
	res.x.assign(varNums, 0);
	for (int i = 0; i < baseVarInd.size(); ++i) {
		res.Z += ceff[baseVarInd[i]] * determinant(i, varType.size());
		res.x[baseVarInd[i]] = determinant(i, varType.size());
	}
	if (OT == MIN)res.Z *= -1;	

#ifdef SIMPLEXDEBUG
	printf("求解类型:%s\n", OT == MIN ? "MIN" : "MAX");
	/*printf("是否有解:");
	switch (res.rt)
	{
	case NoUpBound:
		puts("NoUpBound");
		return;
	case NoSolution:
		puts("NoSolution");
		return;
	case More:
		puts("More");
		break;
	case OnlyOne:
		puts("OnlyOne");
		break;
	default:
		break;
	}

	printf("Z=%.3f\n", res.Z);
	printf("X取值\n");

	for (int i = 0; i < res.x.size(); ++i) 
		printf("x%d: %.3f\n", i, res.x[i]);*/
#endif
}

void BasicTools::Simplex::LPS::printFram(const std::vector<double>& sigma, const std::vector<double>& theta)
{
	double z = 0;
	//	第一行
	printf("\tCj\t");
	for (int i = 0; i < ceff.size(); ++i) {
		if (CmpDouble(BIGM + ceff[i]) == 0) {
			printf("-M\t");
		}
		else {
			printf("%.3f\t", ceff[i]);
		}
	}
	puts("");
	//	第二行
	printf("CBi\ti\t");
	for (int i = 0; i < ceff.size(); ++i) {
		printf("x%d\t", i);
	}
	printf("bi\tthi\t");
	puts("");
	// 打印变量
	int m = ceff.size() + 1;
	for (int i = 0; i < baseVarInd.size(); ++i) {
		if (CmpDouble(BIGM + ceff[baseVarInd[i]])==0) printf("-M");
		else printf("%.2f", ceff[baseVarInd[i]]);
		printf("\t%d\t", baseVarInd[i]);

		for (int j = 0; j < m; ++j) {
			printf("%.3f\t", determinant(i, j));
		}
		z += determinant(i, m - 1)* ceff[baseVarInd[i]];
		if (CmpDouble(BIGM - theta[i]))printf("%.2f\t", theta[i]);
		else printf("-\t");
		puts("");
	}

	// 检验数
	printf("\tej\t");
	for (int i = 0; i < ceff.size(); ++i) {
		printf("%.2e\t", sigma[i]);
		//std::cout << std::scientific << std::precision(2) << sigma[i];
	}

	printf("Z=%.3e\t", z);
	puts("");
	puts("----------------------------------------------------------------------------------------");

}

测试文件格式

0(表示目标函数是max);1(表示目标函数是min)  

3 3 约束条件矩阵维数,第一个是变量个数,第二个是约束条件个数

4 5 1   目标函数系数向量

3 2 1 18 0         最后一个数字表示约束条件符号,0表示大于等于

2 1 0 4 1          最后一个数字表示约束条件符号,1表示小于等于

1 2 0 5 2         最后一个数字表示约束条件符号,2表示等于

效果测试

文件路径:https://gitcode.com/chenbb1989/3DAlgorithm/tree/master/Tests/BasicTest/SimplexTest
t1
在这里插入图片描述

t2
在这里插入图片描述

t3
在这里插入图片描述

t4
在这里插入图片描述
t5
在这里插入图片描述
t6
在这里插入图片描述


本人码农,希望通过自己的分享,让大家更容易学懂计算机知识。创作不易,帮忙点击公众号的链接。

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

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

相关文章

用于将Grafana默认数据库sqlite3迁移到MySQL数据库

以下是一个方案&#xff0c;用于将Grafana数据迁移到MySQL数据库。 背景: grafana 默认采用的是sqlite3&#xff0c;当我们要以集群形式部署的时使用mysql较为方便&#xff0c;试了很多sqlite转mysql的方法要么收费,最后放弃。选择自己动手风衣足食。 目标: 迁移sqlite3切换…

【深圳游戏业:腾讯引领小型公司创新求发展】

深圳游戏业&#xff1a; 腾讯引领小型公司创新求发展 一 深圳游戏公司主要类型 腾讯集团 作为中国最大的游戏公司&#xff0c;腾讯在游戏领域可以说是第一强者。2022年&#xff0c;腾讯的游戏业务营收高达1707亿元&#xff0c;约占了中国整个游戏市场总收入的64%。 刚开始时&…

【机器学习算法】KNN鸢尾花种类预测案例和特征预处理。全md文档笔记(已分享,附代码)

本系列文章md笔记&#xff08;已分享&#xff09;主要讨论机器学习算法相关知识。机器学习算法文章笔记以算法、案例为驱动的学习&#xff0c;伴随浅显易懂的数学知识&#xff0c;让大家掌握机器学习常见算法原理&#xff0c;应用Scikit-learn实现机器学习算法的应用&#xff0…

基于四足机器人和机械臂的运动控制系统(一)

文章目录 一、项目框架二、设计内容与功能需求1. 导航与路径规划2. 视觉感知3. 运动控制4. 精准遥控5. 环境探测6. 云端监控与数据分析7. 人机协同8. 充电桩9. 紧急响应与救援 三、硬件设计1. 四足机器人2. 机械臂3. 机器主控板4. 遥控器板5. 舵机驱动板 四、软件设计1. 环境2.…

【机器学习笔记】14 关联规则

关联规则概述 关联规则&#xff08;Association Rules&#xff09;反映一个事物与其他事物之间的相互依存性和关联性。如果两个或者多个事物之间存在一定的关联关系&#xff0c;那么&#xff0c;其中一个事物就能够通过其他事物预测到。 关联规则可以看作是一种IF-THEN关系。…

Sora:最强文生视频工具

Sora是什么 Sora&#xff0c;是一款能够根据文本创建出逼真的、富有想象力场景的AI模型。Sora能够娴熟地创造出高达一分钟的高清视频&#xff0c;其视觉内容丰富多样&#xff0c;分辨率精准无误。Sora的强大之处在于&#xff0c;它通过在视频和图像的压缩潜在空间中进行训练&a…

[ai笔记10] 关于sora火爆的反思

欢迎来到文思源想的ai空间&#xff0c;这是技术老兵重学ai以及成长思考的第10篇分享&#xff01; 最近sora还持续在技术圈、博客、抖音发酵&#xff0c;许多人都在纷纷发表对它的看法&#xff0c;这是一个既让人惊喜也感到焦虑的事件。openai从2023年开始&#xff0c;每隔几个…

SpringSecurity + OAuth2 详解

SpringSecurity入门到精通 ************************************************************************** SpringSecurity 介绍 **************************************************************************一、入门1.简介与选择2.入门案例-默认的登录和登出接口3.登录经过了…

笑营宝课后延时服务选课报名管理系统简介

课后延时服务是在“双减”政策背景下推向全国的校园服务。开展丰富多彩的课后服务&#xff0c;既解决家长负担&#xff0c;又能在校内提供作业辅导及素质提升课程&#xff0c;实现教育公平。是解决孩子三点半放学之后的校园服务&#xff0c;但也需要最大限度的降低学校老师的工…

基于java的企业校园招聘平台的设计与实现

分享一个自己的毕业设计&#xff0c;想要获取源码的同学加V&#xff1a;qq2056908377 链接&#xff1a;https://pan.baidu.com/s/1It0CnXUvc9KVr1kDcHWvEw 提取码&#xff1a;1234 摘要&#xff1a; 摘要&#xff1a;本毕业设计旨在设计和实现一个企业校园招聘平台&#xf…

【详细流程】vue+Element UI项目中使用echarts绘制圆环图 折线图 饼图 柱状图

vueElement UI项目中数据分析功能需要用到圆环图 折线图 饼图 柱状图等&#xff0c;可视化图形分析 安装流程及示例 1.安装依赖 npm install echarts --save2.在main.js中引入并挂载echarts import echarts from echarts Vue.prototype.$echarts echarts3.在需要使用echart…

代码随想录刷题笔记-Day20

1. 二叉树的最近公共祖先 236. 二叉树的最近公共祖先https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/ 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#x…

RecombiMAb anti-mouse CD40,FGK4.5-CP133单克隆抗体

FGK4.5-CP133单克隆抗体是原始FGK4.5单克隆抗体的重组嵌合型抗体。可变结构域序列与原始FGK4.5克隆号相同&#xff0c;但是恒定区序列已经从大鼠IgG2a变为小鼠IgG2a。FGK4.5-CP133抗体像原始大鼠IgG2a抗体一样&#xff0c;不包含Fc突变。 FGK4.5-CP133单克隆抗体能与小鼠CD40(也…

压缩感知(Compressed Sensing,CS)的基础知识

压缩感知&#xff08;Compressed Sensing&#xff0c;CS&#xff09;是一种用于信号处理的技术&#xff0c;旨在以少于奈奎斯特采样定理所要求的样本频率来重构信号。该技术利用信号的稀疏性&#xff0c;即信号可以用较少的非零系数表示。压缩感知在图像获取中的应用使得在采集…

阿里云个人建站笔记

导航 一、购买ECS服务器二、配置mysql&#xff08;一&#xff09;安装Mysql步骤一&#xff1a;安装mysql步骤二&#xff1a;配置MySQL步骤三&#xff1a;远程访问MySQL数据库 &#xff08;二&#xff09;给实例配置安全组策略&#xff08;三&#xff09;设置防火墙 一、购买ECS…

防御保护——综合实验

拓扑图 实验需求&#xff1a; 1.Fw1和Fw2组成主备模式的双机热备 2.DMZ区存在两台服务器&#xff0c;现在要求生产区的设备仅能在办公时间&#xff08;9:00-18:00&#xff09;访问&#xff0c;办公区的设备全天都可以访问。 3.办公区设备可以通过电信链路和移动链路上网(多对多…

Linux 实例常用内核参数介绍—容器访问外部网络之ip_forward数据包转发

文章目录 1 问题解决1.1 问题1.2 原因1.3 解决临时打开永久打开 下面为扩展内容Linux 实例常用内核参数介绍:[https://cloud.tencent.com/document/product/213/46400](https://cloud.tencent.com/document/product/213/46400) 2 net.ipv4.ip_forward内核参数通俗解释3 在Linux…

[office] EXCEL怎么制作大事记图表- #学习方法#其他

EXCEL怎么制作大事记图表? 在宣传方面&#xff0c;经常会看到一些记录历史事件、成长历程的图&#xff0c;非常的直观、好看(如下图所示)。那么是怎么做到呢呢?这里我们介绍一下用EXCEL表格快速做出事件记录图的方法。 1、首先&#xff0c;做出基础表格(如下图一所示)。表格…

nacos部署

简介 Nacos 阿里巴巴推出来的开源项目&#xff0c;是更易于构建云原生应用的动态服务发现、配置管理和服务管理平台 Nacos 致力于发现、配置和管理微服务&#xff0c;并提供简单易用的特性集&#xff0c;能够快速实现动态服务发现、服务配置、服务元数据及流量管理。 Nacos 更…

金三银四,全网最详细的软件测试面试题总结

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 前面看到了一些面试题&#xff0c;总感觉会用得到&#xff0c;但是看一遍又记不住&#xff0c;所…