首页 > 编程语言 >基于弦截法求解多项式函数根的 C++ 程序及其多领域延伸应用

基于弦截法求解多项式函数根的 C++ 程序及其多领域延伸应用

时间:2025-01-10 16:30:21浏览次数:3  
标签:std 函数 double 多项式 C++ result 截法 y1 include

一、头文件部分

#include<iostream>
#include<cmath>
#include<vector>
#include<algorithm>
#include <opencv2\opencv.hpp>
#include <Eigen/Dense>
#include <iostream>
#include <complex>
#include <unsupported/Eigen/Polynomials>
using namespace std;
using namespace cv;

  • #include<iostream>:用于输入输出操作,例如使用 cout 和 cin 进行数据的输入和输出。
  • #include<cmath>:包含了数学函数,如 sqrtsincos 等。在本代码中,可能用于一些数学计算,尽管未显式看到使用除基本运算符外的 cmath 函数,但对于可能的扩展是有帮助的。
  • #include<vector>:引入了 vector 容器,用于存储一系列的元素,在代码中用于存储计算得到的结果,如 resultresult2result3 等。
  • #include<algorithm>:包含一些算法,在代码中使用了 sort 函数对 vector 中的元素进行排序。
  • #include <opencv2\opencv.hpp>:这是 OpenCV 的头文件,OpenCV 是一个强大的计算机视觉库,常用于图像处理、计算机视觉算法等领域。在本代码中,使用了 cv::Mat 类来存储多项式的系数,并且调用了 cv::solvePoly 函数求解多项式的根。
  • #include <Eigen/Dense> 和 #include <unsupported/Eigen/Polynomials>:Eigen 是一个 C++ 模板库,提供了矩阵和向量的操作以及线性代数、矩阵分解、多项式求解等功能。这里使用了 Eigen 中的 PolynomialSolver 来求解多项式的根,Eigen::VectorXd 表示一个动态大小的双精度向量。
  • using namespace std; 和 using namespace cv;:使用了 std 命名空间和 cv 命名空间,避免在代码中重复书写 std:: 和 cv::,但这在大型项目中不是一个好的编程习惯,可能会导致命名冲突。

二、函数 f(double x) 部分

double f(double x)
{
    return -1.9064497988904843e-14 * x * x * x * x * x * x * x + 1.2113999489408717e-11 * x * x * x * x * x * x + -3.1990140110989868e-09 * x * x * x * x * x
        + 4.5356985840400170e-07 * x * x * x * x + -3.159308768194479e-05 * x * x * x + 0.0017530360721085955 * x * x + -0.043954029312592555 * x + 0.45047264597968462;
}

  • 这是一个多项式函数,根据输入的 x 计算多项式的值。输入为 x,输出为多项式在 x 处的值。
  • 多项式的形式为:-1.9064497988904843e-14 * x^7 + 1.2113999489408717e-11 * x^6 + -3.1990140110989868e-09 * x^5 + 4.5356985840400170e-07 * x^4 + -3.159308768194479e-05 * x^3 + 0.0017530360721085955 * x^2 + -0.043954029312592555 * x + 0.45047264597968462

三、函数 point(double a, double b) 部分

double point(double a, double b) {
    return (a * f(b) - b * f(a)) / (f(b) - f(a));
}

  • 该函数计算连接点 (a, f(a)) 和 (b, f(b)) 的弦与 x 轴的交点的横坐标。
  • 其数学原理基于直线的两点式方程:对于直线上两点 (x1, y1) 和 (x2, y2),直线方程为 y - y1 = ((y2 - y1) / (x2 - x1)) * (x - x1)。当 y = 0(与 x 轴相交)时,求解 x 得到 x = (x1 * y2 - x2 * y1) / (y2 - y1)。在这里,x1 = ay1 = f(a)x2 = by2 = f(b)

四、函数 root(double a, double b) 部分

double root(double a, double b) {
    double x, y;
    double y1;
    double e = 0.0001;
    y1 = f(a);
    do {
        x = point(a, b);
        y = f(x);
        if (y * y1 > 0) {
            y1 = y;
            a = x;
        }
        else {
            b = x;
        }
    } while (fabs(y) >= e);
    return x;
}

  • 这是弦截法求方程根的核心函数。弦截法是一种求方程 f(x)=0 根的迭代方法,类似于牛顿法,但不需要求导数。
  • 初始化 y1 = f(a)
  • 在 do-while 循环中:
    • 首先调用 point(a, b) 求出弦与 x 轴的交点 x
    • 计算 y = f(x)
    • 若 y 和 y1 同号(y * y1 > 0),更新 a = x 并将 y1 更新为 y;若异号,更新 b = x
    • 当 |y| < ee 是计算精度)时,迭代结束,认为找到了满足精度要求的根。

