最优化理论与自动驾驶(二-补充):求解算法(梯度下降法、牛顿法、高斯牛顿法以及LM法,C++代码)

news2024/11/15 23:33:25

在之前的章节里面(最优化理论与自动驾驶(二):求解算法)我们展示了最优化理论的基础求解算法,包括高斯-牛顿法(Gauss-Newton Method)、梯度下降法(Gradient Descent Method)、牛顿法(Newton's Method)和勒文贝格-马夸尔特法(Levenberg-Marquardt Method, LM方法)法。在实际工程应用中,我们一般采用C++进行开发,所以本文补充了上述求解方法的C++代码。在实际应用中,我们既可以自己进行简单的求解,也可以采用第三方库进行求解。我们列举了三种方式:1)直接使用c++ vector容器 2)采用eigen库进行迭代计算 3)采用eigen库封装好的函数求解,工程应用中建议使用eigen库进行矩阵操作,因为底层进行了大量的优化,包括SIMD指令集优化、懒惰求值策略等。

C++示例代码如下:

以指数衰减模型y=a\cdot e^{bx} 为例,通过不同方法获得最小二乘拟合参数,其中参数为a和b。

1. 梯度下降法

1.1 使用C++ vector容器

#include <iostream>
#include <vector>
#include <cmath>
#include <limits>

// 定义指数衰减模型函数 y = a * exp(b * x)
double model(const std::vector<double>& params, double x) {
    double a = params[0];
    double b = params[1];
    return a * std::exp(b * x);
}

// 定义残差函数
std::vector<double> residuals(const std::vector<double>& params, const std::vector<double>& x, const std::vector<double>& y) {
    std::vector<double> res(x.size());
    for (size_t i = 0; i < x.size(); ++i) {
        res[i] = model(params, x[i]) - y[i];
    }
    return res;
}

// 计算目标函数(即平方和)
double objective_function(const std::vector<double>& params, const std::vector<double>& x, const std::vector<double>& y) {
    std::vector<double> res = residuals(params, x, y);
    double sum = 0.0;
    for (double r : res) {
        sum += r * r;
    }
    return 0.5 * sum;
}

// 计算梯度
std::vector<double> compute_gradient(const std::vector<double>& params, const std::vector<double>& x, const std::vector<double>& y) {
    double a = params[0];
    double b = params[1];
    std::vector<double> res = residuals(params, x, y);

    // 梯度计算
    double grad_a = 0.0;
    double grad_b = 0.0;
    for (size_t i = 0; i < x.size(); ++i) {
        grad_a += res[i] * std::exp(b * x[i]);             // 对 a 的偏导数
        grad_b += res[i] * a * x[i] * std::exp(b * x[i]);   // 对 b 的偏导数
    }
    
    return {grad_a, grad_b};
}

// 梯度下降法
std::vector<double> gradient_descent(const std::vector<double>& x, const std::vector<double>& y, const std::vector<double>& initial_params, 
                                     double learning_rate = 0.01, int max_iter = 10000, double tol = 1e-6) {
    std::vector<double> params = initial_params;

    for (int i = 0; i < max_iter; ++i) {
        // 计算梯度
        std::vector<double> gradient = compute_gradient(params, x, y);

        // 更新参数
        std::vector<double> params_new = {params[0] - learning_rate * gradient[0], params[1] - learning_rate * gradient[1]};

        // 检查收敛条件
        double diff = std::sqrt(std::pow(params_new[0] - params[0], 2) + std::pow(params_new[1] - params[1], 2));
        if (diff < tol) {
            std::cout << "Converged after " << i + 1 << " iterations." << std::endl;
            return params_new;
        }

        params = params_new;
    }

    std::cout << "Max iterations exceeded. No solution found." << std::endl;
    return params;
}

int main() {
    // 示例数据
    std::vector<double> x_data = {0, 1, 2, 3, 4, 5};
    std::vector<double> y_data;
    for (double x : x_data) {
        y_data.push_back(2 * std::exp(-1 * x));
    }

    // 初始参数猜测 (a, b)
    std::vector<double> initial_guess = {1.0, -1.0};

    // 执行梯度下降法
    std::vector<double> solution = gradient_descent(x_data, y_data, initial_guess);

    // 输出结果
    std::cout << "Fitted parameters: a = " << solution[0] << ", b = " << solution[1] << std::endl;

    return 0;
}

1.2 使用eigen库

#include <iostream>
#include <Eigen/Dense>
#include <cmath>

using Eigen::VectorXd;

// 定义指数衰减模型函数 y = a * exp(b * x)
VectorXd model(const VectorXd& params, const VectorXd& x) {
    double a = params(0);
    double b = params(1);
    return a * (x.array() * b).exp().matrix(); // 使用 Eigen 的数组和矩阵运算
}

// 定义残差函数
VectorXd residuals(const VectorXd& params, const VectorXd& x, const VectorXd& y) {
    return model(params, x) - y;
}

// 计算目标函数(即平方和)
double objective_function(const VectorXd& params, const VectorXd& x, const VectorXd& y) {
    VectorXd res = residuals(params, x, y);
    return 0.5 * res.squaredNorm(); // 使用 Eigen 的 squaredNorm() 计算平方和
}

// 计算梯度
VectorXd compute_gradient(const VectorXd& params, const VectorXd& x, const VectorXd& y) {
    double a = params(0);
    double b = params(1);
    VectorXd res = residuals(params, x, y);

    // 梯度计算
    double grad_a = (res.array() * (b * x.array()).exp()).sum();                  // 对 a 的偏导数
    double grad_b = (res.array() * a * x.array() * (b * x.array()).exp()).sum();   // 对 b 的偏导数

    VectorXd gradient(2);
    gradient << grad_a, grad_b;

    return gradient;
}

// 梯度下降法
VectorXd gradient_descent(const VectorXd& x, const VectorXd& y, const VectorXd& initial_params, 
                          double learning_rate = 0.01, int max_iter = 10000, double tol = 1e-6) {
    VectorXd params = initial_params;

    for (int i = 0; i < max_iter; ++i) {
        // 计算梯度
        VectorXd gradient = compute_gradient(params, x, y);

        // 更新参数
        VectorXd params_new = params - learning_rate * gradient;

        // 检查收敛条件
        if ((params_new - params).norm() < tol) {
            std::cout << "Converged after " << i + 1 << " iterations." << std::endl;
            return params_new;
        }

        params = params_new;
    }

    std::cout << "Max iterations exceeded. No solution found." << std::endl;
    return params;
}

int main() {
    // 示例数据
    VectorXd x_data(6);
    x_data << 0, 1, 2, 3, 4, 5;
    
    VectorXd y_data = 2 * (-1 * x_data.array()).exp();

    // 初始参数猜测 (a, b)
    VectorXd initial_guess(2);
    initial_guess << 1.0, -1.0;

    // 执行梯度下降法
    VectorXd solution = gradient_descent(x_data, y_data, initial_guess);

    // 输出结果
    std::cout << "Fitted parameters: a = " << solution(0) << ", b = " << solution(1) << std::endl;

    return 0;
}

1.3 使用eigen库QR分解

#include <iostream>
#include <Eigen/Dense>
#include <cmath>

using Eigen::VectorXd;
using Eigen::MatrixXd;

int main() {
    // 示例数据
    VectorXd x_data(6);
    x_data << 0, 1, 2, 3, 4, 5;
    
    VectorXd y_data(6);
    for (int i = 0; i < 6; ++i) {
        y_data(i) = 2 * std::exp(-1 * x_data(i));
    }

    // 对 y_data 取对数以转换为线性方程 ln(y) = ln(a) + b * x
    VectorXd log_y_data = y_data.array().log();

    // 构造线性方程的矩阵形式 A * params = log(y)
    // A 是 x_data 的增广矩阵 [1, x_data]
    MatrixXd A(x_data.size(), 2);
    A.col(0) = VectorXd::Ones(x_data.size()); // 第一列为 1
    A.col(1) = x_data;                        // 第二列为 x_data

    // 使用最小二乘法求解参数 [ln(a), b]
    VectorXd params = A.colPivHouseholderQr().solve(log_y_data);

    // 提取参数 ln(a) 和 b
    double log_a = params(0);
    double b = params(1);
    double a = std::exp(log_a);  // 还原 a

    // 输出拟合结果
    std::cout << "Fitted parameters: a = " << a << ", b = " << b << std::endl;

    return 0;
}

2. 牛顿法

#include <iostream>
#include <Eigen/Dense>
#include <cmath>

using Eigen::VectorXd;
using Eigen::MatrixXd;

// 定义模型函数 y = a * exp(b * x)
VectorXd model(const VectorXd& params, const VectorXd& x) {
    double a = params(0);
    double b = params(1);
    return a * (x.array() * b).exp().matrix(); // 使用 Eigen 的数组和矩阵运算
}

// 定义残差函数
VectorXd residuals(const VectorXd& params, const VectorXd& x, const VectorXd& y) {
    return model(params, x) - y;
}

// 计算梯度和 Hessian 矩阵
std::pair<VectorXd, MatrixXd> gradient_and_hessian(const VectorXd& params, const VectorXd& x, const VectorXd& y) {
    double a = params(0);
    double b = params(1);
    VectorXd res = residuals(params, x, y);
    
    // 计算雅可比矩阵 J
    MatrixXd J(x.size(), 2);
    J.col(0) = (b * x.array()).exp();            // 对 a 的偏导数
    J.col(1) = a * x.array() * (b * x.array()).exp();  // 对 b 的偏导数

    // 计算梯度:g = J.transpose() * res
    VectorXd gradient = J.transpose() * res;

    // 计算 Hessian:H = J.transpose() * J + 二阶项(这里省略二阶项,只保留 J.T * J)
    MatrixXd Hessian = J.transpose() * J;

    return {gradient, Hessian};
}

// 牛顿法求解
VectorXd newton_method(const VectorXd& x, const VectorXd& y, const VectorXd& initial_params, int max_iter = 100, double tol = 1e-6) {
    VectorXd params = initial_params;

    for (int i = 0; i < max_iter; ++i) {
        auto [gradient, Hessian] = gradient_and_hessian(params, x, y);
        
        // 检查 Hessian 是否可逆
        if (Hessian.determinant() == 0) {
            std::cerr << "Hessian matrix is singular." << std::endl;
            return VectorXd();
        }

        // 更新参数:params_new = params - H.inverse() * gradient
        VectorXd delta_params = Hessian.inverse() * gradient;
        VectorXd params_new = params - delta_params;

        // 检查收敛条件
        if (delta_params.norm() < tol) {
            std::cout << "Converged after " << i + 1 << " iterations." << std::endl;
            return params_new;
        }

        params = params_new;
    }

    std::cerr << "Max iterations exceeded. No solution found." << std::endl;
    return params;
}

int main() {
    // 示例数据
    VectorXd x_data(6);
    x_data << 0, 1, 2, 3, 4, 5;

    VectorXd y_data(6);
    y_data = 2 * (-1 * x_data.array()).exp();

    // 初始参数猜测 (a, b)
    VectorXd initial_guess(2);
    initial_guess << 1.0, -1.0;

    // 执行牛顿法
    VectorXd solution = newton_method(x_data, y_data, initial_guess);

    // 输出结果
    if (solution.size() > 0) {
        std::cout << "Fitted parameters: a = " << solution(0) << ", b = " << solution(1) << std::endl;
    } else {
        std::cout << "No solution found." << std::endl;
    }

    return 0;
}

3. 高斯牛顿法

#include <iostream>
#include <Eigen/Dense>
#include <cmath>

using Eigen::VectorXd;
using Eigen::MatrixXd;

// 定义模型函数 y = a * exp(b * x)
VectorXd model(const VectorXd& params, const VectorXd& x) {
    double a = params(0);
    double b = params(1);
    return a * (b * x.array()).exp();
}

// 定义残差函数
VectorXd residuals(const VectorXd& params, const VectorXd& x, const VectorXd& y) {
    return model(params, x) - y;
}

// 定义雅可比矩阵
MatrixXd jacobian(const VectorXd& params, const VectorXd& x) {
    double a = params(0);
    double b = params(1);
    
    MatrixXd J(x.size(), 2);
    J.col(0) = (b * x.array()).exp();                 // 对 a 的偏导数
    J.col(1) = a * x.array() * (b * x.array()).exp();  // 对 b 的偏导数

    return J;
}

// 高斯牛顿法函数
VectorXd gauss_newton(const VectorXd& x, const VectorXd& y, const VectorXd& initial_params, int max_iter = 1000, double tol = 1e-6) {
    VectorXd params = initial_params;

    for (int i = 0; i < max_iter; ++i) {
        VectorXd res = residuals(params, x, y);
        MatrixXd J = jacobian(params, x);

        // 计算更新步长:delta_params = (J^T * J)^(-1) * J^T * res
        VectorXd delta_params = (J.transpose() * J).ldlt().solve(J.transpose() * res);

        // 更新参数
        VectorXd params_new = params - delta_params;

        // 检查收敛条件
        if (delta_params.norm() < tol) {
            std::cout << "Converged after " << i + 1 << " iterations." << std::endl;
            return params_new;
        }

        params = params_new;
    }

    std::cout << "Max iterations exceeded. No solution found." << std::endl;
    return params;
}

int main() {
    // 示例数据
    VectorXd x_data(6);
    x_data << 0, 1, 2, 3, 4, 5;

    VectorXd y_data(6);
    y_data = 2 * (-1 * x_data.array()).exp();

    // 初始参数猜测 (a, b)
    VectorXd initial_guess(2);
    initial_guess << 1.0, -1.0;

    // 执行高斯牛顿法
    VectorXd solution = gauss_newton(x_data, y_data, initial_guess);

    // 输出结果
    std::cout << "Fitted parameters: a = " << solution(0) << ", b = " << solution(1) << std::endl;

    return 0;
}

4. LM法

#include <iostream>
#include <Eigen/Dense>
#include <cmath>

using Eigen::VectorXd;
using Eigen::MatrixXd;

// 定义模型函数 y = a * exp(b * x)
VectorXd model(const VectorXd& params, const VectorXd& x) {
    double a = params(0);
    double b = params(1);
    return a * (b * x.array()).exp();
}

// 定义残差函数
VectorXd residuals(const VectorXd& params, const VectorXd& x, const VectorXd& y) {
    return model(params, x) - y;
}

// 定义雅可比矩阵
MatrixXd jacobian(const VectorXd& params, const VectorXd& x) {
    double a = params(0);
    double b = params(1);
    
    MatrixXd J(x.size(), 2);
    J.col(0) = (b * x.array()).exp();                 // 对 a 的偏导数
    J.col(1) = a * x.array() * (b * x.array()).exp();  // 对 b 的偏导数

    return J;
}

// Levenberg-Marquardt算法
VectorXd levenberg_marquardt(const VectorXd& x, const VectorXd& y, const VectorXd& initial_params, int max_iter = 1000, double tol = 1e-6, double lambda_init = 0.01) {
    VectorXd params = initial_params;
    double lamb = lambda_init;
    
    for (int i = 0; i < max_iter; ++i) {
        VectorXd res = residuals(params, x, y);
        MatrixXd J = jacobian(params, x);
        
        // 计算 Hessian 矩阵近似 H = J.T * J
        MatrixXd H = J.transpose() * J;
        
        // 添加 lambda 倍的单位矩阵,以调整步长方向
        MatrixXd H_lm = H + lamb * MatrixXd::Identity(params.size(), params.size());
        
        // 计算梯度
        VectorXd gradient = J.transpose() * res;
        
        // 计算参数更新值
        VectorXd delta_params = H_lm.ldlt().solve(gradient);
        
        // 更新参数
        VectorXd params_new = params - delta_params;
        
        // 计算新的残差
        VectorXd res_new = residuals(params_new, x, y);
        
        // 如果新的残差平方和小于当前残差平方和,则接受新的参数,并减小 lambda
        if (res_new.squaredNorm() < res.squaredNorm()) {
            params = params_new;
            lamb /= 10;
        } else {
            // 否则增加 lambda
            lamb *= 10;
        }
        
        // 检查收敛条件
        if (delta_params.norm() < tol) {
            std::cout << "Converged after " << i + 1 << " iterations." << std::endl;
            return params;
        }
    }

    std::cout << "Max iterations exceeded. No solution found." << std::endl;
    return params;
}

int main() {
    // 示例数据
    VectorXd x_data(6);
    x_data << 0, 1, 2, 3, 4, 5;

    VectorXd y_data(6);
    y_data = 2 * (-1 * x_data.array()).exp();

    // 初始参数猜测 (a, b)
    VectorXd initial_guess(2);
    initial_guess << 1.0, -1.0;

    // 执行 Levenberg-Marquardt 法
    VectorXd solution = levenberg_marquardt(x_data, y_data, initial_guess);

    // 输出结果
    std::cout << "Fitted parameters: a = " << solution(0) << ", b = " << solution(1) << std::endl;

    return 0;
}

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

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

相关文章

蓝桥杯【物联网】零基础到国奖之路:十一. LORA

蓝桥杯【物联网】零基础到国奖之路:十一. LORA 第一节 LORA理论第二节 Lora的无线收发数据1&#xff0c;硬件解读2&#xff0c;CubeMX配置3&#xff0c;MDK代码 第一节 LORA理论 Lora是一种长距离、低功耗的无线通信技术&#xff0c;专为iot和远程应用设计。Lora技术基于半双工…

傅里叶变换及其应用笔记

傅里叶变换 预备知识学习路线扼要描述两者之间的共同点&#xff1a;线性运算周期性现象对称性与周期性的关系周期性 预备知识 学习路线 从傅里叶级数&#xff0c;过度到傅里叶变换 扼要描述 傅里叶级数&#xff08;Fourier series&#xff09;&#xff0c;几乎等同于周期性…

针对考研的C语言学习(定制化快速掌握重点1)

1.printf函数的几个要点 printf函数中所有的输出都是右对齐的&#xff0c;除非在%后面添加负号&#xff0c;则表示左对齐 #include<stdio.h> int main() {int num 10;int nums 100;float f 1000.2333333333;printf("%3d\n", nums);//%3d表示输出的总宽度至…

Python画笔案例-064 绘制彩花之旋转羽毛

1、绘制彩花之旋转羽毛 通过 python 的turtle 库绘制 彩花之旋转羽毛,如下图: 2、实现代码 绘制 彩花之旋转羽毛,以下为实现代码: """彩花之旋转羽毛.py本程序需要coloradd模块支持,安装方法:pip install coloradd技术支持微信scartch8,QQ:406273900www.l…

ROS2 技术及分布式介绍

PC端开发环境搭建 WSL环境搭建 https://www.guyuehome.com/46574 In Windows 11 builds that support wslg: 1. Open up powershell and enter wsl --install ROS2系统安装 方法一 • 设置编码 Bash $ sudo apt update && sudo apt install loca…

DAY80服务攻防-中间件安全HW2023-WPS 分析WeblogicJettyJenkinsCVE

知识点 1、中间件-Jetty-CVE&信息泄漏 2、中间件-Jenkins-CVE&RCE执行 3、中间件-Weblogic-CVE&反序列化&RCE 4、应用WPS-HW2023-RCE&复现&上线CS 中间件-Jetty-CVE&信息泄漏 Jetty是一个开源的servlet容器&#xff0c;它为基于Java的Web容器…

RAG(Retrieval-Augmented Generation)检索增强生成技术基础了解学习与实践

RAG&#xff08;Retrieval-Augmented Generation&#xff09;是一种结合了信息检索&#xff08;Retrieval&#xff09;和生成模型&#xff08;Generation&#xff09;的技术&#xff0c;旨在提高生成模型的性能和准确性。RAG 技术通过在生成过程中引入外部知识库&#xff0c;使…

两张图讲透软件测试实验室认证技术体系与质量管理体系

软件测试实验室在申请相关资质认证时&#xff0c;需要建立一套完整的质量管理体系和过硬的技术体系。这其中涉及到的要素非常繁杂&#xff0c;工作量非常庞大&#xff0c;为了帮助大家快速梳理清楚软件测试实验室认证过程中质量管理体系和技术体系的建设思路&#xff0c;我们梳…

Humanoid 3D Charactor_P08_Federica

3D模型(人形装备)女孩 “P08_联邦” 内容仅为3D人物模型。 图片中的背景和家具不包括在内。 由Blender制作 包括: 1. 人形机器人3D模型和材质。 2. “Unity-chan!”着色器。 性别:女 装备:人形 皮肤网格:4个骨骼权重 多边形: 20000~40000 纹理分辨率:2K纹理 混合形状:…

AI漏扫工具:SmartScanner

SmartScanner 是一款先进的 AI 漏洞扫描工具&#xff0c;旨在帮助用户识别和修复软件、系统及网络中的安全漏洞。以下是 SmartScanner 的一些主要特点&#xff1a; 1.智能识别 通过机器学习和深度学习技术&#xff0c;SmartScanner 能够快速识别已知和未知的漏洞&#xff0c;提…

Redis实战--Redis的数据持久化与搭建Redis主从复制模式和搭建Redis的哨兵模式

Redis作为一个高性能的key-value数据库&#xff0c;广泛应用于缓存、消息队列、排行榜等场景。然而&#xff0c;Redis是基于内存的数据库&#xff0c;这意味着一旦服务器宕机&#xff0c;内存中的数据就会丢失。为了解决这个问题&#xff0c;Redis提供了数据持久化的机制&#…

为什么会出现电话机器人?语音电话机器人的出现起到了什么作用?

电话机器人的出现是科技发展与市场需求相结合的产物&#xff0c;它们的广泛应用反映了现代社会对效率、成本和服务质量的不断追求。以下是电话机器人出现的几个主要原因。 1. 市场需求的变化 随着经济的发展和消费模式的转变&#xff0c;客户对服务的期望不断提高。他们希望能…

ecology9的待办推送外部系统

&#x1f3c6;本文收录于《全栈Bug调优(实战版)》专栏&#xff0c;主要记录项目实战过程中所遇到的Bug或前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&…

Maven重点学习笔记(包入门 2万字)

Maven依赖管理项目构建工具 尚硅谷 5h 2023最新版 一&#xff0c;Maven简介 1.为什么学习Maven 1.1, Maven是一个依赖管理工具 1️⃣ jar包的规模 随着我们使用越来越多的框架&#xff0c;或者框架封装程度越来越高&#xff0c;项目中使用的jar包也越来越多。项目中&…

小红书开源StoryMaker:个性化图像生成模型,实现角色一致性与背景变化的完美结合

文章链接&#xff1a;https://arxiv.org/pdf/2409.12576 Github链接&#xff1a;https://github.com/RedAIGC/StoryMaker 模型链接&#xff1a;https://huggingface.co/RED-AIGC/StoryMaker 亮点直击 解决了生成具有一致面部、服装、发型和身体的图像的任务&#xff0c;同时允许…

关系数据库标准语言SQL(3,4)

目录 定义基本表 数据类型 模式与表 创建基本表 修改基本表 删除基本表 定义基本表 每行都定义一个列及其约束条件&#xff0c;最后加上表的约束条件。 其中sno&#xff0c;约束为primary key&#xff0c;表示它是主码&#xff0c;sname约束为unique表示它是唯一值&#x…

VScode的c/c++环境搭建

文章目录 前言1、下载VScode2、安装cpptools3、下载MinGW4、配置环境变量5、修改C/C配置文件5.1、编辑 launch.json 配置文件5.2、编辑 tasks.json 文件 6、运行7、其他 前言 用VS进行C/C开发&#xff0c;环境配置遵循以下步骤&#xff1a; 1.指定头文件目录。“配置属性”→…

【质优价廉】GAP9 AI算力处理器赋能智能可听耳机,超低功耗畅享未来音频体验!

当今世界&#xff0c;智能可听设备已经成为了流行趋势。随后耳机市场的不断成长起来&#xff0c;消费者又对AI-ANC&#xff0c;AI-ENC&#xff08;环境噪音消除&#xff09;降噪的需求逐年增加&#xff0c;但是&#xff0c;用户对于产品体验的需求也从简单的需求&#xff0c;升…

Spring源码学习:SpringMVC(3)mvcannotation-driven标签解析【RequestMappingHandlerMapping生成】

目录 前言mvc:annotation-driven标签概述mvc:annotation-driven标签解析【RequestMappingHandlerMapping生成】AnnotationDrivenBeanDefinitionParser#parse &#xff08;解析入口&#xff09;RequestMappingHandlerMapping的实例化类图afterPropertiesSetAbstractHandlerMetho…

Spring6梳理11——依赖注入之注入List集合类型属性

以上笔记来源&#xff1a; 尚硅谷Spring零基础入门到进阶&#xff0c;一套搞定spring6全套视频教程&#xff08;源码级讲解&#xff09;https://www.bilibili.com/video/BV1kR4y1b7Qc 11 依赖注入之注入List集合类型属性 11.1 创建实体类Emp以及Dept Dept类中添加了遍历Emp…