每日一題13:多項式的(基於鏈表實現)簡單運算

多項式的每一項需要兩個參數,即係數與指數。描述多項式的一種方式是用數組的下標表示項的指數,而用數組存儲的元素表示相應項的係數。這樣表示的多項式看起來很簡單,但是在很多計算中卻顯得很不方便,這種不方便主要出現”在稀疏的“多項式中(比如x的10000次方加1),如果要輸出多項式卻要從頭到尾掃描數組。另一個缺點是浪費了很多的空間,上面的例子中有效的數組元素僅僅只有兩個。所以最好還是用鏈表表示,每個節點存儲一個項的係數和指數,再加上一個指向下一節點的指針。爲了以後運算方便時,在構建時就將各項按指數大小排序,如果從小到大排列,那麼當輸出多項式時就很不方便,並且這樣與數學中多項式的寫法也不一致,所以最好是從大到小的排序。

#include "stdafx.h"
#include <iostream>

using namespace std;

struct PolynomialItem
{
    float Coeff;
    int Power;
    PolynomialItem* next;
};

struct Polynomial
{
    int ItemCount;
    PolynomialItem *FirstItem;
    //加入一個lastItem指針,方便函數的編寫
    //特別是編寫O(M^2N)時間複雜度的Multi算法時
    PolynomialItem *LastItem;
};

數據結構定義好之後,再寫一些基本的函數,方便後面編寫多項式運算的代碼:

PolynomialItem* CreatePolyItem(float Coeff,int Power)
{
    if(Coeff == 0.0f) return NULL;
    PolynomialItem* PolyItem = new PolynomialItem;
    PolyItem->Coeff = Coeff;
    PolyItem->Power = Power;
    PolyItem->next = NULL;
    return PolyItem;
}

PolynomialItem* CopyPolyItem(const PolynomialItem* Item)
{
    if(Item == NULL) return NULL;
    return CreatePolyItem(Item->Coeff,Item->Power);
}

Polynomial* CreateEmptyPolynomial()
{
    Polynomial* Poly = new Polynomial;
    Poly->ItemCount = 0;
    Poly->FirstItem = NULL;
    Poly->LastItem = NULL;
    return Poly;
}

//Append表示不考慮輸入Item中Power的值,直接連接到多項式的後面
//不保證插入的節點處在正確的位置上
void AppendPolyItem(Polynomial** Poly,PolynomialItem *Item)
{
    if((*Poly)->ItemCount == 0) 
    {
        (*Poly)->FirstItem = Item;
        (*Poly)->LastItem = Item;
        ++(*Poly)->ItemCount;
        return;
    }
    Item->next = NULL;
    (*Poly)->LastItem->next = Item;
    (*Poly)->LastItem = Item;
    ++(*Poly)->ItemCount;
}

void AppendPolyItem(Polynomial** Poly,float Coeff,int Power)
{
    AppendPolyItem(Poly,CreatePolyItem(Coeff,Power));
}

//Insert會將輸入Item安插到正確的位置
void InsertPolyItem(Polynomial** Poly,PolynomialItem *Item)
{
    if((*Poly)->ItemCount == 0)
    {
        AppendPolyItem(Poly,Item);
        return;
    }
    if((*Poly)->FirstItem->Power < Item->Power)
    {
        Item->next = (*Poly)->FirstItem;
        (*Poly)->FirstItem = Item;
        ++(*Poly)->ItemCount;
        return;
    }
    PolynomialItem* p = (*Poly)->FirstItem;
    PolynomialItem* q;
    while(p)
    {
        if(p->Power > Item->Power)
        {
            q = p;
            p = p->next;
        }
        else if(p->Power == Item->Power) 
        {
            p->Coeff += Item->Coeff;
            return;
        }
        else
        {
            Item->next = q->next;
            q->next = Item;
            ++(*Poly)->ItemCount;
            return;
        }
    }
    AppendPolyItem(Poly,Item);
    return;
}

void InsertPolyItem(Polynomial** Poly,float Coeff,int Power)
{
    InsertPolyItem(Poly,CreatePolyItem(Coeff,Power));
}

Polynomial* CreatePolynomial(float Coeffs[],int Powers[],int Count)
{
    if(Count <= 0) return NULL;
    Polynomial* Poly = CreateEmptyPolynomial();
    for (int i = 0; i < Count; ++i)
    {
        InsertPolyItem(&Poly,Coeffs[i],Powers[i]);
    }
    return Poly;
}

構建數據結構的函數基本編寫完成,可以開始編寫運算函數了:

//以下三個函數只負責項與項的運算
PolynomialItem* ItemPlus(const PolynomialItem* item1,const PolynomialItem* item2)
{
    if(item1->Power != item2->Power) return NULL;
    float Coeff = item1->Coeff + item2->Coeff;
    int Power = item1->Power;
    return CreatePolyItem(Coeff,Power);
}

PolynomialItem* ItemMinus(const PolynomialItem* item1,const PolynomialItem* item2)
{
    if(item1->Power != item2->Power) return NULL;
    float Coeff = item1->Coeff - item2->Coeff;
    int Power = item1->Power;
    return CreatePolyItem(Coeff,Power);
}

PolynomialItem* ItemMulti(const PolynomialItem* item1,const PolynomialItem* item2)
{
    float Coeff = item1->Coeff * item2->Coeff;
    int Power = item1->Power + item2->Power;
    return CreatePolyItem(Coeff,Power);
}

//多項式運算函數開始
Polynomial* Plus(const Polynomial* Poly1,const Polynomial* Poly2)
{
    Polynomial* Poly = CreateEmptyPolynomial();
    PolynomialItem* p = Poly1->FirstItem,*q = Poly2->FirstItem;
    while(p && q)
    {
        PolynomialItem* newItem = NULL;
        if(p->Power == q->Power)
        {
            newItem = ItemPlus(p,q);
            p = p->next;
            q = q->next;
        }
        else if(p->Power > q->Power)
        {
            newItem = CopyPolyItem(p);
            p = p->next;
        }
        else
        {
            newItem = CopyPolyItem(q);
            q = q->next;
        }
        AppendPolyItem(&Poly,newItem);
    }
    while(p)
    {
        AppendPolyItem(&Poly,CopyPolyItem(p));
        p = p->next;
    }
    while(q)
    {
        AppendPolyItem(&Poly,CopyPolyItem(q));
        q = q->next;
    }
    return Poly;
}
Polynomial* Minus(const Polynomial* Poly1,const Polynomial* Poly2)
{
    Polynomial* Poly = CreateEmptyPolynomial();
    PolynomialItem* p = Poly1->FirstItem,*q = Poly2->FirstItem;
    while(p && q)
    {
        PolynomialItem* newItem = NULL;
        if(p->Power == q->Power)
        {
            newItem = ItemMinus(p,q);
            p = p->next;
            q = q->next;
        }
        else if(p->Power > q->Power)
        {
            newItem = CopyPolyItem(p);
            p = p->next;
        }
        else
        {
            newItem = CopyPolyItem(q);
            newItem->Coeff = -newItem->Coeff;
            q = q->next;
        }
        AppendPolyItem(&Poly,newItem);
    }
    while(p)
    {
        AppendPolyItem(&Poly,CopyPolyItem(p));
        p = p->next;
    }
    while(q)
    {
        PolynomialItem* newItem = CopyPolyItem(q);
        newItem->Coeff = -newItem->Coeff;
        AppendPolyItem(&Poly,newItem);
        q = q->next;
    }
    return Poly;
}
Polynomial* Multi(const Polynomial* Poly1,const Polynomial* Poly2)
{
    Polynomial* Poly = CreateEmptyPolynomial();
    PolynomialItem* q = Poly2->FirstItem;
    PolynomialItem* p = Poly1->FirstItem;
    while(q)
    {
        AppendPolyItem(&Poly,ItemMulti(p,q));
        q = q->next;
    }
    p = p->next;
    Polynomial* tempPoly = CreateEmptyPolynomial();
    while(p)
    {
        q = Poly2->FirstItem;
        while(q)
        {
            AppendPolyItem(&tempPoly,ItemMulti(p,q));
            q = q->next;
        }
        PolynomialItem* r = tempPoly->FirstItem;
        PolynomialItem* s = Poly->FirstItem,*t = NULL;
        while(s && s->Power >= r->Power) 
        {
            t = s;
            s = s->next;
        }
        while(r && s)
        {
            tempPoly->FirstItem = r->next;
            if(t->Power > r->Power)
            {
                r->next = t->next;
                t->next = r;
                t = r;
                ++Poly->ItemCount;
            }
            else if(t->Power == r->Power)
            {
                t->Coeff += r->Coeff;
                delete r;
            }
            r = tempPoly->FirstItem;
            --tempPoly->ItemCount;
            while(r && s && s->Power >= r->Power)
            {
                t = s;
                s = s->next;
            }
        }
        if(r)
        {
            if(r->Power == Poly->LastItem->Power)
            {
                Poly->LastItem->Coeff += r->Coeff;
                tempPoly->FirstItem = r->next;
                --tempPoly->ItemCount;
                delete r;
            }
            Poly->LastItem->next = tempPoly->FirstItem;
            Poly->LastItem = tempPoly->LastItem;
            Poly->ItemCount += tempPoly->ItemCount; 
        }
        tempPoly->FirstItem = NULL;
        tempPoly->LastItem = NULL;
        tempPoly->ItemCount = 0;
        p = p->next;
    }
    return Poly;
}

