數學中一元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電子書,資料,幫忙內推,歡迎拍磚!