多项式最大公因式求解器

引言

在使用辗转相除法计算最大公因式求解过程中,部分题目计算过程十分繁琐,比如《高等代数》第五版(北京大学数学系前代数小组编)29页习题5的(2):
f ( x ) = x 4 − 4 x 3 + 1 , g ( x ) = x 3 − 3 x 2 + 1 f(x) = x^4 - 4x^3 + 1, g(x) = x^3 - 3x^2 + 1 f(x)=x44x3+1,g(x)=x33x2+1
f ( x ) f(x) f(x) g ( x ) g(x) g(x) 的最大公因式。

使用辗转相除法求解过程如下:

g ( x ) g(x) g(x) f ( x ) f(x) f(x)
q 2 ( x ) = − 1 3 x + 10 9 q_2(x) = -\frac{1}{3}x + \frac{10}{9} q2(x)=31x+910 x 3 − 3 x 2 + 1 x^3 - 3x^2 + 1 x33x2+1 x 4 − 4 x 3 + 1 x^4 - 4x^3 + 1 x44x3+1 x − 1 = q 1 ( x ) x - 1 = q_1(x) x1=q1(x)
x 3 + 1 3 x 2 − 2 3 x x^3 + \frac{1}{3}x^2 - \frac{2}{3}x x3+31x232x x 4 − 3 x 3 + x x^4 - 3x^3 + x x43x3+x
− 10 3 x 2 + 2 3 x + 1 -\frac{10}{3}x^2 + \frac{2}{3}x + 1 310x2+32x+1 − x 3 − x + 1 -x^3 - x + 1 x3x+1
− 10 3 x 2 − 10 9 x + 20 9 -\frac{10}{3}x^2 - \frac{10}{9}x + \frac{20}{9} 310x2910x+920 − x 3 + 3 x 2 − 1 -x^3 + 3x^2 - 1 x3+3x21
− 4096 243 x + 2816 243 -\frac{4096}{243}x + \frac{2816}{243} 2434096x+2432816 16 9 x − 11 9 = r 2 ( x ) \frac{16}{9}x - \frac{11}{9} = r_2(x) 916x911=r2(x) − 3 x 2 − x + 2 = r 1 ( x ) -3x^2 - x + 2 = r_1(x) 3x2x+2=r1(x) − 27 16 x − 441 256 = q 3 ( x ) -\frac{27}{16}x - \frac{441}{256} = q_3(x) 1627x256441=q3(x)
16 9 x \frac{16}{9}x 916x − 3 x 2 + 33 16 x -3x^2 + \frac{33}{16}x 3x2+1633x
− 11 9 -\frac{11}{9} 911 − 49 16 x + 2 -\frac{49}{16}x + 2 1649x+2
− 11 9 -\frac{11}{9} 911 − 49 16 x + 539 256 -\frac{49}{16}x + \frac{539}{256} 1649x+256539
0 = r 3 ( x ) 0 = r_3(x) 0=r3(x) − 27 256 = r 2 ( x ) -\frac{27}{256} = r_2(x) 25627=r2(x)

另附一张更为清晰的图像:
辗转相除法计算最大公因式
可以看到在这道题目中,由于系数设置的不合理,导致计算过程十分繁琐,故萌生了使用代码求解的想法(目前为止,还没有找到能够清晰展示计算过程的求解器)。

代码实现

所使用Python环境为3.12。

import tkinter as tk
from tkinter import ttk, messagebox
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
from sympy import Poly, S, latex, simplify
from latex2sympy2 import latex2sympy
from sympy.abc import x
import matplotlib

# 设置中文字体支持
matplotlib.rcParams['font.sans-serif'] = ['SimHei']  # 使用黑体
matplotlib.rcParams['axes.unicode_minus'] = False    # 正确显示负号

class PolynomialGCDCalculator:
    def __init__(self, root):
        self.root = root
        self.root.title("多项式辗转相除法计算器")

        # 设置字体
        plt.rcParams['mathtext.fontset'] = 'cm'
        plt.rcParams['font.family'] = ['SimHei', 'STIXGeneral']

        # 主框架布局
        main_frame = ttk.Frame(root, padding="5")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

        # 输入框
        ttk.Label(main_frame, text="多项式 A:").grid(row=0, column=0, sticky=tk.W)
        self.poly_a = ttk.Entry(main_frame, width=50)
        self.poly_a.grid(row=0, column=1, pady=5)
        self.poly_a.insert(0, "x^4 -4x^3 +1")

        ttk.Label(main_frame, text="多项式 B:").grid(row=1, column=0, sticky=tk.W)
        self.poly_b = ttk.Entry(main_frame, width=50)
        self.poly_b.grid(row=1, column=1, pady=5)
        self.poly_b.insert(0, "x^3 -3 x^2 +1")

        # 计算按钮
        ttk.Button(main_frame, text="计算GCD", command=self.calculate_gcd).grid(
            row=2, column=0, columnspan=2, pady=10)

        # 左侧:步骤显示
        ttk.Label(main_frame, text="计算步骤").grid(row=3, column=0, sticky=tk.W)
        self.steps_text = tk.Text(main_frame, width=50, height=20)
        self.steps_text.grid(row=4, column=0, padx=5, pady=5, sticky=tk.W)

        # 右侧:公式显示
        ttk.Label(main_frame, text="LaTeX公式渲染").grid(row=3, column=1, sticky=tk.W)
        self.fig = Figure(figsize=(5, 3),dpi=150)
        self.canvas = FigureCanvasTkAgg(self.fig, master=main_frame)
        self.canvas.get_tk_widget().grid(row=4, column=1, padx=5, pady=5)

    def latex_to_polynomial(self, latex_str):
        try:
            expr = latex2sympy(latex_str)
            poly = Poly(expr, x)
            return poly
        except Exception as e:
            messagebox.showerror("错误", f"LaTeX解析错误:{str(e)}\n请检查输入格式是否正确")
            return None

    def format_polynomial(self, poly):
        """将 sympy 的多项式格式化为标准 LaTeX 表达式"""
        if poly is None:
            return ""

        terms = []
        for deg, coeff in sorted(poly.terms(), reverse=True):
            coeff = simplify(coeff)

            # 提取指数(如将 (4,) 转为 4)
            degree = deg[0] if isinstance(deg, tuple) else deg

            if degree == 0:  # 常数项
                terms.append(latex(coeff))
            elif degree == 1:  # 一次项
                if coeff == 1:
                    terms.append("x")
                elif coeff == -1:
                    terms.append("-x")
                else:
                    terms.append(f"{latex(coeff)}x")
            else:  # 高次项
                if coeff == 1:
                    terms.append(f"x^{{{degree}}}")
                elif coeff == -1:
                    terms.append(f"-x^{{{degree}}}")
                else:
                    terms.append(f"{latex(coeff)}x^{{{degree}}}")

        # 将各项用 "+" 拼接,替换 "+ -" 为 "-"
        formatted_poly = " + ".join(terms).replace("+ -", "- ")
        return formatted_poly

    def calculate_gcd(self):
        poly_a = self.latex_to_polynomial(self.poly_a.get())
        poly_b = self.latex_to_polynomial(self.poly_b.get())

        if poly_a is None or poly_b is None:
            return

        # 清空左侧步骤
        self.steps_text.delete("1.0", tk.END)

        steps = []  # 存储所有步骤的 LaTeX 格式公式
        r_prev = poly_a
        r_curr = poly_b
        step = 1

        try:
            while not r_curr.is_zero:
                q, r = r_prev.div(r_curr)

                # 每步计算公式
                formula = (
                    f"{self.format_polynomial(r_prev)} = "
                    f"({self.format_polynomial(q)}) \\times ({self.format_polynomial(r_curr)}) + ({self.format_polynomial(r)})"
                )

                steps.append(f"Step {step}: {formula}")

                # 左侧文本显示
                step_text = f"步骤 {step}:\n{formula}\n\n"
                self.steps_text.insert(tk.END, step_text)

                r_prev = r_curr
                r_curr = r
                step += 1

            # 最终结果
            gcd = r_prev.monic()
            final_formula = f"\\text{{Maximum common factor: }} {self.format_polynomial(gcd)}"
            steps.append(final_formula)

            # 左侧更新最大公因式
            self.steps_text.insert(tk.END, f"最大公因式(monic):\n{self.format_polynomial(gcd)}\n")

            # 渲染所有步骤到右侧
            self.display_latex("\n".join(steps))

        except Exception as e:
            messagebox.showerror("错误", f"计算过程中出错:{str(e)}")
            return

    def display_latex(self, steps):
        """在右侧渲染所有步骤的 LaTeX 公式"""
        self.fig.clear()

        ax = self.fig.add_subplot(111)
        ax.axis('off')

        # 按行渲染每个公式
        for i, step in enumerate(steps.split("\n")):
            y_pos = 1 - i * 0.15
            ax.text(0.05, y_pos, f"${step}$", fontsize=6, va="top")

        self.fig.tight_layout()
        self.canvas.draw()


def main():
    root = tk.Tk()
    app = PolynomialGCDCalculator(root)
    root.mainloop()


if __name__ == "__main__":
    main()

目前,代码实现了如下效果:

  • 以LaTeX格式输入相应的多项式;
  • 左侧显示计算步骤;
  • 右侧渲染出对应的数学公式。

代码中默认的多项式即为引言中所提及到的多项式。

运行结果展示

代码运行结果
每一步都会显示出当前步骤的商和余式,在练习过程中可根据该结果更为容易的写出辗转相除的过程。

下面再以29页习题5的(3)作为测试,该小题多项式的系数涉及到根式:
f ( x ) = x 4 − 10 x 2 + 1 , g ( x ) = x 4 − 4 2 x 3 + 6 x 2 + 4 2 x + 1 f(x) = x^4 - 10x^2 + 1, g(x) = x^4 - 4\sqrt{2}x^3 + 6x^2 + 4\sqrt{2}x + 1 f(x)=x410x2+1,g(x)=x442 x3+6x2+42 x+1
f ( x ) f(x) f(x) g ( x ) g(x) g(x) 的最大公因式。

辗转相除法计算结果
得到
( f ( x ) , g ( x ) ) = x 2 − 2 2 x − 1. (f(x),g(x)) = x^2-2\sqrt{2}x-1. (f(x),g(x))=x222 x1.
另附求解过程如下:
在这里插入图片描述
可以看到求解器得到的结果是正确的。

注: 在辗转相除法计算过程中,最后一个非零余式即为最大公因式,不过需要注意最大公因式是首一多项式,需要进行系数的调整。

2025-02-22更新

更新说明:

  • 使用PyQt5重构界面,更加美观。
  • 使用更为专业的LaTeX渲染工具,使用 KaTeXQWebEngineView 渲染公式,支持动态加载 HTML 和 CSS,使得公式渲染效果更加美观。
  • 提供本地和在线两种katex支持。

代码实现

使用在线资源

import sys
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QLabel, QLineEdit, QPushButton, QTextEdit, QGridLayout
)
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtCore import QUrl
from sympy import Poly, gcd, latex, div
from sympy.abc import x
from latex2sympy2 import latex2sympy  # 导入 latex2sympy
import tempfile

class PolynomialGCDCalculator(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("多项式辗转相除法计算器")
        self.setGeometry(100, 100, 1800, 800)

        # 主框架布局
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        layout = QGridLayout(main_widget)

        # 输入框:多项式 A
        layout.addWidget(QLabel("多项式 A:"), 0, 0, 1, 1)
        self.poly_a = QLineEdit(self)
        self.poly_a.setText("x^4 - 10x^2 + 1")  # 默认多项式 A
        layout.addWidget(self.poly_a, 0, 1, 1, 1)

        # 输入框:多项式 B
        layout.addWidget(QLabel("多项式 B:"), 1, 0, 1, 1)
        self.poly_b = QLineEdit(self)
        self.poly_b.setText("x^4 - 4\\sqrt{2}x^3 + 6x^2 + 4\\sqrt{2}x + 1")  # 默认多项式 B
        layout.addWidget(self.poly_b, 1, 1, 1, 1)

        # 计算按钮
        calculate_button = QPushButton("计算 GCD", self)
        calculate_button.clicked.connect(self.calculate_gcd)
        layout.addWidget(calculate_button, 2, 0, 1, 2)

        # 左侧:步骤显示
        layout.addWidget(QLabel("计算步骤"), 3, 0, 1, 1)
        self.steps_text = QTextEdit(self)
        self.steps_text.setReadOnly(True)
        layout.addWidget(self.steps_text, 4, 0, 1, 1)

        # 右侧:公式显示
        layout.addWidget(QLabel("LaTeX 公式渲染"), 3, 1, 1, 1)
        self.formula_view = QWebEngineView(self)
        layout.addWidget(self.formula_view, 4, 1, 1, 1)

    def latex_to_polynomial(self, latex_str):
        """
        将 LaTeX 格式的多项式转换为 SymPy 多项式。
        """
        try:
            expr = latex2sympy(latex_str)  # 使用 latex2sympy 解析 LaTeX
            poly = Poly(expr, x)
            return poly
        except Exception as e:
            self.steps_text.setText(f"错误:无法解析多项式。\n{str(e)}")
            return None

    def calculate_gcd(self):
        # 获取输入的多项式
        poly_a_input = self.poly_a.text().strip()
        poly_b_input = self.poly_b.text().strip()

        # 将 LaTeX 格式的多项式转换为 SymPy 多项式
        poly_a = self.latex_to_polynomial(poly_a_input)
        poly_b = self.latex_to_polynomial(poly_b_input)

        if poly_a is None or poly_b is None:
            return

        # 使用 SymPy 的 div 方法模拟辗转相除法的步骤
        steps = []
        r_prev = poly_a
        r_curr = poly_b
        step = 1
        while not r_curr.is_zero:
            q, r = div(r_prev, r_curr)
            steps.append(
                f"步骤 {step}: "
                f"{latex(r_prev.as_expr())} = "
                f"({latex(q.as_expr())}) \\times ({latex(r_curr.as_expr())}) + ({latex(r.as_expr())})"
            )
            r_prev = r_curr
            r_curr = r
            step += 1

        # 提取最大公因式的表达式
        gcd_poly = gcd(poly_a, poly_b).as_expr()  # 提取最大公因式的表达式
        final_result = f"最大公因式:${latex(gcd_poly)}$"  # 包裹在 $ 中以确保渲染
        steps.append(final_result)  # 添加到最后一步

        # 更新左侧步骤文本(每步之间空一行)
        self.steps_text.setText("\n\n".join(steps))  # 每步之间插入两个换行符

        # 使用 KaTeX 渲染公式
        latex_content = ""
        for step in steps[:-1]:  # 渲染所有步骤
            if "步骤" in step:  # 提取提示内容
                prompt, formula = step.split(": ", 1)
                latex_content += f"<b>{prompt}:</b> ${formula}$<br><br>"

        # 单独处理最大公因式
        latex_content += f"<b>{steps[-1]}</b><br><br>"

        # html_content 内容
        html_content = f"""
        <!DOCTYPE html>
        <html>
        <head>
            <!-- 使用在线资源 -->
            <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css">
            <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js"></script>
            <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js"
                onload="renderMathInElement(document.body, {{delimiters: [{{left: '$', right: '$', display: false}}]}});"></script>
            <style>
                body {{
                    font-size: 22px; /* 调整整体字体大小 */
                }}
                b {{
                    font-size: 22px; /* 提示内容字体稍大 */
                }}
            </style>
        </head>
        <body>
            <div>{latex_content}</div>
        </body>
        </html>
        """

        # 将 HTML 内容保存为临时文件
        with tempfile.NamedTemporaryFile("w", suffix=".html", delete=False, encoding="utf-8") as f:
            f.write(html_content)
            temp_html_path = f.name

        # 使用 QUrl.fromLocalFile 加载临时文件
        self.formula_view.setUrl(QUrl.fromLocalFile(temp_html_path))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = PolynomialGCDCalculator()
    window.show()
    sys.exit(app.exec_())

使用本地资源

KaTeX 官方 GitHub 发布页面 下载最新的 KaTeX 版本,下载解压后放到当前目录,接下来只需要替换在线资源 代码中110行的html_content 中的内容:

html_content = f"""
        <!DOCTYPE html>
        <html>
        <head>
            <!-- 使用本地资源 -->
            <link rel="stylesheet" href="file:///./katex/katex.min.css">
            <script defer src="file:///./katex/katex.min.js"></script>
            <script defer src="file:///./katex/contrib/auto-render.js"
                onload="renderMathInElement(document.body, {{delimiters: [{{left: '$', right: '$', display: false}}]}});"></script>
            <style>
                body {{
                    font-size: 22px; /* 调整整体字体大小 */
                }}
                b {{
                    font-size: 22px; /* 提示内容字体稍大 */
                }}
            </style>
        </head>
        <body>
            <div>{latex_content}</div>
        </body>
        </html>
        """

之后就可以正常运行了。

效果展示

仍然以前面的第二个样例测试,运行结果如下所示。
结果展示
可以看到计算结果是一致的,但是公式渲染更为美观。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值