前言:个人实现的一个用带头结点的链表实现的一元 n 次多项式运算程序。该程序支持多项式的创建、打印、加、减、乘以及取反操作。并且具备相应的用户操作菜单,用户提示菜单。其中针对多项式的各种操作已经模块化,并且每一个函数的功能都做了非常详细的注释。话不多说,开始讲解代码。
文章目录
存储结构
链表结点结构体
采用单链表的结点形式来存储多项式的每一个单项式。结构体中定义了单项式的系数和次数,以及指向下一个结点的指针。
typedef struct LNode // 定义单项式结点结构体
{
double coef; // 系数,全称 coefficient
int power; // 次数
struct LNode* next;
} LNode;
全局变量
定义了一个指针数组 List,用以存放指向每一个多项式的头结点的指针;
定义了一个 counter,用来计数多项式的个数。
LNode* List[MaxLen]; // 结点指针数组,存放指向多项式的头结点的指针
int counter = 0; // 全局计数器,计数多项式的个数
程序框架
运行过程
主程序运行过程很简单,总体就分这三步:
- 输入多项式的数据
- 选择相应的功能
- 退出程序
我尽量将所有的单一功能都放在了一个函数中,实现了很大程度上的模块化。这也就可以使得我的主程序代码尽量简单明了,仅仅做的事情就是打印用户菜单。
函数介绍
所有的 head 都是指头结点。
链表(多项式)复制函数
LNode* CopyPolyn(LNode* head);
将以 head 为头结点的链表复制给新的链表 head1,返回新链表的头结点。
- 指针 q 指向新链表的开始结点(头结点的后一个结点),指针 p 指向新链表的头结点 head1;
- while 循环中,创建新结点 s,将 q 所指结点信息复制给 s,q 后移,然后将 s 插入新链表;
- 重复第2步直到 q == NULL;
- 返回 head1。
结点(单项式)创建函数
void Creat(LNode* head, double iCoef, int iPower);
依据传入的系数 iCoef 和次数 iPower 创建新的结点,并将其添加到头结点为 head 的链表上。
- 定义遍历指针 p 指向开始结点,q 指向 p 的前驱;
- while 循环中,依据 p 指向的结点的次数和输入次数的大小来判断操作;
结点在链表中按照次数从小到大排列,所以新结点添加进链表时要做以下操作:
- 遵循次数从小到大的插入。
- 有同类项则合并,合并后若为0则删除结点。
链表(多项式)创建函数
void CreatPolynCin();
void CreatPolynIfs();
依据用户的选择,程序执行两种数据加载函数的其中之一。其中前者为读取键盘输入,后者为读取 txt 文本文件输入。txt 文本文件内容我会放在后文中。文本读取函数的具体执行过程用注释标注的非常详细,可以自行理解。
链表(多项式)选择函数
int Choose(int cho[], int len);
智能处理用户操作函数时输入的链表对象,既可以只操作一个链表,也可以操作多个链表。原理是借助一个数组来记录本次操作中用户希望操作的链表对象的头结点指针在数组 LIst 中的下标。
链表(多项式)打印函数
void PrintPolyn(LNode* head);
void ChoosePrint();
调用后者函数,用户输入希望打印的多项式。然后传入相应链表的头结点打印相应的多项式。同时对于输出要做一下格式化的处理。
链表(多项式)相加函数
LNode* AddPolyn(LNode* head1, LNode* head2);
void ChooseAdd();
加法的原理:复制以 head1 为头结点的链表给以 head 为头结点的链表,然后读取 head2 中每一个结点,就像创建链表一样与 head 中的每一个结点进行比较。有同类项则合并,无同类项则插入,有0项则删除。后者函数同理,智能处理用户输入的相加的多项式的对象。
链表(多项式)取反函数
void Opposite(LNode* head);
void ChooseOpposite();
用户选择要取反的对象,程序直接将其取反并打印输出。此函数最多取反一个多项式。
链表(多项式)相减函数
void ChooseSubtract();
该函数实际上是一个减法选择函数,因为多个多项式连续相减可以转换为多个多项式连续相加。只需要将第一个多项式取反,再将最后结果取反即可得到相减的结果。
链表(多项式)相乘函数
LNode* MutPolyn(LNode* head1, LNode* head2);
void ChooseMutiply();
同理,用户选择两个或多个要相乘的多项式,程序会自动处理。乘法的原理也很简单,同样的能化成加法来操作。在这里,我采用的是传统的,即我们平常笔算时用到的多项式乘法规则——项项相乘再相加。感兴趣的同学可以关注我的另一篇博客FFT应用于多项式乘法。这篇博客介绍了如何加快多项式乘法的算法,尤其在面对项数特别大时的情况,有着非常好的优化效果。但是对于项数一千以内的多项式乘法,逐个相乘的效果是要好一点的。
txt文本文件的内容
1.9 2 2 5
5 2 2.5 4
-1.5 2 -2 5 2 4 5 2
5.8 2 -2 4
8 9 5 2 -1 2 -2 5
其实这个你们完全可以自己写的,但是为了方便还是把我自己的,比较完整的测试数据放在这里,请自行取用吧。
完整代码(C++)
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <windows.h>
#define MaxLen 1024
using namespace std;
typedef struct LNode // 定义单项式结点结构体
{
double coef; // 系数,全称 coefficient
int power; // 次数
struct LNode* next;
} LNode;
LNode* List[MaxLen]; // 结点指针数组,存放指向多项式的头结点的指针
int counter = 0; // 全局计数器,计数多项式的个数
// 链表的复制
LNode* CopyPolyn(LNode* head)
{
/*
将 head 所在多项式链表复制给head1
*/
LNode* q = head->next; // 一定要给下一个结点,否则头结点也会被复制进去
LNode* head1 = new LNode;
LNode* p = head1;
while (q != NULL)
{
LNode* s = new LNode;
s->coef = q->coef;
s->power = q->power;
p->next = s;
p = s;
q = q->next;
}
p->next = NULL;
return head1;
}
// 创建单项式结点的函数,提高可重用性
void Creat(LNode* head, double iCoef, int iPower)
{
LNode* p = head->next; // p 是遍历指针
LNode* q = head; // q 是 p 的前驱
while (1) // 该循环依据 p 指向的项的次数和输入次数的大小来判断操作
{
if (p == NULL || p->power > iPower)
{
/*
如果找到了比输入次数大的项,就新建一个结点插在该项之前
或者 p 指向了空,说明不存在比输入次数大的项,就在表尾插入新结点
p == NULL 放在第一个 if 判断,否则在分支判断中会因为读取不到数据出问题
而且由短路逻辑可知,p == NULL 判断为真后就不再判断后一个,同样不会出问题
*/
p = new LNode; // 令 p 指向一个新结点
p->coef = iCoef;
p->power = iPower;
p->next = q->next;
q->next = p;
break; // 退出循环,读取下一个输入
}
else if (p->power < iPower)
{ // 如果输入的次数更大则往后比较下一个结点
p = p->next;
q = q->next;
}
else if (p->power == iPower) // 如果次数一样就合并同类项
{
if (p->coef + iCoef == 0) // 如果系数相反就删除结点
{
q->next = p->next;
free(p); // 释放指针 p 指向的结点空间
p = q->next; // 令 p 指向 q 的后继,为空的话直接退出循环
}
else
p->coef += iCoef;
break; // 上述无论哪个操作执行完都要退出循环读取下一个输入
}
}
}
// 手动输入创建链表
void CreatPolynCin()
{
int inputPower; // 输入的指数
double inputCoef; // 输入的系数
LNode* head = new LNode; // 创建头结点
head->next = NULL; // 头结点 next 指针初始化
cin.get(); // 过滤回车
printf("\t请按照“系数”“次数”的格式输入多项式的各项\n");
printf("\t注意:仅支持一次输入一个多项式\n\t");
while (cin.peek() != EOF && cin.peek() != '\n') // 读到文件结尾或者回车时退出循环,按次数从小到大插入新的结点
{
cin >> inputCoef >> inputPower;
Creat(head, inputCoef, inputPower); // 创建新结点并添加到链表上
}
List[++counter] = head; // 创建了新链表,计数值+1,并将头指针存入数组 List
printf("\t成功创建了第%d个多项式\n", counter);
}
// 文本输入创建链表
void CreatPolynIfs()
{
int i = 0, j, len, flag; // flag 标记是否为负数
double temp; // temp 存储读到的数
double input[MaxLen]; // 数组 input 保存多项式的项
char line[MaxLen]; // 字符串 line 保存每一行的输入
LNode* head;
printf("\t正在读取文件,请稍等:\n");
printf("\t加载中:");
while ((i += 10) < 100) // 按一定的时间间隔输出字符
{
Sleep(300);
printf("*");
}
printf(" 加载完成!\n\n");
Sleep(1000);
ifstream file; // 定义输入文本
file.open("file.txt", ios::in); // 以读方式打开
while (file.getline(line, MaxLen)) // 逐行读取 MaxLen 个字符,读取到 EOF 时会返回-1结束循环
{
for (i = 0, len = 0; line[i] != '\0'; ++i)
{
temp = 0, flag = 0;
if (line[i] >= '0' && line[i] <= '9')
{
if (line[i - 1] == '-') // 前面有负号的话标记一下
flag = 1;
for (j = i; line[j] != '\0' && line[j] != '.' && line[j] != ' '; ++j) // 开始循环找完整的一个数
{ // 循环结束的条件是读到了字符串的终止符或小数点或空格
temp = temp * 10 + line[j] - '0';
}
if (line[j] == ' ') // 读到空格说明该数读完了,把 j - 1 赋给 i 跳过已遍历过的字符
i = j - 1;
if (line[j] == '.') // 如果读到了小数点
{
double num = 0, point = 0; // num 记录小数部分,point 记录小数数位
for (j += 1; line[j] != '\0' && line[j] != ' '; ++j) // j += 1 表示挪到小数第一位
{ // 由于已经读到了小数点,所以循环结束的条件是读到了字符串的终止符或空格
num = num * 10 + line[j] - '0';
++point;
}
temp += num / pow(10, point); // 将 num 转为小数加到 temp 上
i = j - 1;
}
flag ? input[len++] = -1 * temp : input[len++] = temp;
}
}
head = new LNode; // 每次都要让 head 指向新创建的结点,否则创建的所有链表都一样了
head->next = NULL; // 头结点 next 指针初始化
for (i = 0; i < len; i += 2) // 每两个相邻元素取出来作为一个项创建新结点
Creat(head, input[i], (int)input[i + 1]); // 别忘记转换类型,不用担心丢失精度,次数是整数
List[++counter] = head;
printf("\t成功创建了第%d个多项式\n\n", counter);
}
}
// 选择要操作的链表序号
int Choose(int cho[], int len)
{
while (cin.peek() != '\n')
{
cin >> cho[len];
if (cho[len] <= 0 || cho[len] > counter)
{
printf("\t您输入了不存在的%d号多项式!\n", cho[len]);
cin.sync(); // 如果不存在的多项式后仍然有输入,sync 函数将清空它们以免读取进了用户操作
return 0;
}
else
++len;
}
return len;
}
// 多项式打印
void PrintPolyn(LNode* head)
{
LNode* p = head->next;
while (p != NULL)
{
// 因为次数递增,因此次数为0的情况只可能发生在第一个结点上,后面就无需判断次数是否为0
if (p->power == 0) // 常数项直接输出
printf("\t%.1lf", p->coef);
else
{
if (p->coef > 0 && p != head->next) // 如果系数大于0且不是首项
printf(" + %.1lfx^%d", p->coef, p->power);
else if (p->coef < 0 && p != head->next) // 如果系数小于0且不是首项
printf(" - %.1lfx^%d", -1 * p->coef, p->power);
else // 如果是首项直接输出
printf("\t%.1lfx^%d", p->coef, p->power);
}
p = p->next;
}
printf("\n\n");
}
// 用户选择要打印的多项式
void ChoosePrint()
{
int snum[MaxLen] = {0}; // 序号,全称 serial number
int serNum = 0;
cin.get();
printf("\t请输入您要打印的多项式的序号\n\t");
serNum = Choose(snum, serNum);
for (int i = 0; i < serNum; ++i)
{
printf("\t第%d个多项式是:", snum[i]);
PrintPolyn(List[snum[i]]);
}
if (!serNum)
return;
}
// 多项式相加
LNode* AddPolyn(LNode* head1, LNode* head2)
{
/*
加法原理:
复制 head1 链表给 head
将 head2 中的每个结点读入 head 中
*/
LNode* p, * head; // head 指向新链表的头结点
p = head2->next; // q 指向 head2 头结点的后继结点
head = CopyPolyn(head1);
while (p != NULL)
{
Creat(head, p->coef, p->power);
p = p->next;
}
return head;
}
// 用户选择要相加的多项式
void ChooseAdd()
{
int add[MaxLen] = { 0 };
int addNum = 0; // 计录参与相加的多项式个数,标记是否输入错误的标志
cin.get();
printf("\t请输入参与相加的多项式序号\n\t");
addNum = Choose(add, addNum);
if (!addNum)
return;
LNode* addHead = List[add[0]]; // 新建好和式链表头结点并初始化以便参与迭代
for (int i = 1; i < addNum; ++i) // 逐个相加
addHead = AddPolyn(addHead, List[add[i]]); // 迭代
/*
如果相加后的结果只有一个常数,就要存入一个系数次数都为0的结点
而创建结点和相加函数要经常调用,因此不适合在它们内部放入这个操作
*/
if (addHead->next == NULL)
{
LNode* s = new LNode;
s->coef = 0;
s->power = 0;
s->next = NULL;
addHead->next = s;
}
List[++counter] = addHead;
printf("\t相加后的结果是(第%d个多项式):\n", counter);
PrintPolyn(addHead);
}
// 多项式取反
void Opposite(LNode* head)
{
LNode* p = head;
while (p != NULL)
{
p->coef *= -1;
p = p->next;
}
}
// 用户选择取反的多项式
void ChooseOpposite()
{
int oppoNum;
printf("\t请输入要取反的多项式序号\n\t");
cin >> oppoNum;
if (oppoNum > counter || oppoNum <= 0)
printf("\t您要取反的多项式不存在!\n");
else
{
Opposite(List[oppoNum]);
printf("\t取反后的结果是\n");
PrintPolyn(List[oppoNum]);
}
}
// 用户选择要相减的多项式并执行多项相减
void ChooseSubtract()
{
int sub[MaxLen] = { 0 };
int subNum = 0, flag = 0; // subNum 计数相减的多项式的个数,flag 标记是否输入错误的标志
cin.get();
printf("\t请输入参与相减的多项式序号\n\t");
subNum = Choose(sub, subNum);
if (!subNum) // 返回0表明输入序号异常,退出该操作
return;
LNode* subHead = CopyPolyn(List[sub[0]]);
Opposite(subHead); // 先取一次反
for (int i = 1; i < subNum; ++i) // 逐个相加
subHead = AddPolyn(subHead, List[sub[i]]);
Opposite(subHead); // 对最后的结果再取一次反就得到最终结果
if (subHead->next == NULL)
{
LNode* s = new LNode;
s->coef = 0;
s->power = 0;
s->next = NULL;
subHead->next = s;
}
List[++counter] = subHead;
printf("\t相减后的结果是(第%d个多项式):\n", counter);
PrintPolyn(subHead);
}
// 多项式相乘
LNode* MutPolyn(LNode* head1, LNode* head2)
{
LNode* head = new LNode;
head->next = NULL;
LNode* p = head1->next;
LNode* q = head2->next;
while (p != NULL)
{
q = head2->next; // 每次循环都初始化q指向开始结点
while (q != NULL)
{
Creat(head, p->coef * q->coef, p->power + q->power);
q = q->next;
}
p = p->next;
}
return head;
}
// 用户选择要相乘的多项式
void ChooseMutiply()
{
int mut[10] = { 0 };
int mutNum = 0; // 计录参与相乘的多项式个数
cin.get();
cout << "\t请输入参与相乘的多项式序号\n\t";
mutNum = Choose(mut, mutNum);
if (!mutNum) return;
LNode* mutHead = List[mut[0]]; // 新建好和式链表头结点并初始化以便参与递归
for (int i = 1; i < mutNum; i++) // 逐个相加
mutHead = MutPolyn(mutHead, List[mut[i]]); // 迭代
List[++counter] = mutHead;
cout << "\t相乘后的结果是(第" << counter << "个多项式):" << endl;
PrintPolyn(mutHead);
}
// 根据用户选择执行操作
void Operation(int choice)
{
switch (choice)
{
case 1:
printf("\t请问您要手动输入还是文本输入?\n");
printf("\t1.我不怕累!我要手动输入!\n");
printf("\t2.手太酸了,文本输入吧...\n\t");
int c;
cin >> c;
if (c == 1)
CreatPolynCin();
else
CreatPolynIfs();
break;
case 2:
ChoosePrint();
break;
case 3:
ChooseAdd();
break;
case 4:
ChooseSubtract();
break;
case 5:
ChooseMutiply();
break;
case 6:
ChooseOpposite();
break;
case 7:
printf("\t我说您是男神没意见吧?\n");
exit(0);
case 8:
printf("\t我就知道您会瞎选,嘿嘿\n");
break;
default:
printf("\t抱歉该选项功能还未开发!请重新输入!\n");
break;
}
}
// 主函数入口
int main(void)
{
int choice;
printf("\t\t程序初始化成功!\n");
printf("\t\t 欢迎您!\n");
while (1)
{
printf("\t*******************************\n");
printf("\t*** 请选择您要执行的操作 ***\n");
printf("\t*** 1.创建多项式 ***\n");
printf("\t*** 2.打印多项式 ***\n");
printf("\t*** 3.多项式相加 ***\n");
printf("\t*** 4.多项式相减 ***\n");
printf("\t*** 5.多项式相乘 ***\n");
printf("\t*** 6.多项式取反 ***\n");
printf("\t*** 7.退出程序 ***\n");
printf("\t*******************************\n");
printf("\t");
cin >> choice;
Operation(choice);
}
return 0;
}
希望本篇博客能对你起到一点的帮助作用,也希望能动动小手点个赞哟~~。