数学中一元n次多项式可表示成如下的形式:

 Pn(x)=p0+p1x+p2x^2+…+pnx^n     (最多有 n+1 项,n +1 个系数唯一确定她)     

  (1)请设计一套接口用以表示和操作一元多项式

  (2)根据上述设计实现一元n次多项式的加法运算

  (3)根据上述设计实现一元n次多项式的乘法运算

分析: 

题目大概意思:

数学里常见的一元 n 次表达式,设计出加减乘除的函数(方法),同时提供对外接口,把内部实现封装起来,而 n 的大小没有指定。

问题的本质:

就是一系列的单个未知数 x,指数和系数数字p(i),0 =< i <= n组成的长串的表达式,然后把长长的表达式进行数学运算,假发和乘法,那么立马想到,这里需要线性表这个数据结构,符号多项式的表示和操作是线性表处理的典型用例。 且有变量 N,那么自然会联系到范围问题。

解决思路:

对问题进行进一步分解,常用的线性表,无非就是链表和顺序表,又分静态和动态内存分配的。数学的一元多项式,用一个线性表 P 来表示:P = ( p0, p1, p2, …, pn )

每一项,x的指数  隐含在其系数 p(i) 的下标i里。这样的话,只是存储系数就 ok了,未知数 x 的指数让系数下标标识。比较简单是用数组(静态顺序表存储),那么问题来了……

存储空间不够了怎么办?

显然,可以使用动态线性表,这样,伸缩自如!再也不担心不够存储了!继续分析,这样解决了空间大小的问题(有限范围内的内存),剩下的就是设计计算的算法,联想中学时代的算数,多项式并不是每一个项都必须写出来的,那么问题来了……

如果 p 里含有大量的0怎么办?

显然这样的存储结构也不太好,会大量浪费内存。比如 p=1 + 7x^12000,要用一个长度为 12001 的线性表来表示,表中仅有 两 个非零系数,会浪费大量存储空间。 不划算。需要改变策略,首先一个原则就是:系数=0不存储!那么自然是只存储非0系数,而指数就必须同时也要存储!否则无法标识指数。其实,日常生活里,也是这样的,一个数学或者物理化学的公式,表达式,系数为0的项,没人会把它写出来吧?!而且这样的多项式数序上叫稀疏多项式。

最终,确定使用链表来存储,因为,系数为0的项,经过计算,可能变为非0,相反非0的项经过计算,也可能变味0,必然要用到删除和插入算法,那么显然链表还是最佳的表示方法,效率比较高,不用大量移动元素,且可以动态增长。节省存储空间。

定义 ADT三要素:数据对象,数据关系,数据操作

PS:其实还是面向对象表达 ADT 比较好,当然 c 也完全 ok,个人 c++不是很融汇贯通,高级的语法和特性不敢乱用,免得班门弄斧,贻笑大方……不使用高级特性和复杂语法的话,cpp就索然无味,索性一直用 纯真的 c 来练习一些东西,因为这样更好的把重点放到算法和工程问题本身,而不是复杂庞大的 c++语言的枝端末节上,c++还是比 c 多了很多复杂繁琐的东西(这里又想到了一道奇葩的问题——如何用 c 实现面向对象?)

google 的这个问题充分暴露了本人c++功底不过关, c++求解实现的过程因为使用的是类模版,函数模版,继承,和输入输出等的运算符重载,导致程序代码较多,且出现了很多错误,考虑编码练习和算法设计,重点学习的是思想和方法,还是用c写,c++的巩固和加强完全可以放到其他闲散时间完成.

这个题的难点其实是最后一问!

因为既然是google的题,肯丢最后要考虑时间复杂度和优化问题。只有结果肯丢过不了关,这里先提供一个最直接的思路。也是比较费时间的。o(n*m)

//直接思考,多项式相乘,每一项一一顺次(考虑两个嵌套循环)的去做乘法,指数相加,系数相乘,保存到临时变量,又考虑到有多项,自然是数组保存了……

//这是正常的很直观的思路,可以依靠数组的下标去映射指数,把系数存到对应指数(下标)处,这里是累加的和。

然后再依靠一个循环,顺次去判断数组的内容,取出想要的系数和对应下标(就是指数),组成一个新项,那就ok了。
//最后的阶数必然是两式子最高阶之和,自然这个数组的长度=这个和+1

 /************************************************************************/
// 头文件Polynomial.h
// 定义链表结构
/************************************************************************/
#ifndef POLYNOMIAL_H
#define POLYNOMIAL_H
#include <stdlib.h>
#include <stdio.h>
#include <float.h> //链表结构
typedef struct Node{
struct Node *next;
double coefficient;
int exponent;
} Node, *Polynomial; //链表初始化
void initList(Polynomial *L)
{
//头结点
if (NULL == *L)
{
*L = (Polynomial)malloc(sizeof(Node));
(*L)->coefficient = 0.0;
(*L)->exponent = -;
(*L)->next = NULL;
}
else
{
puts("表已经存在!");
}
} //判断指数同否
int compareExponent(Polynomial nodeA, Polynomial nodeB)
{
int a = nodeA->exponent;
int b = nodeB->exponent; if (a == b)
{
return ;
}
else
{
return a > b ? : -;
}
} //系数判断
bool isZeroByCoefficient(Polynomial node)
{
if (node->coefficient >= -LDBL_EPSILON && node->coefficient <= LDBL_EPSILON)
{
return true;
}
else
{
return false;
}
} //判断2
//系数判断
bool isZeroByDouble(double a)
{
if (a >= -LDBL_EPSILON && a <= LDBL_EPSILON)
{
return true;
}
else
{
return false;
}
} //尾插法建表
void creatListByTail(Polynomial *L, int n)
{
//头结点
if (NULL == *L)
{
*L = (Polynomial)malloc(sizeof(Node));
(*L)->coefficient = 0.0;
(*L)->exponent = -;
(*L)->next = NULL;
Polynomial tail = NULL;
Polynomial ptr = *L;
//初始化?
if (NULL == (*L)->next)
{
puts("请按照指数升幂,连续的输入项的系数(double)和指数(int):(中间空格隔开)");
//循环建表
for (int i = ; i < n; i++)
{
tail = (Polynomial)malloc(sizeof(Node));
tail->next = NULL;
scanf("%lf %d", &tail->coefficient, &tail->exponent); while (getchar() != '\n')
{
continue;
}
//链接
ptr->next = tail;
//移动指针
ptr = ptr->next;
//尾结点
}
}
else
{
puts("表已经建立!");
}
}
else
{
puts("表头已经存在!");
}
} //遍历
void traverseList(Polynomial L)
{
Polynomial ptr = L->next;
int i = ; while (ptr != NULL)
{ printf("一元多项式的第%d项:%g X ^ %d\n", i, ptr->coefficient, ptr->exponent);
i++;
ptr = ptr->next;
}
} //求最高阶数
int getMaxExp(Polynomial L)
{
Polynomial ptr = L; while (ptr->next != NULL)
{
ptr = ptr->next;
} return ptr->exponent;
} //删除结点,删除L中ptr指向的结点
void deleteNode(Polynomial L, Polynomial ptr)
{
Polynomial p = L; while (p->next != ptr)
{
p = p->next;
} ptr = p->next;
p->next->next = ptr->next;
free(ptr);
ptr = NULL;
} //多项式相加,本质是链表的归并算法
//可以另外开辟空间,也可以使用已存在的空间存储,这里使用后者的算法
void addPolynomial(Polynomial LA, Polynomial LB)
{
//不再开辟内存
Polynomial a = LA->next;
Polynomial b = LB->next;
Polynomial LC = LB;
Polynomial tail = LC; while (a != NULL && b != NULL)
{
//判断指数的关系 a > b ? 1 : -1 else 0
switch (compareExponent(a, b))
{
case :
tail->next = b;
tail = tail->next;
b = b->next;
break; case -:
tail->next = a;
tail = tail->next;
a = a->next;
break; default:
double temp = a->coefficient + b->coefficient;
// 0?
if (isZeroByDouble(temp))
{
a = a->next;
b = b->next;
//删除
deleteNode(LC, tail->next);
}
else
{
tail->next = b;
tail = tail->next;
b->coefficient = temp;
a = a->next;
b = b->next;
}// end of if
}// end of switch
}//end of while
//一表比完
if (NULL == a)
{
tail->next = b;
}
else
{
tail->next = a;
}// end of if free(LA);
LA = NULL;
} //多项式相乘
void mulPolynomial(Polynomial LA, Polynomial LB, Polynomial LC)
{
Polynomial a = LA->next;
Polynomial b = LB->next;
Polynomial c = LC;
Polynomial ptr = NULL;
//两多项式的阶数
int numA = getMaxExp(LA);
int numB = getMaxExp(LB);
//结果多项式的阶数
int maxNum = numA + numB;
//动态开辟数组空间
double *receive = (double *)malloc((maxNum + ) * sizeof(double));
//为数组赋值
for (int i = ; i < maxNum + ; i++)
{
//i相当于指数,数组值就是相应指数的系数
receive[i] = 0.0;
}
//指数及数组下标
int expByIndex = ;
//顺次扫描A
while (a != NULL)
{
//A不空,顺次扫描B
while (b != NULL)
{
//两项做乘法之后的指数和
expByIndex = a->exponent + b->exponent;
//系数之间做乘,结果保存到对应的指数下(下标),
receive[expByIndex] += (a->coefficient) * (b->coefficient);
b = b->next;
} b = LB->next;
a = a->next;
}// end of while
//数组保存的是全部项,两两分别乘法之后的结果,保存在对应的下标(数组位置)
for (int i = ; i < maxNum + ; i++)
{
// 0?
if (isZeroByDouble(receive[i]))
{
//not do sth
}
else
{
//生成结点
ptr = (Polynomial)malloc(sizeof(Node));
//接到 LC 表
c->next = ptr;
c = c->next;
//赋值
c->coefficient =receive[i];
c->exponent = i;
}// end of if
}// end of for c->next = NULL;
} //链表销毁
void destroyList(Polynomial *L)
{
Polynomial ptr = NULL; while (*L != NULL)
{
ptr = (*L)->next;
free(*L);
*L = ptr;
}
//
*L = NULL;
puts("销毁完毕");
}
#endif

注意:

1、

//数组维数在c99之前必须是常量,c99之后可以是变长数组,但是很多编译器还不支持。

//double receive[maxNum + 1] = {0};目前来说error!

2、

最后销毁的时候销毁B就行了,因为把A插到B,B就是C,C就是B,A只剩下头结点,在相加函数里,早已经被删除!如果还销毁B,铁定报错!重复析构。

3、

多项式相乘(其实就是两个链表的合并问题),这里有两个方法比较常见:

最简单也是最费时间(时间复杂度 o(n*m)最高的实现方法)的直接相乘法:

其实很简单,把表 A 的每一项系数分别和表 B 的每一项系数做乘法,同时,把他们的指数相加,存储到临时数组里,这样得到 N(A)x N(B)个新的项,按照指数相同的,把他们的系数相加组合为一新的项,附带这个指数,输出,得结果。

比较经典的是分治法。

 #include "Polynomial.h"

 int main(void)
{
puts("第一波计算加法:初始化表A,B");
Polynomial LA = NULL;
Polynomial LB = NULL; puts("建表A");
creatListByTail(&LA, );
puts("打印A");
traverseList(LA);
puts("建立表B");
creatListByTail(&LB, );
puts("打印表B");
traverseList(LB);
//相加
puts("表A,B相加");
addPolynomial(LA, LB);
puts("打印和");
traverseList(LB);
//销毁B即可
puts("销毁表A,B");
destroyList(&LB);

 puts("第二波计算乘法:初始化表A,B,C");
Polynomial LAX = NULL;
Polynomial LBX = NULL;
Polynomial LCX = NULL;
initList(&LCX); puts("建表A,B");
creatListByTail(&LAX, );
puts("打印表A");
traverseList(LAX);
puts("建立表B");
creatListByTail(&LBX, );
puts("打印表B");
traverseList(LBX);
//相乘
puts("表A,B做乘法");
mulPolynomial(LAX, LBX, LCX);
puts("打印结果");
traverseList(LCX);
//销毁
puts("销毁表ABC");
destroyList(&LAX);
destroyList(&LBX);
destroyList(&LCX); system("pause");
return ;
}

还有一种改进的快速的傅里叶变换算法实现(未完待续)

欢迎关注

dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!

一道 google曾出过的笔试题:编程实现对数学一元多项式的相加和相乘操作(1)的更多相关文章

  1. 【c++】一道关于继承和析构的笔试题

    题目如下,求输出结果 class A { public: A() { cout<<"A"<<endl; } ~A() { cout<<" ...

  2. 一道关于C++ 继承/虚函数 笔试题 [转]

    转自:http://www.cnblogs.com/yangyh/archive/2011/06/04/2072393.html 首先这位作者, 因为看了这篇简短的一个博文, 我相同了关于虚函数方面的 ...

  3. 2016年4月21百度iOS实习生在线笔试题&编程题

    1.一个人上台阶可以一次上1个,2个,或者3个,问这个人上32层的台阶,总共有几种走法? 思路:先建立数学模型,设3步的走 i 次,2步的走 j 次, 1步的走 k 次,上了3*i + 2*j + 1 ...

  4. XX公司在线笔试题编程题之一

    题目: #include <iostream> #include <vector> #include <string> #include <list> ...

  5. 【Python】【面试必看】Python笔试题

    前言 现在面试测试岗位,一般会要求熟悉一门语言(python/java),为了考验求职者的基本功,一般会出 2 个笔试题,这些题目一般不难,主要考察基本功.要是给你一台电脑,在编辑器里面边写边调试,没 ...

  6. 关于面试总结4-python笔试题

    前言 现在面试测试岗位,一般会要求熟悉一门语言(python/java),为了考验求职者的基本功,一般会出2个笔试题,这些题目一般不难,主要考察基本功. 要是给你一台电脑,在编辑器里面边写边调试,没多 ...

  7. 关于面试总结-python笔试题

    关于面试总结4-python笔试题 前言 现在面试测试岗位,一般会要求熟悉一门语言(python/java),为了考验求职者的基本功,一般会出2个笔试题,这些题目一般不难,主要考察基本功. 要是给你一 ...

  8. 一道简单的 Java 笔试题,但值得很多人反思!

    前言 面试别人,对我来说是一件新奇事,以前都是别人面试我.我清楚地知道,我在的地域与公司,难以吸引到中国的一流软件人才.所以,我特地调低了期望,很少问什么深入的技术问题,只问一些广泛的.基础的.我只要 ...

  9. 【Android】一道Android OpenGL笔试题

    一道Android OpenGL笔试题 SkySeraph May. 5th 2016 Email:skyseraph00@163.com 更多精彩请直接访问SkySeraph个人站点:www.sky ...

随机推荐

  1. 状态开关按钮(ToggleButton)和开关(Switch)

    ToggleButton支持的XML属性及相关方法1.android:checked----->setChecked(boolean) ----->设置该按钮是否被选中2.android: ...

  2. 如何为Eclipse安装主题(Color Theme)

    Eclipse开发环境默认都是白底黑字的,看到同事的Xcode中设置的黑灰色背景挺好看的,就去网上查了一下.发现Eclipse也可以设置主题. 方法1:你可以从Eclipse Marketplace中 ...

  3. vsftp搭建+虚拟用户

    yum安装vsfpd: [root@localhost ~]# yum -y install vsftpd db4-utils Loaded plugins: fastestmirror, refre ...

  4. 工作当中实际运用(2)——js原生实现全选/反选

    老规矩 直接上代码  代码中详细注释: function checkAll(){ var alls=document.getElementById('tab-stp').getElementsByTa ...

  5. DBCC 命令2

    状态查询:收集和显示各类信息,状态检查. 如cachestats.pss.sqlmgrstats.memorystatus.proccache.freeproccache.freesystemcach ...

  6. CSS 禁止浏览器滚动条的方法(转)

    1.完全隐藏 在<boby>里加入scroll="no",可隐藏滚动条: <boby scroll="no"> 这个我用的时候完全没效果 ...

  7. 【视频教程】使用UIAutomation开发软件外挂

    UIAutomation是.Net 3.5之后提供的“界面自动化测试”技术,本来是给测试人员用的,不过UIAutomation由于也是界面自动操作的技术,比直接使用keybd_event.GetWin ...

  8. Web服务器常用设置

    1.Tomcat浏览目录 找到安装目录下的文件/conf/web.xml,  找到以下配置节,将parame-value设置为true即可 <init-param>             ...

  9. Redhat Linux /etc/profile 与 /etc/bashrc 的区别

    最近学习RHCE,在umask这里,书里说要修改/etc/profile和/etc/bashrc两个文件,却没有说明这两个区别.于是在上网查看之后倒是明白了各是怎么用的./etc/profile是对应 ...

  10. DeviceOne 竟然做出来如此复杂的App

    分享来自DeviceOne论坛 此文章分享自DeviceOne的论坛,论坛上的用户分享,下面有二维码扫描,可以进行安装和试用.支持android和ios两个 操作系统. 这个还没做完,目前已经完成了初 ...