最近遇到这样一个题目,使用链表来实现两个多项式的加法,刚开始觉得应该比较简单,也可能是自己基础不扎实吧,这其中也是踩了很多的坑啊,最终还是成功了,特此写博客记录一下。
一、题目要求
使用链表来实现两个多项式的加法,输出最终相加后的多项式。多项式默认是按指数(幂次)依次递增的,输入时会依次输入每一项的系数和指数,两个多项式之间的数据以连续两个0来结束一个多项式的输入,最终输出两个多项式相加的多项式结果。例如:
多项式A: X^1 + 2 * X^2 + 3 * X^3
多项式B: X^1 + 2 * X^2 + 3 * X^3
输入:
1 1
2 2
3 3
0 0
1 1
2 2
3 3
0 0
输出:
2 * X^1 + 4 * X^2 + 6 * X^3
上面便是题目的要求,下面我们开始正式分析。
二、题目分析
我们先要解决的问题是多项式的存储问题,即它在计算机中的表现形式,很明显我们只需要存储了多项式的系数和指数即可以开始后面加法计算。这里我们又两种存储方式,一种是线性存储,另一种是链式存储。
线性存储,比如我们采用数组来存储这些数据,理论来说是可以的,但是由于我们的多项式的项数是不确定的,但数组必须在刚开始就必须
先给定一个初始的大小,这个初始大小我们不好确定,分配少了,没法存储全部多项式,分配多了,会造成空间浪费,所以我们在这里采用链表来存储多项式,每次输入一个多项式节点,我们就分配一个链表的节点,这样便可以合理的节约空间。
接着我们分析的是具体的实现步骤,我在这里将其分为三步:
第一步:
接收用户控制台的输入,并且生成对应的两个多项式的链表表示。
我们的每个链表节点有三个域,前两个域是数据域,依次表示多项式的系数和指数,第三个域是指针域,指向下一个节点
这里我们为了方便后续处理,给每个连边增加一个头结点,头结点的数据域内都放-1,没有实际意义。如下图所示:
第二步:
依次对两个链表中的数据进行加法处理
这一步是我们的关键步骤,下面将以图文结合的方式来进行解释:
如图中所示,我们准备了A、B两个链表
链表A表示的多项式: 7 * X^1 + 2 * X^2 + 8 * X^5
链表B表示的多项式: 2 * X^1 + (-2 * X^2) + 2 * X^3
链表C是我们最终的和链表
这里我们按照这样的步骤进行处理:
1 . 我们规定三个头指针,分别指向三个链表的头,然后再规定三个移动指针,分别指向当前三个链表中正在处理的那个节点
2 . 我们让A、B、C的移动指针刚开始也处于头指针的位置,然后,我们拿A第一个节点中的指数和B第一个节点中的指数进行比较,这个时候有三种情况:
a情况 . A中当前的节点指数 < B中当前的节点指数 ------ 我们将A中的当前节点插入C中,然后向后移动A和C的指针(因为A中当前节点已经处理了)
b情况 . A中当前的节点指数 > B中当前的节点指数 ------ 我们将B中的当前节点插入C中,然后向后移动B和C的指针(因为B中当前节点已经处理了) 即图中⑧的情况。
c情况 . A中当前的节点指数 > B中当前的节点指数 ------ 此时A和B当前节点指数相同,可以进行系数相加,这时候也会出现两种情况:
情况1 . 系数之和不为0 ------ 我们此时将系数之和放到A中的当前节点的系数域,然后将A中的该节点插入C中,然后向后移动C的指针(记住,我们这里不是产生一个新的节点,而是直接更改A的系数域,然后将A的当前节点插入C中),即图中的①和②产生③的过程。
情况2 . 系数之和为0 ------ 此时我们不能将系数和为0的项放入链表C中,理论来说我们什么都不用做,但是这里有一个小问题,因为按照情况1来看,我们在系数和不为0时是将A节点直接插到C中,我们假设我们在系数和为0后什么都不做,继续处理A中后续节点,后面遇到一个系数和不为0的情况,我们将后面遇到的这个系数不为0的节点插入C中,那其实也将前面那个系数为0的项也一并插入C中了,以为前面那个系数为0的节点和其他后面的节点一直保持联系。所以我们此时必须在系数和为0时,将A中的当前节点删除了。即图中的④和⑤产生⑥的过程。
无论上面是情况1还是情况2,总之我们都同时处理了节点A和节点B,所以,我们还需要同时将节点A和B的移动指针向后移动。
这里还有一个情况,我们的A、B链表可能长度不是一致的,那么就有可能其中一个链表的移动指针已经移动到了末尾,那么此时,我们就不需要继续移动了,我们只需要将另一个链表中未处理的数据直接接在当前已经生产的C链表的后面即可。
第三步:
打印输出最终计算所得的和链表表达式
三、代码实现
经过上面的分析,我们这里分别采用C语言和Java来实现上述思路,代码中有详细的注释,下面只进行简单解释。
C语言实现:
#include<stdio.h>
#include<malloc.h>
typedef struct node{
float coef;
int expn;
node *next;
}Lnode, * Dxs;
Dxs create();
Dxs add_dxs(Dxs firsta, Dxs firstb);
void printDxs(Dxs h);
void deleteNode(Dxs h, Dxs p);
int main(){
Dxs ha, hb, hc;
printf("请依次输入第一个多项式的系数和指数\n");
ha = create();
printf("请依次输入第二个多项式的系数和指数\n");
hb = create();
printf("输入的第一个多项式是: ");
printDxs(ha->next);
printf("输入的第二个多项式是: ");
printDxs(hb->next);
hc = add_dxs(ha, hb);
printf("两个多项式的和为: ");
printDxs(hc->next);
return 0;
}
//创建链表(读入数据以 0 0 结束)
Dxs create(){
float coef;
int expn;
Dxs first, qa, s;
first = (Dxs)malloc(sizeof(Lnode));
first->coef = -1;
first->expn = -1;
first->next = NULL;
qa = first;
while(1){
scanf("%f", &coef);
scanf("%d", &expn);
if(coef == 0 && expn == 0){
break;
}
s = (Dxs)malloc(sizeof(Lnode));
s->coef = coef;
s->expn = expn;
s->next = NULL;
qa->next = s;
qa = s;
}
return first;
}
//链表相加
Dxs add_dxs(Dxs firsta, Dxs firstb){
Dxs firstc, ha, hb, pc, s;
int a, b;
float sum;
firstc = (Dxs)malloc(sizeof(Lnode));
firstc->coef = -1;
firstc->expn = -1;
firstc->next = NULL;
pc = firstc;
ha = firsta->next;
hb = firstb->next;
while(ha!= NULL && hb != NULL){
a = ha->expn;
b = hb->expn;
if(a < b){
//将a加入c中,移动a和c的指针
pc->next = ha;
pc = pc->next;
ha = ha->next;
}else if(a > b){
//将b加入c中,移动b和c的指针
pc->next = hb;
pc = pc->next;
hb = hb->next;
}else{
sum = ha->coef + hb->coef;
if(sum != 0.0){
//将和加入a中,再将a加入c中,移动c的指针
ha->coef = sum;
pc->next = ha;
pc = pc->next;
}else{
//查找删除A中系数之和为0的那个节点
s = firsta;
while(s != ha){
s = s->next;
}
s->next = ha->next;
}
//ab已经处理完成,同时后移一位
ha = ha->next;
hb = hb->next;
}
}
//将剩余部分加入c后面
if(ha != NULL){
pc->next = ha;
}
if(hb != NULL){
pc->next = hb;
}
return firstc;
}
//遍历显示链表
void printDxs(Dxs h){
while(h != NULL){
printf("%0.2f*X^%d + ", h->coef, h->expn);
h=h->next;
}
printf("\n");
}
Java语言描述:
package cn.codekong;
import java.util.Scanner;
/**
* 链表类
* @author szh
*
*/
public class MyLink {
/**
* 链表节点类
* @author szh
*
*/
class Node{
//多项式的系数
private float coef;
//多项式的指数
private int expn;
//指向下级节点
public Node next = null;
public Node(float coef, int expn){
this.coef = coef;
this.expn = expn;
}
}
/**
* 创建链表类
* 从从控制台不断读入数据,以(0 0)结束
* @return 创建好链表的第一个节点
*/
public Node createLink(){
//存取从控制台读到的系数和指数
float coef = 0.0f;
int expn = 0;
//头尾节点(尾节点方便插入)
Node head, tail;
head= new Node(-1, -1);
head.next = null;
tail = head;
Scanner scanner = new Scanner(System.in);
while(true){
String res = scanner.nextLine();
//以空格分割一行中的字符串
String[] resArray = res.split("\\s+");
coef = Float.parseFloat(resArray[0]);
expn = Integer.parseInt(resArray[1]);
if(coef == 0 && expn == 0.0f){
break;
}
Node node = new Node(coef, expn);
node.next = null;
tail.next = node;
tail = tail.next;
}
return head;
}
/**
* 打印链表
* @param head 链表首节点
*/
public void printLink(Node head){
while(head != null){
System.out.format("%.2f*X^%d + ", head.coef, head.expn);
head = head.next;
}
System.out.println();
}
/**
* 计算两个链表的和
* @param nodeA
* @param nodeB
* @return 最终和的链表
*/
public Node addLink(Node nodeA, Node nodeB){
Node nodeC = new Node(-1, -1);
nodeC.next = null;
//始终指向链表的当前需要处理的节点(刚开始要除去开头的(-1,-1)节点)
Node pA = nodeA.next, pB = nodeB.next, pC = nodeC;
//当前指向的两个链表的指数
int valueAExpn = 0, valueBExpn = 0;
while(pA != null && pB != null){
valueAExpn = pA.expn;
valueBExpn = pB.expn;
if(valueAExpn < valueBExpn){
//将A中的该节点加入C中, 同时移动A和C的指针指向下个元素
pC.next = pA;
pC = pC.next;
pA = pA.next;
}else if(valueAExpn > valueBExpn){
//将B中的该节点加入C中,同时移动B和C的指针指向下个元素
pC.next = pB;
pC = pC.next;
pB = pB.next;
}else{
//两节点指数相同
//现将系数相加放到A节点的系数中,然后将A节点加入C中
float sum = pA.coef + pB.coef;
if(sum != 0.0f){
//系数和不为0,将A节点的系数改变为sum,然后将A节点加入C中
pA.coef = sum;
pC.next = pA;
pC = pC.next;
}else{
//系数和为0,必须将A链表中的该节点从链表中删除掉,如果只移动指针,会在输出时也输出该项
//在整个A链表中依次查找,必须找到要删除节点的前驱节点才能将其删除
Node s = nodeA;
while(s != pA){
s = s.next;
}
//删除该节点
s.next = pA.next;
}
//对于系数相同的情况,A和B节点指针都往后移动
pA = pA.next;
pB = pB.next;
}
}
if(pA != null){
pC.next = pA;
}
if(pB != null){
pC.next = pB;
}
return nodeC;
}
}
package cn.codekong;
import cn.codekong.MyLink.Node;
public class MyTest {
public static void main(String[] args) {
MyLink myLink = new MyLink();
System.out.println("请依次输入第一个多项式的系数和指数");
Node nodea = myLink.createLink();
System.out.println("请依次输入第二个多项式的系数和指数");
Node nodeb = myLink.createLink();
System.out.println("输入的第一个多项式是: ");
myLink.printLink(nodea.next);
System.out.println("输入的第二个多项式是: ");
myLink.printLink(nodeb.next);
Node nodec = myLink.addLink(nodea, nodeb);
System.out.println("两个多项式的和为: ");
myLink.printLink(nodec.next);
}
}
其实Java的链表实现和C语言很类似,只是C语言中的节点是定义结构体,而此处是使用一个内部类来定义链表的每个节点,C语言中的指针其实就是Java中的引用,我们生命的Java对象是存储在堆中,而对象的引用则是存储在堆栈中。
上面为了保持Java和C在控制台输入的一致性,我通过使用Scanner读入一行,然后通过正则表达式分割出系数和指数进行后续处理。
四、代码运行验证
C语言运行结果:
Java运行结果: