一道 google曾出過的筆試題:編程實現對數學一元多項式的相加和相乘操作(1)


數學中一元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

  1 /************************************************************************/
  2 // 頭文件Polynomial.h
  3 // 定義鏈表結構
  4 /************************************************************************/
  5 #ifndef POLYNOMIAL_H
  6 #define POLYNOMIAL_H
  7 #include <stdlib.h>
  8 #include <stdio.h>
  9 #include <float.h>
 10 
 11 //鏈表結構
 12 typedef struct Node{
 13     struct Node *next;
 14     double coefficient;
 15     int exponent;
 16 } Node, *Polynomial;
 17 
 18 //鏈表初始化
 19 void initList(Polynomial *L)
 20 {
 21     //頭結點
 22     if (NULL == *L)
 23     {
 24         *L = (Polynomial)malloc(sizeof(Node));
 25         (*L)->coefficient = 0.0;
 26         (*L)->exponent = -1;
 27         (*L)->next = NULL;
 28     }
 29     else
 30     {
 31         puts("表已經存在!");
 32     }
 33 }
 34 
 35 //判斷指數同否
 36 int compareExponent(Polynomial nodeA, Polynomial nodeB)
 37 {
 38     int a = nodeA->exponent;
 39     int b = nodeB->exponent;
 40 
 41     if (a == b)
 42     {
 43         return 0;
 44     }
 45     else
 46     {
 47         return a > b ? 1 : -1;
 48     }
 49 }
 50 
 51 //系數判斷
 52 bool isZeroByCoefficient(Polynomial node)
 53 {
 54     if (node->coefficient >= -LDBL_EPSILON && node->coefficient <= LDBL_EPSILON)
 55     {
 56         return true;
 57     } 
 58     else
 59     {
 60         return false;
 61     }
 62 }
 63 
 64 //判斷2
 65 //系數判斷
 66 bool isZeroByDouble(double a)
 67 {
 68     if (a >= -LDBL_EPSILON && a <= LDBL_EPSILON)
 69     {
 70         return true;
 71     } 
 72     else
 73     {
 74         return false;
 75     }
 76 }
 77 
 78 //尾插法建表
 79 void creatListByTail(Polynomial *L, int n)
 80 {
 81     //頭結點
 82     if (NULL == *L)
 83     {
 84         *L = (Polynomial)malloc(sizeof(Node));
 85         (*L)->coefficient = 0.0;
 86         (*L)->exponent = -1;
 87         (*L)->next = NULL;
 88         Polynomial tail = NULL;
 89         Polynomial ptr = *L;
 90         //初始化?
 91         if (NULL == (*L)->next)
 92         {
 93             puts("請按照指數升冪,連續的輸入項的系數(double)和指數(int):(中間空格隔開)");
 94             //循環建表
 95             for (int i = 0; i < n; i++)
 96             {
 97                 tail = (Polynomial)malloc(sizeof(Node));
 98                 tail->next = NULL;
 99                 scanf("%lf %d", &tail->coefficient, &tail->exponent);
100 
101                 while (getchar() != '\n')
102                 {
103                     continue;
104                 }
105                 //鏈接
106                 ptr->next = tail;
107                 //移動指針
108                 ptr = ptr->next;
109                 //尾結點
110             }
111         }
112         else
113         {
114             puts("表已經建立!");
115         }
116     }
117     else
118     {
119         puts("表頭已經存在!");
120     }
121 }
122 
123 //遍歷
124 void traverseList(Polynomial L)
125 {
126     Polynomial ptr = L->next;
127     int i = 1;
128 
129     while (ptr != NULL)
130     {
131 
132         printf("一元多項式的第%d項:%g X ^ %d\n", i, ptr->coefficient, ptr->exponent);
133         i++;
134         ptr = ptr->next;
135     }
136 }
137 
138 //求最高階數
139 int getMaxExp(Polynomial L)
140 {
141     Polynomial ptr = L;
142 
143     while (ptr->next != NULL)
144     {
145         ptr = ptr->next;
146     }
147 
148     return ptr->exponent;
149 }
150 
151 //刪除結點,刪除L中ptr指向的結點
152 void deleteNode(Polynomial L, Polynomial ptr)
153 {
154     Polynomial p = L;
155 
156     while (p->next != ptr)
157     {
158         p = p->next;
159     }
160 
161     ptr = p->next;
162     p->next->next = ptr->next;
163     free(ptr);
164     ptr = NULL;
165 }
166 
167 //多項式相加,本質是鏈表的歸並算法
168 //可以另外開辟空間,也可以使用已存在的空間存儲,這里使用后者的算法
169 void addPolynomial(Polynomial LA, Polynomial LB)
170 {
171     //不再開辟內存
172     Polynomial a = LA->next;
173     Polynomial b = LB->next;
174     Polynomial LC = LB;
175     Polynomial tail = LC;
176 
177     while (a != NULL && b != NULL)
178     {
179         //判斷指數的關系 a > b ? 1 : -1  else 0
180         switch (compareExponent(a, b))
181         {
182         case 1:
183             tail->next = b;
184             tail = tail->next;
185             b = b->next;
186             break;
187 
188         case -1:
189             tail->next = a;
190             tail = tail->next;
191             a = a->next;
192             break;
193 
194         default:
195             double temp = a->coefficient + b->coefficient;
196             // 0?
197             if (isZeroByDouble(temp))
198             {
199                 a = a->next;
200                 b = b->next;
201                 //刪除
202                 deleteNode(LC, tail->next);
203             }
204             else
205             {
206                 tail->next = b;
207                 tail = tail->next;
208                 b->coefficient = temp;
209                 a = a->next;
210                 b = b->next;
211             }// end of if
212         }// end of switch
213     }//end of while
214     //一表比完
215     if (NULL == a)
216     {
217         tail->next = b;
218     }
219     else
220     {
221         tail->next = a;
222     }// end of if
223 
224     free(LA);
225     LA = NULL;
226 }
227 
228 //多項式相乘
229 void mulPolynomial(Polynomial LA, Polynomial LB, Polynomial LC)
230 {
231     Polynomial a = LA->next;
232     Polynomial b = LB->next;
233     Polynomial c = LC;
234     Polynomial ptr = NULL;
235     //兩多項式的階數
236     int numA = getMaxExp(LA);
237     int numB = getMaxExp(LB);
238     //結果多項式的階數
239     int maxNum = numA + numB;
240      //動態開辟數組空間
241      double *receive = (double *)malloc((maxNum + 1) * sizeof(double));
242      //為數組賦值
243      for (int i = 0; i < maxNum + 1; i++)
244      {
245          //i相當於指數,數組值就是相應指數的系數
246          receive[i] = 0.0;
247      }
248     //指數及數組下標
249     int expByIndex = 0;
250     //順次掃描A
251     while (a != NULL)
252     {
253         //A不空,順次掃描B
254         while (b != NULL)
255         {
256             //兩項做乘法之后的指數和
257             expByIndex = a->exponent + b->exponent;
258             //系數之間做乘,結果保存到對應的指數下(下標),
259             receive[expByIndex] += (a->coefficient) * (b->coefficient);
260             b = b->next;
261         }
262 
263         b = LB->next;
264         a = a->next;
265     }// end of while
266     //數組保存的是全部項,兩兩分別乘法之后的結果,保存在對應的下標(數組位置)
267     for (int i = 0; i < maxNum + 1; i++)
268     {
269         // 0?
270         if (isZeroByDouble(receive[i]))
271         {
272             //not do sth
273         }
274         else
275         {
276             //生成結點
277             ptr = (Polynomial)malloc(sizeof(Node));
278             //接到 LC 表
279             c->next = ptr;
280             c = c->next;
281             //賦值
282             c->coefficient =receive[i];
283             c->exponent = i;
284         }// end of if
285     }// end of for
286 
287     c->next = NULL;
288 }
289 
290 //鏈表銷毀
291 void destroyList(Polynomial *L)
292 {
293     Polynomial ptr = NULL;
294 
295     while (*L != NULL)
296     {
297         ptr = (*L)->next;
298         free(*L);
299         *L = ptr;
300     }
301     //
302     *L = NULL;
303     puts("銷毀完畢");
304 }
305 #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)個新的項,按照指數相同的,把他們的系數相加組合為一新的項,附帶這個指數,輸出,得結果。

比較經典的是分治法。

 1 #include "Polynomial.h"
 2 
 3 int main(void)
 4 {
 5     puts("第一波計算加法:初始化表A,B");
 6     Polynomial LA = NULL;
 7     Polynomial LB = NULL;
 8 
 9     puts("建表A");
10     creatListByTail(&LA, 4);
11     puts("打印A");
12     traverseList(LA);
13     puts("建立表B");
14     creatListByTail(&LB, 3);
15     puts("打印表B");
16     traverseList(LB);
17     //相加
18     puts("表A,B相加");
19     addPolynomial(LA, LB);
20     puts("打印和");
21     traverseList(LB);
22     //銷毀B即可
23     puts("銷毀表A,B");
24     destroyList(&LB);

 1 puts("第二波計算乘法:初始化表A,B,C");
 2     Polynomial LAX = NULL;
 3     Polynomial LBX = NULL;
 4     Polynomial LCX = NULL;
 5     initList(&LCX);
 6 
 7     puts("建表A,B");
 8     creatListByTail(&LAX, 4);
 9     puts("打印表A");
10     traverseList(LAX);
11     puts("建立表B");
12     creatListByTail(&LBX, 3);
13     puts("打印表B");
14     traverseList(LBX);
15     //相乘
16     puts("表A,B做乘法");
17     mulPolynomial(LAX, LBX, LCX);
18     puts("打印結果");
19     traverseList(LCX);
20     //銷毀
21     puts("銷毀表ABC");
22     destroyList(&LAX);
23     destroyList(&LBX);
24     destroyList(&LCX);
25 
26     system("pause");
27     return 0;
28 }

 

還有一種改進的快速的傅里葉變換算法實現(未完待續)

 

歡迎關注

dashuai的博客是終身學習踐行者,大廠程序員,且專注於工作經驗、學習筆記的分享和日常吐槽,包括但不限於互聯網行業,附帶分享一些PDF電子書,資料,幫忙內推,歡迎拍磚!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM