小肥柴慢慢手写数据结构(C篇)(2-7 多项式)
目录
2-26 多项式数据结构分析
- 多项式形式
p ( n ) = a n x n + a n − 1 x n − 1 + . . . + a 2 x 2 + a 1 x + a 0 {p(n)}={a_n}x^{n}+{a_n-1}x^{n-1}+...+{a_2}x^{2}+{a_1}x+{a_0} p(n)=anxn+an−1xn−1+...+a2x2+a1x+a0
可以观察到:
(1)多项式建模的关键是每项前面的系数 a k {a_k} ak级对应的次幂 k k k;
(2)从最简单的情况考虑,设 k k k是整数,且系数 a k {a_k} ak可能为0。 - 数组or链表
如果采用二元数组来存储多项式,必然容易表达和计算;但如果某些项系数为0,则会留下很多间隔,浪费空间==>结论显而易见:链表非常适用于这个场景。
2-27 解决问题的自强方案
其实《黑皮书》3.2.7 P39给出了一种实现参考,但这次咱就自己独立撸一把!
2-27-1 设计思路
- 创建如下结构,使用带虚拟头结点的链表。
typedef int keyType;
typedef int valTyep;
typedef struct Node{
keyType exp;
valTyep val;
struct Node *next;
} Node;
typedef struct Poly{
Node *header;
int length;
} Poly;
添加一个变量length,方便判断异常和
- 为了贴近使用习惯并方便后续操作,插入元素时,使用降幂排序;并设置虚拟头结点幂为INT_MAX。
- ADT:
(1)初始化/创建多项式
(2)功能强大的插入操作,包括:
a. 0系数问题
b. 相同次幂系数计算问题
c. 相同次幂系数计算后删除节点问题
d. 普通的降幂插入新节点问题
(3)多项式加法
(4)多项式乘法
其中(3)、(4)均是基于(2)快速实现的。
2-27-2 具体代码实现
- 完整头文件 poly.h
#ifndef _POLY_H
#define _POLY_H
typedef int keyType;
typedef int valTyep;
typedef struct Node{
keyType exp;
valTyep val;
struct Node *next;
} Node;
typedef struct Poly{
Node *header;
int length;
} Poly;
Poly *createPoly(); //创建一个空多项式
int insertElem(keyType exp, valTyep val, Poly *p); //插入元素
Poly *addPoly(Poly *p1, Poly *p2); //加法返回新生成的多项式
Poly *multPoly(Poly *p1, Poly *p2); //乘法返回新生成的多项式
void addPoly2(Poly *p1, Poly *p2); //p1本身存放结果
void printPoly(Poly *list); //打印多项式
#endif
- 创建空多项式,注意有虚拟头结点,该节点幂次设置为最大int值,为后续降幂插入提供方便;其实我们是可以采用双链表实现多项式的,但我认为能使用一些小技巧完成任务就没有必要使用更加复杂或者更消耗空间的方式。
Poly *createPoly(){
Poly *poly = (Poly *)malloc(sizeof(struct Poly));
if(!poly)
return NULL;
poly->header = (Node *)malloc(sizeof(struct Node));
if(!poly->header){
free(poly);
return NULL;
}
poly->header->exp = INT_MAX;
poly->header->val = 0;
poly->header->next = NULL;
poly->length = 0;
return poly;
}
- 插入元素,注意
(1)入参val为0的时候,没有必要继续插入动作;
(2)回想之前处理跳表的操作,快速找到前置节点和插入位置;
(3)如果插入元素的幂exp在多项式中已经存在,则直接累加对应次幂项的系数;
(4)如果更新系数时,系数为0,则需要删除对应项;
(5)仅当添加新项时,多项式的长度增加。
int insertElem(keyType exp, valTyep val, Poly *p){
if(val == 0)
return -1;
Node *cur = p->header, *target = NULL;
while((target = cur->next)&& target->exp > exp)
cur = target;
if(target && target->exp == exp){
target->val += val;
if(target->val == 0){
cur->next = target->next;
free(target);
}
return 1;
}
Node *newNode = (Node *)malloc(sizeof(struct Node));
if(!newNode)
return -1;
newNode->exp = exp;
newNode->val = val;
newNode->next = target;
cur->next = newNode;
p->length++;
return 0;
}
- 朴素的加法,运行机制和归并排序很像
Poly *addPoly(Poly *p1, Poly *p2){
if(!p1 || p1->length == 0 || !p2 || p2->length == 0)
return NULL;
Node *cur1 = p1->header->next;
Node *cur2 = p2->header->next;
Poly *resPoly = createPoly();
if(!resPoly)
return NULL;
while(cur1&&cur2){
if(cur1->exp == cur2->exp){
insertElem(cur1->exp, cur1->val + cur2->val, resPoly);
cur1 = cur1->next;
cur2 = cur2->next;
} else if(cur1->exp > cur2->exp){
insertElem(cur1->exp, cur1->val, resPoly);
cur1 = cur1->next;
} else {
insertElem(cur2->exp, cur2->val, resPoly);
cur2 = cur2->next;
}
}
while(cur1){
insertElem(cur1->exp, cur1->val, resPoly);
cur1 = cur1->next;
}
while(cur2){
insertElem(cur2->exp, cur2->val, resPoly);
cur2 = cur2->next;
}
return resPoly;
}
- 乘法需要注意两个多项式的项数长短,在细节上减少操作次数
Poly *multPoly(Poly *p1, Poly *p2){
if(!p1 || p1->length == 0 || !p2 || p2->length == 0)
return NULL;
return p1->length > p2->length ? doMultPoly(p1, p2) : doMultPoly(p2, p1);
}
Poly *doMultPoly(Poly *pl, Poly *ps){
Poly *resPoly = createPoly();
if(!resPoly)
return NULL;
Node *cur = ps->header->next;
while(cur){
Node *tag = pl->header->next;
while(tag){ //注意exp的计算为加法,val的计算时乘法
insertElem(cur->exp + tag->exp, cur->val * tag->val, resPoly);
tag = tag->next;
}
cur = cur->next;
}
return resPoly;
}
- 节省空间的另一种加法
void addPoly2(Poly *p1, Poly *p2){
if((p1 && p1->length > 0) && (p2 && p2->length > 0)){
Node *cur = p2->header->next;
while(cur){
insertElem(cur->exp, cur->val, p1);
cur = cur->next;
}
}
}
- 简单的打印
void printPoly(Poly *p){
Node *cur = p->header->next;
printf("\t");
while(cur){
printf("(%d, %d)->", cur->exp, cur->val);
cur = cur->next;
}
printf("end\n");
}
- 测试
#include <stdio.h>
#include <stdlib.h>
#include "poly.h"
int main(int argc, char *argv[]) {
int i;
Poly *p1 = createPoly();
for(i=0; i<10; i=i+2)
insertElem(i, i, p1);
printf("p1:");
printPoly(p1);
Poly *p2 = createPoly();
for(i=1; i<10; i=i+2)
insertElem(i, i, p2);
printf("p2:");
printPoly(p2);
Poly *p3 = createPoly();
for(i=1; i<10; i=i+2)
insertElem(i, i*2, p3);
printf("p3:");
printPoly(p3);
printf("\nadd p1, p2");
printPoly(addPoly(p1, p2));
printf("\nadd p2, p3");
printPoly(addPoly(p2, p3));
printf("\nmult p1, p2");
printPoly(multPoly(p1, p2));
printf("\nadd2 p3, p2");
addPoly2(p3, p2);
printPoly(p3);
printf("\n======= insert test==========\n");
insertElem(9, -27, p3);
printPoly(p3);
insertElem(9, -27, p3);
printPoly(p3);
insertElem(1, -3, p3);
printPoly(p3);
insertElem(1, -3, p3);
printPoly(p3);
insertElem(5, -15, p3);
printPoly(p3);
return 0;
}
2-27-3 简单的讨论
其实在很多教材中,都讨论过处理乘法的方案
即将乘法转化为加法去实现,但每个多项式的单独计算和多个多项式加法的总消耗不见得一定优于挨个插入的实现;实际上我自己也实现过一个版本的乘法转化为加法的代码,感觉有点废,不如逐项操作清爽,就不贴上来了。
另外,也可以将元素的插入改为入参为一个Node结构类型,这样更有“面对对象”的味道,移植为其他语言可能方便些。
2-28 严版教材解决方案
严版教材的参考代码和书本上仅给出了加法的简单实现,有闲心的同学,可以尝试把这张完整ADT里的所有函数都实现出来,当做对链表基础操作的复习。
2-29 浙大版数据结构课程解决方案
先上对应的学习视频(转至B站)
12_2.1.1 引子_多项式表示
24_2.4 多项式的加法运算实现
以下两个为小白专场(视频中就这样讲的)
25_25 1.理解与多项式表示
26_26 2.程序框架及读入多项式
27_27 3.加法、乘法运算及多项式输出
对应的关键代码
(1)结构类型
(2)多项式相加,在细节的处理上有自己的特点,建议看视频理解。
(3)乘法的实现,这里浙大课程学习思路就很踏实了,给出了两种解决方案
相关的代码很长,详情请参考视频,相信大家能够实现;但不得不吐槽一句:不使用虚拟头结点,用flag的方式打印,确实不太像浙大应有的水平,但考虑到这是慕课,面向全国各个层次的学校,那么还是可以接受的。
自此,有关链表的学习基本上就都到位了,可能后续还会新添到2-8,但就第一次踏踏实实学习数据结构的同学来讲,能认真理解并自己实现2-1到2-7的所有内容(自己写代码,耐心调试,不单纯的抄写),那么必然会收获不小。
下一篇,我们将讨论栈的相关知识。