引言
在使用辗转相除法计算最大公因式求解过程中,部分题目计算过程十分繁琐,比如《高等代数》第五版(北京大学数学系前代数小组编)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)=x4−4x3+1,g(x)=x3−3x2+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 x3−3x2+1 | x 4 − 4 x 3 + 1 x^4 - 4x^3 + 1 x4−4x3+1 | x − 1 = q 1 ( x ) x - 1 = q_1(x) x−1=q1(x) |
x 3 + 1 3 x 2 − 2 3 x x^3 + \frac{1}{3}x^2 - \frac{2}{3}x x3+31x2−32x | x 4 − 3 x 3 + x x^4 - 3x^3 + x x4−3x3+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 −x3−x+1 | ||
− 10 3 x 2 − 10 9 x + 20 9 -\frac{10}{3}x^2 - \frac{10}{9}x + \frac{20}{9} −310x2−910x+920 | − x 3 + 3 x 2 − 1 -x^3 + 3x^2 - 1 −x3+3x2−1 | ||
− 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) 916x−911=r2(x) | − 3 x 2 − x + 2 = r 1 ( x ) -3x^2 - x + 2 = r_1(x) −3x2−x+2=r1(x) | − 27 16 x − 441 256 = q 3 ( x ) -\frac{27}{16}x - \frac{441}{256} = q_3(x) −1627x−256441=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)=x4−10x2+1,g(x)=x4−42x3+6x2+42x+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))=x2−22x−1.
另附求解过程如下:
可以看到求解器得到的结果是正确的。
注: 在辗转相除法计算过程中,最后一个非零余式即为最大公因式,不过需要注意最大公因式是首一多项式,需要进行系数的调整。
2025-02-22更新
更新说明:
- 使用PyQt5重构界面,更加美观。
- 使用更为专业的LaTeX渲染工具,使用
KaTeX
和QWebEngineView
渲染公式,支持动态加载 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>
"""
之后就可以正常运行了。
效果展示
仍然以前面的第二个样例测试,运行结果如下所示。
可以看到计算结果是一致的,但是公式渲染更为美观。