上面三個函數,加和減是比較簡單,加法中,因爲兩個多項式都是按指數排序的,所以先各從一個多項式中取出一項,比較指數的大小,只有兩個項的指數相同時才進行兩項的加法,否則只是將指數較大的一項複製到結果多項式中,然後擁有較大指數項的多項式取下一項,而擁有較小項的多項式還是講剛纔取出來的一項與另一個多項式中取出來的新項進行比較,當任何一個多項式遍歷完後,直接將另一個多項式剩餘的項複製到結果多項式中就可以了;減法與加法是一樣的,注意“減數多項式”的符號就可以了。乘法如果用簡單的方法,即就像人做多項式懲罰一樣,先計算出M*N個項來,然後再進行同內項合併或沒計算出一個新項,就將它插入到結果多項式中正確的位置,這樣編碼,比較方便,但是時間複雜度是O(M*M*N*N),其中M*N是不可避免的,重要的是如何減少另一個M*N,簡單算法中使得另一個M*N存在是因爲沒計算出一個新項,查找它的正確位置做了很多重複計算,假設用第一個多項式的I項依次去成另一個多項式的J1、J2…Jn項,得到一個結果項K1、K2…Kn項,簡單算法中,對於每一個K都從已經得到的結果多項式開頭去查找它的正確位置,由於最終結果多項式會有(<=M*N)項,但是(<=)並不影響複雜度的計算,所以總的複雜度就變爲了O(M*M*N*N),但是實際上K1到Kn也是有序的,它們也是按指數從大到小依次排列的,如果能夠用上這個信息,那麼必然會減少許多計算,利用這個思想,整個乘法的複雜度變爲O(M (N + M N)),即O(M*M*N),因爲多項式記錄了它的項數,所以很容易使M爲兩個多項式中較小的一個。
當然對於乘法還有一種方法是複雜度變爲O(M*N*LogM*N),但是我認爲這個複雜度下的算法應該要改變實現鏈表的數據結構(比如兩個數組或(平衡)二叉樹才能達到,大概是利用了二分查找,沒有具體實現,想了一下),後兩種複雜度很難說那個更好,這取決於M、N的相對大小。
在我寫完這些代碼的時候,對於C++封裝具有安全性的理解更進一步,以兩個Append函數作爲例子,這兩個函數只是爲了方便編寫其它函數而存在的,所以它可以不保證插入的節點不處在正確的位置上,大部分情況下這並不是用戶想要的結果,而現在它暴露在外面,任何人都可以調用,所以很不安全;另一個比如鏈表數據結構也是暴露在外面的,任何人都可以隨意修改其中指針的指向,而這種修改很容易造成致命的錯誤,絕不可以暴露在外面的,必須隱藏起來,但是C語言是做不到這些的(我不知道C語言是否可以做到,即使可以,那也要花費很大的力氣吧)。而在C++中protected、private關鍵字可以幫助我們在一定程度上輕鬆地做到不讓用戶看到不應該看到的東西、不讓用戶做不應該做的操作。這也是這兩個關鍵字存在的理由之一吧,應該是很重要的一個。
另一個感受是,這些代碼接口的定義大致能表達它的功能,但是內部實現是變量命名就比較隨意了,不利於閱讀,有很重的臭味,另一點是Multi函數裏處理的特殊情況較多,很可能是設計上出了問題或是邏輯並沒有弄得很清楚。
最後附上簡單求解多項式乘法的代碼:

Polynomial* Multi(const Polynomial* Poly1,const Polynomial* Poly2)
{
    Polynomial* Poly = new Polynomial;
    Poly->ItemCount = 0;
    Poly->Items = NULL;
    PolynomialItem* p = Poly1->Items;
    while(p)
    {
        PolynomialItem* q = Poly2->Items;
        while(q)
        {
            //這個函數裏的查找過程很費時間
            InsertPolyItem(&Poly,Multi(p,q));
            q = q->next;
        }
        p = p->next;
    }
    return Poly;
}

再貼一段簡單顯示多項式的代碼,沒有處理係數、指數爲0,或者是指數爲負數的情況,今天之內沒有找到比較好的邏輯使代碼儘量簡單放棄了。

void Display(const Polynomial* Poly)
{
    if(Poly->ItemCount == 0) return;
    cout<<Poly->Items->Coeff<<"X^"<<Poly->Items->Power;
    PolynomialItem* p = Poly->Items->next;
    while(p)
    {
        if(p->Coeff > 0)
        {
            cout<<'+'<<p->Coeff<<"x^"<<p->Power;
        }
        else if(p->Coeff < 0)
        {
            cout<<p->Coeff<<"x^"<<p->Power;
        }
        p = p->next;
    }
}

遍歷鏈表時比較容易忘記推進指針,從而造成無限循環,所以最好一寫完while(p)語句馬上加一句p = p->next;可以節省不少等待時間哦(改了代碼得重新編譯的%>_<%)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章