五、main 函数部分

int main()
{
    vector<double>result;
    vector<double>result2;
    vector<double>result3;
    double a, b;
    for (int i = 0; i < 300; i = i + 20)
    {
        double test = root(i, i + 10);
        result.push_back(test);
    }
    sort(result.begin(), result.end());
    result2.push_back(result[0]);
    for (int i = 1; i < result.size() - 1; i++)
    {
        if (abs(result[i] - result[i - 1]) > 5)
            result2.push_back(result[i]);
    }
    cv::Mat coef = (cv::Mat_<float>(8, 1) << 0.45047264597968462, -0.043954029312592555, 0.0017530360721085955
       , -3.7159308768194479e-05, 4.5356985840400170e-07, -3.1990140110989868e-09, 1.2113999489408717e-11, -1.9064497988904843e-14);
    cv::Mat roots;
    cv::solvePoly(coef, roots);
    int cols = roots.cols;
    int rows = roots.rows;
    vector<double>res;
    double value;
    for (int i = 0; i < rows; i++) 
    {
        for (int j = 0; j < cols; j++) 
        {
            value = roots.at<cv::Vec2f>(i, j)[0];
            res.push_back(value);
        }
    }
    sort(res.begin(), res.end());
    Eigen::VectorXd coeffs(8);
    coeffs << 0.45047264597968462, -0.043954029312592555, 0.0017530360721085955
       , -3.7159308768194479e-05, 4.5356985840400170e-07, -3.1990140110989868e-09, 1.2113999489408717e-11, -1.9064497988904843e-14; 
    Eigen::PolynomialSolver<double, Eigen::Dynamic> solver;
    solver.compute(coeffs);
    const Eigen::PolynomialSolver<double, Eigen::Dynamic>::RootsType& roots3 = solver.roots();
    std::cout << "Roots are:" << std::endl;
    for (int i = 0; i < roots3.size(); ++i) 
    {
        std::cout << roots3[i] << std::endl;
    }
    return 0;
}

  • vector<double>result; 等:创建 double 类型的 vector 容器用于存储结果。
  • 第一个 for 循环:
    • 从 0 到 300 步长为 20,调用 root(i, i + 10) 计算 [i, i + 10] 区间内的根并存储在 result 中。
    • 对 result 进行排序。
    • 将 result[0] 存储在 result2 中,并筛选出距离上一个存储元素大于 5 的元素添加到 result2 中。
  • cv::Mat coef:创建一个 cv::Mat 类型的矩阵存储多项式的系数。
  • cv::solvePoly(coef, roots);:使用 OpenCV 的 solvePoly 函数求解多项式的根,结果存储在 roots 矩阵中。
  • 两个嵌套的 for 循环:将 roots 矩阵中的元素存储在 vector<double> res 中,并排序。
  • Eigen::VectorXd coeffs(8);:使用 Eigen 创建多项式系数的向量。
  • Eigen::PolynomialSolver<double, Eigen::Dynamic> solver;:创建一个多项式求解器。
  • solver.compute(coeffs);:使用 Eigen 的求解器求解多项式的根。
  • 最后将求解的根输出。

五、并行计算的延伸

  • 多线程计算加速
    • 在计算 root 函数时,我们可以使用 C++11 的 std::thread 来并行计算不同区间的根。例如,将 for (int i = 0; i < 300; i = i + 20) 改为:
#include <thread>
#include <vector>
std::vector<std::thread> threads;
for (int i = 0; i < 300; i = i + 20) {
    threads.emplace_back([i]() {
        double test = root(i, i + 10);
        std::lock_guard<std::mutex> guard(mtx);
        result.push_back(test);
    });
}
for (auto& t : threads) {
    t.join();
}

这里使用 std::thread 创建多个线程,每个线程计算一个区间的根,并将结果存储在 result 向量中。使用 std::mutex 保证线程安全,避免多个线程同时修改 result 向量导致的数据竞争。

  • 也可以使用 std::async 进行异步计算,例如:
#include <future>
std::vector<std::future<double>> futures;
for (int i = 0; i < 300; i = i + 20) {
    futures.emplace_back(std::async(std::launch::async, [i]() {
        return root(i, i + 10);
    }));
}
for (auto& f : futures) {
    result.push_back(f.get());
}

这样可以利用多核处理器的优势,提高程序的性能,特别是在处理大规模数据或复杂函数 f(x) 时。

六、函数优化的延伸

  • 使用牛顿迭代法
    牛顿迭代法的迭代公式为 。对于函数 f(x),我们可以实现牛顿迭代法如下:
double df(double x) {
    return -1.9064497988904843e-14 * 7 * x * x * x * x * x * x + 1.2113999489408717e-11 * 6 * x * x * x * x * x + -3.1990140110989868e-09 * 5 * x * x * x * x + 4.5356985840400170e-07 * 4 * x * x * x + -3.159308768194479e-05 * 3 * x * x + 0.0017530360721085955 * 2 * x + -0.043954029312592555;
}
double newtonRoot(double x0) {
    double e = 0.0001;
    double x = x0;
    do {
        double fx = f(x);
        double dfx = df(x);
        double x_new = x - fx / dfx;
        if (std::abs(x_new - x) < e) break;
        x = x_new;
    } while (true);
    return x;
}

这里 df(x) 是 f(x) 的导数,牛顿迭代法通常比弦截法收敛速度更快,但需要计算导数,适用于可导函数。

  • 使用改进的弦截法
double steffensenRoot(double x0) {
    double e = 0.0001;
    double x = x0;
    do {
        double fx = f(x);
        double gx = (f(x + fx) - fx) / fx;
        double x_new = x - fx / gx;
        if (std::abs(x_new - x) < e) break;
        x = x_new;
    } while (true);
    return x;
}

七、误差分析的延伸

  • 精度评估
    • 在求解出根之后,我们可以计算函数 f(x) 在根处的值,来评估根的精度。例如,在 root 函数中添加:
double root(double a, double b) {
    double x, y;
    double y1;
    double e = 0.0001;
    y1 = f(a);
    do {
        x = point(a, b);
        y = f(x);
        if (y * y1 > 0) {
            y1 = y;
            a = x;
        }
        else {
            b = x;
        }
    } while (fabs(y) >= e);
    double error = fabs(f(x)); // 计算误差
    std::cout << "Error at root: " << error << std::endl;
    return x;
}

这样可以输出每个根的误差,根据误差的大小,我们可以调整 e 的值,或者尝试其他迭代方法。

  • 我们可以使用不同的精度标准,例如相对误差,double error = fabs(f(x)) / fabs(x);,这对于求解非常大或非常小的根时更有意义。

八、动态区间选择的延伸

  • 自适应区间调整
    • 我们可以根据函数 f(x) 的斜率或曲率来动态调整区间。例如,我们可以计算 f(x) 的导数或二阶导数,根据导数的大小来调整区间长度。在 root 函数中,可以添加以下逻辑:
double root(double a, double b) {
    double x, y;
    double y1;
    double e = 0.0001;
    y1 = f(a);
    do {
        x = point(a, b);
        y = f(x);
        if (y * y1 > 0) {
            y1 = y;
            a = x;
            double df = (f(x + 0.001) - f(x)) / 0.001; // 近似导数
            if (std::abs(df) > threshold) {
                b = x + small_interval;
            } else {
                b = x + large_interval;
            }
        }
        else {
            b = x;
            double df = (f(x + 0.001) - f(x)) / 0.001;
            if (std::abs(df) > threshold) {
                a = x - small_interval;
            } else {
                a = x - large_interval;
            }
        }
    } while (fabs(y) >= e);
    return x;
}

这里 threshold 是一个阈值,small_interval 和 large_interval 是根据导数大小选择的不同区间长度,当导数较大时,使用较小的区间,提高精度;当导数较小时,使用较大的区间,提高计算效率。

九、可视化的延伸

  • 使用 Matplotlib C++ 进行函数绘图
    • 可以使用 Matplotlib C++ 库来绘制函数 f(x) 的图像以及标记出求解的根。以下是一个简单的示例:
#include <matplotlibcpp.h>
namespace plt = matplotlibcpp;
int main() {
    std::vector<double> x;
    std::vector<double> y;
    for (double i = 0; i < 10; i += 0.1) {
        x.push_back(i);
        y.push_back(f(i));
    }
    plt::plot(x, y);
    // 假设 result 存储了求解的根
    std::vector<double> roots = result; 
    plt::scatter(roots, std::vector<double>(roots.size(), 0), 50);
    plt::show();
}

这样可以直观地看到函数的曲线和求解出的根的位置,帮助我们更好地理解函数的性质和根的分布。

十、交互式应用的延伸

  • 用户输入和异常处理
    • 可以将程序修改为一个交互式的工具,允许用户输入多项式的系数和求解区间。例如:
int main() {
    std::vector<double> coeffs;
    int degree;
    std::cout << "Enter the degree of the polynomial: ";
    std::cin >> degree;
    std::cout << "Enter the coefficients: ";
    for (int i = 0; i <= degree; ++i) {
        double coeff;
        std::cin >> coeff;
        coeffs.push_back(coeff);
    }
    double a, b;
    std::cout << "Enter the interval [a, b]: ";
    std::cin >> a >> b;
    // 构建函数 f(x)
    auto f = [&coeffs](double x) {
        double res = 0;
        for (size_t i = 0; i < coeffs.size(); ++i) {
            res += coeffs[i] * std::pow(x, i);
        }
        return res;
    };
    // 调用 root 函数求解
    double rootValue = root(a, b);
    std::cout << "Root in the interval [" << a << ", " << b << "] is: " << rootValue << std::endl;
    return 0;
}

并且可以添加异常处理,如:

try {
    // 输入部分
} catch (std::exception& e) {
    std::cerr << "Error: " << e.what() << std::endl;
}

这样可以使程序更加灵活,用户可以输入不同的多项式和求解区间,程序会输出相应的根,同时能处理用户输入错误的情况。

标签:std,函数,double,多项式,C++,result,截法,y1,include
From: https://blog.csdn.net/m0_44975814/article/details/145024006

相关文章

  • C++项目Visual Studio 如何在Release编译模式下断点调试
    在VS中,Debug编译模式下通常是默认支持断点调试的,但有时项目需要会需要在Release编译模式下进行打断点调试原因无外乎三点:Debug模式下编译运行无异常,但Release下出现问题需要release下屏蔽断言和部分宏定义win平台的动态库区分release和debug模式,部分项目......
  • 【C++】C++11(二)
    目录九、可变参数模板十、lambda表达式10.1C++98中的一个例子10.2lambda表达式10.3lambda表达式语法10.3.1lambda表达式各部分说明10.3.2捕获列表说明10.4函数对象与lambda表达式十一、包装器11.1function包装器11.2bind十二、线程库12.1线程12.1.1thread......
  • 通过循环展开减少循环控制的开销 c++实现
    循环展开是一种优化技术,通过减少循环控制的开销来提高程序性能。在C++中,可以通过手动展开循环来实现这一点。以下是如何在C++中实现循环展开的示例。示例:向量加法的循环展开我们将创建一个简单的向量加法示例,展示如何通过循环展开来提高性能。1.基本向量加法首先实现一个......
  • C++ 中使用预取指令可以帮助减少缓存未命中的延迟
    在C++中使用预取指令可以帮助减少缓存未命中的延迟,从而提高程序性能。以下是如何在代码中实现预取的详细步骤和示例。1.理解预取指令预取指令允许CPU提前加载数据到缓存中,从而在需要时可以更快地访问。这在处理大量数据时特别有用。例如,SSE2提供了_mm_prefetch指令。2.......
  • 在c++代码怎么实现数据对齐
    在C++中实现数据对齐可以通过以下几种方法:1.使用alignas关键字C++11引入了alignas关键字,可以用来控制变量的对齐方式。#include<iostream>#include<emmintrin.h>//SSE2alignas(16)floata[4]={1.0f,2.0f,3.0f,4.0f};alignas(16)floatb[4]={5.0f,6.0......
  • C++ 文件操作入门到实践:轻松搞定逐行读取,助力期末作业与毕业设计!
    ......
  • C/C++序列重构问题
    问题描述采儿是一位负责一班......
  • 【c++实战项目】负载均衡式在线OJ
    主页:醋溜马桶圈-CSDN博客专栏:实战项目_醋溜马桶圈的博客-CSDN博客gitee:mnxcc(mnxcc)-Gitee.com项目源码文件版:OnlineJudge_file:负载均衡在线OJ项目基于文件版本数据库版:mnxcc/OnlineJudge_MySQL目录1.项目目标2.技术与开发环境2.1技术2.2开发环境3.项目宏观......
  • C++泛型编程:类模版中成员函数的创建时机,类模版函数传参、类模版继承
    普通类的成员函数的话,在刚开始就创建出来了,但是类模版中的成员函数的话,只有在具体调用运行的时候才会被创建,可见以下代码例子:#include<iostream>usingnamespacestd;classpeople1{public: voidrun(){ cout<<"跑"<<endl; }};classcircle1{public: void......
  • 从上千份大厂面经呕心沥血整理:大厂高频手撕面试题(数据结构和算法篇 ,C++实现亲试可跑)
    目录 怎么判断两个链表是否相交?怎么优化?(字节跳动、货拉拉)手撕冒泡排序(美团)手撕快速排序(作业帮)手撕堆排序(美团)手撕归并排序(美团)手撕二分查找(VIVO)字符串的全排列(要求去重)(字节跳动)求一个字符串中最长不重复子串的长度(字节跳动) 反转字符串的单词:如何在原字符串上翻转......