记第一次实践——数据结构 线性表实践(稀疏多项式)剖析程序(避坑参考)

今天试手稀疏多项式程序设计。做了3、4个小时吧,debug时间比写代码时间长,遇到了一些问题,目前解决了。算法的健壮性也在debug中不断完善。
不多说,直接上代码,解说代码!

typedef struct plynml
{
    int expr;
    float coef;
}Ply;
typedef struct node
{
    Ply data;
    struct node *next;
}Lnode,*Linklist;

头文件只有iostream和cstdlib,代码脚趾头想也能想到,还有那个using nsp std。

  • 这是我定义的稀疏多项式的项数结构和链表的结点结构,项数结构有系数和指数
inline bool CreateLkList(Linklist &L)
{
    L=new Lnode;
    if(!L)
    {
        cerr<<"Cannot create LkList.\n";
        return false;
    }
    L->next=NULL;
    return true;
}
  • 初始化链表操作,先给头指针L一个结点(头结点),然后头结点next域置空,完成。(这里加了个inline来提高代码执行效率(直接在该被调函数调用点展开该被调函数,同时免去被调函数整体拷贝到程序堆栈里。)
bool plynmlCreate(Linklist &L,int n)//头插法产生指数从小到大的(可稀疏)多项式
{//这个函数比较重要,重点注释剖析!记下!
    Lnode *p,*q,*pre;

    for(int i=1;i<=n;i++)
    {
        p=new Lnode;//这里,不要tm的搞错了,你是要通过循环建立节点的,吧这个语句定义在外面
        //后果就是后来输入的数据覆盖这个结点!记下!
        cout<<"Input coef and expr:\n";
        cin>>p->data.coef>>p->data.expr;
        pre=L;//注意49 50的位序和赋值的变量。。。。不要写在for循环外
        q=L->next;//你肯定多项式里不光有一项,你得有N项,所以你要把这行代码放到这里,记下!
        while((q&&q->data.expr<p->data.expr))//比较时莫忘记加成员变量,记住!
        {
            pre=q;
            q=q->next;
        }
        p->next=q;
        pre->next=p;
    }
    return true;
}
  • 比较重要的建表操作,这个建表的原理是将新结点和表中原来所有结点比较结点(项式)的指数,我设定的是多项式升序排列,所以如果新结点的指数最大,就接在链表最后,如果新结点的指数不是最大,在比该新结点的指数大的结点前停下,插入。
  • 细节深入说明:
    ①因为这是单链表,所以我设定了p指针(指向新结点),q指针(给p指针指向的新结点找位置的),pre指针(用来连接新结点和前驱结点)
    我一开始把p=new Lnode;写在了外头,想想这样做的后果是什么?
    ※答:你后面给结点数据域赋的值都是在那一个结点上,后者覆盖前者。
    因为你肯定多项式不能光有一项,你必须不断给多项式添加新结点(新项式)!所以你必须保证每次for循环从1~n你每循环一次都能插入一个结点,这样才能符合应该符合的规则。
    pre=L;q=L->next;这两句代码,也不要给写外头,你在for循环每新增一个结点,你就得找到其应该归属的位置,那你必然得从头找,因为这是单链表!所以写在for循环里,while循环外
    while((q&&q->data.expr<p->data.expr)) { pre=q; q=q->next; }
    这段代码是q指针在给新插入结点寻找位置,因为多项式我要求升序排列,我的q指针一开始指向首元结点,新增结点和q指针指向的结点依次比较,直到找到新增节点的指数大于q结点的指数时q结点的位置所在。此时你可以脑补一下,当q=q->next;执行后q结点让循环退出了(q结点的指数大于新增结点p结点的指数了导致循环退出),那么q结点的位置之前就该插入p结点!(如果以q=NULL形式退出,则证明新增结点的指数最大,应该放在链表最后。 此时你再看,pre指针指向的就是新插入结点(假如它已插进链表)的前驱结点(可以说是原q结点)。
    p->next=q; pre->next=p;
    好,找到位置了,那么就让p结点(新增结点)后继结点连上q结点(此时q结点的指数大于新增结点p结点的指数了),然后应该是p结点的前驱结点连上p结点,好,一轮for循环完成,再执行下一轮直到所有新增结点全部插入多项式链表。然后返回OK。
void plynmladd(Linklist &L1,Linklist &L2)
{
    Lnode *pa=0,*pb=0,*pc=0,*tpa=0,*tpb=0;
    pa=L1->next;
    pb=L2->next;
    pc=L1;
    while(pa&&pb)
    {
        if(pa->data.expr==pb->data.expr)
        {
            if(pa->data.coef+pb->data.coef==0)
            {
                tpa=pa;
                tpb=pb;
                pa=pa->next;
                pb=pb->next;
                delete tpa;
                delete tpb;
                //DeleteLklist(L1,tpa->data.expr);//到这行一切正常,tpb指向的指数还是很正常的
                //DeleteLklist(L2,tpb->data.expr);//到这行,tpb指向的指数突然变成了随机数,为什么?
            }//找到原因,因为pc->next的改变会让L1表链接什么点改变,换句话说,pc和pa这两个指针作用相同
            //你改变pc->next跟改变pa->next是一样的,都会让pc或pa指针指向的结点的指针域指向发生变化!!!
            //也就是会发生指向不再指向本表(L1表),而可能会串表!!!!
            //该bug体现在一个实例上。L1:(7,0) (3,1) (9,8) (5 17)
            //L2:(8,1) (22,7) (-9,8)若不建新表,Pc的指向(即L1表的表结点走向)到产生bug处,
            //为头结点->(7,0)->(11,1)(已合并)->(22,7)->(-9,8),(-9,8)这个结点就会在上面第一次调用
            //DeleteLklist的时候被删除(本该删除(9,8),没删。)
            //!这不就是串表删除了?那你第二次调用DeleteLklist的时候,
            //也就是你在L2进行删除(-9,8)结点的时候,删除谁?
            //你第一次调用删除函数删除了(-9,8),tpb指向就混乱了!所以才有了tpb指向的指数变成了随机数!
            //解决办法:delete函数不要,直接delete,我想多了,应该直接delete的,这样更简单易理解!※
            else
            {
                pa->data.coef=pa->data.coef+pb->data.coef;
                pc->next=pa;
                pc=pa;//这样写或许更好
                pa=pa->next;
                tpb=pb;
                pb=pb->next;
                //DeleteLklist(L2,tpb->data.expr);
                delete tpb;
            }
        }
        else if(pa->data.expr>pb->data.expr)
        {
            pc->next=pb;
            pc=pb;
            pb=pb->next;
        }
        else
        {
            pc->next=pa;
            pc=pa;
            pa=pa->next;
        }
    }
    pc->next=pa?pa:pb;//记住!
}
  • 到了重要的函数了,这个函数的功能是稀疏多项式(链表)相加后合并为一个链表(链表b经操作合并到链表a)
  • 细节深入说明:
    ①这么说吧,我多定义了一个指针pc指向多项式1(链表1)的头结点,然后我用这个指针从头走到尾,新的链表路线即为稀疏多项式相加后的链表。
    ②可以选择纸上走查一遍这个算法。
    多项式1(链表1)的数据为(7,0),(3,1),(9,8),(5,17)。
    多项式2(链表2)的数据为(8,1),(22,7),(-9,8)
    拿数据对着算法走查一遍,你会很明白。但我还要在此解释下这个函数代码。
    我是比较链表1pa指针指向的结点和链表2pb指针指向的结点的数据域中的指数,思路如下(忽略水印
    在这里插入图片描述
    我一开始还寻思删除结点还要写一个正规的链表删除结点函数,后来遇到了一个bug,想了想,其实没这么复杂,就直接给一个指针指向要删除的结点然后delete 那个指针就ok了。
    因为我要删除要删除的结点,而且我也要兼顾到pa、pb指针不能成为野指针(你自己想想删除后你得让pa、pb指向下一个结点吧?),所以我设置了tpa、tpb这种临时指针指向要删除的结点,然后pa/b=pa/b->next;让pa和pb指向要删除结点的下一个结点然后再delete tpa/tpb;删掉要删除的结点。
    鉴于当pa结点和pb结点的指数相等且系数相加=0时进行的操作的代码注释和bug解决过程太黑,我给你搬出来看吧。

//DeleteLklist(L1,tpa->data.expr);//到这行一切正常,tpb指向的指数还是很正常的。(发现自己想多了后因为是试探直接删除行不行所以先加了//)
//DeleteLklist(L2,tpb->data.expr);//到这行,tpb指向的指数突然变成了随机数,为什么?
//通过上面的数据一步步debug+走查算法找到原因,因为pc->next的改变会让L1表链接什么点改变,换句话说,pc和pa这两个指针作用相同
//你改变pc->next跟改变pa->next是一样的,都会让pc或pa指针指向的结点的指针域指向发生变化!!!
//也就是会发生指向不再指向本表(L1表),而可能会串表!!!!
//该bug体现在一个实例上。L1:(7,0) (3,1) (9,8) (5 17)
//L2:(8,1) (22,7) (-9,8)若不建新表,Pc的指向(即L1表的表结点走向)到产生bug处,
//为头结点->(7,0)->(11,1)(已合并)->(22,7)->(-9,8),(-9,8)这个结点就会在上面第一次调用
//DeleteLklist的时候被删除(本该删除(9,8),没删。)
//!这不就是串表删除了?那你第二次调用DeleteLklist的时候,
//也就是你在L2进行删除(-9,8)结点的时候,删除谁?
//你第一次调用删除函数删除了(-9,8),tpb指向就混乱了!所以才有了tpb指向的指数变成了随机数!
//解决办法:delete函数不要,直接delete,我想多了,应该直接delete的,这样更简单易理解!

 else
            {
                pa->data.coef=pa->data.coef+pb->data.coef;
                pc->next=pa;
                pc=pa;//这样写或许更好
                pa=pa->next;
                tpb=pb;
                pb=pb->next;
                //DeleteLklist(L2,tpb->data.expr);
                delete tpb;
            }
        }
        else if(pa->data.expr>pb->data.expr)
        {
            pc->next=pb;
            pc=pb;
            pb=pb->next;
        }
        else
        {
            pc->next=pa;
            pc=pa;
            pa=pa->next;
        }
    }
  • 这一块代码就是除pa结点和pb结点的指数相等且系数相加=0的其他情况了,代码就简单多了。其中pc=pa/pb;写成pc=pc->next;也可以,意思一样。理解也不难理解,就是顺势(依照规则)串起升序排列的多项式结点组成新表。
    OK,到这里走完一轮循环,然后再走循环,直到有一个链表所有结点都接入了新链表(共用L1表和L2表)。
pc->next=pa?pa:pb;
  • 这一行代码,很简洁!意思是如果某一链表的结点都接进新链表了,另一链表的结点还有的没接进新链表的话,那就让pc指针(新链表的指针)把另一链表剩余的结点都全接上。(pc指针肯定指向新链表的最后一个结点,然后把另一链表剩余的结点都接上的)
  • 简洁之处在于你不用写两个while循环,一个是当L1表(pa指针指向L1表第一个剩余结点)剩余结点时的循环来让剩余结点接入新链表(pc指针,共用L1表和L2表)。一个是当L2表(pa指针指向L1表第一个剩余结点)剩余结点时的循环来让剩余结点接入新链表(pc指针,共用L1表和L2表)。减少时间复杂度。
void plynmlintegrate(Linklist &L)
{
    Lnode *p=L->next,*q;
    while(p)
    {
        while(p->next&&p->data.expr==p->next->data.expr)
        {
            p->data.coef=p->data.coef+p->next->data.coef;
            q=p->next;
            p->next=q->next;
            delete q;
        }
        p=p->next;
    }
}//合并同类项

然后你会发现新链表是有同类项存在的,所以必须要合并下同类项,最后输出最终的相加后的稀疏多项式。

  • 细节说明:
    p指针指向首元结点。然后看p结点和其后继结点,两个结点的数据域的系数相不相等,相等了且p结点不是最后一个结点(其后继结点还有,进入内层while循环,把系数加起来给p结点的数据域,然后定义一个q指针指向p结点下一结点,然后参照链表删除结点函数的关键操作删掉q结点(因为同类项已经合并到p结点(q结点前驱结点),q结点已经毫无意义,删掉q结点即可。
    不相等的话,就跳出内层while循环,然后p指针走到下一结点,继续比较,如此往复。
void displayplynml(Linklist &L)
{
    Lnode *p=L->next;//链表在创建完的时候,链表的
    int j=1;
    while(p)
    {
        cout<<"("<<j<<") coef: "<<p->data.coef<<" expr: "<<p->data.expr<<endl;
        j++;
        p=p->next;
    }
    cout<<"------------------------------\n";
}

多项式显示函数,我在创建第一个稀疏多项式和第二个稀疏多项式以及这两个稀疏多项式相加且合并同类项后调用该函数。

int main()
{
    Linklist L1,L2;
    if(CreateLkList(L1))
    {
        cout<<"Successful Creating Linklist1,next.\n";
    }
    else
    {
        cout<<"Oops!The programming will terminated soon!";
        exit(-1);

    }
    if(CreateLkList(L2))
    {
        cout<<"Successful Creating Linklist2,next.\n";
    }
    else
    {
        cout<<"Oops!The programming will terminated soon!";
        exit(-1);

    }
    int n;
    cout<<"Input L1's length.\n";
    cin>>n;
    if(n<1)
    {
        cerr<<"Why do you input n which is smaller than 1?The programming will terminated soon!";
        exit(-1);
    }
    if(plynmlCreate(L1,n))
    {
        cout<<"Successful Creating polynomial1,next.\n";
    }
    else
    {
        cerr<<"Oops!The programming will terminated soon!";
        exit(-1);

    }
    displayplynml(L1);
    cout<<"Input L2's length.\n";
    cin>>n;
    if(n<1)
    {
        cerr<<"Why do you input n which is smaller than 1?The programming will terminated soon!";
        exit(-1);
    }
    if(plynmlCreate(L2,n))
    {
        cout<<"Successful Creating polynomial2,next.\n";
    }
    else
    {
        cerr<<"Oops!The programming will terminated soon!";
        exit(-1);

    }
    displayplynml(L2);
    plynmladd(L1,L2);
    plynmlintegrate(L1);
    cout<<"After operating:\n";
    displayplynml(L1);
    return 0;

主函数已给出,整体流程:
①建表L1和L2
②并建立稀疏多项式给L1和L2
③建立L1链表稀疏多项式完成后显示一下稀疏多项式的数据元素,建立L2时也如此。
如果建表失败和建立稀疏多项式失败,做异常处理,程序提前结束。
④然后对两稀疏多项式进行相加并对新稀疏多项式进行合并同类项,
⑤然后再显示稀疏多项式的数据元素。
⑥OK,程序结束。

以下是程序运行的结果:

 cin>>p->data.coef>>p->data.expr;

这是输入格式,对照参阅。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
总之我从这个项目中学到了一些东西,比如简洁代码。
这是个宝贵的财富,日后用到一些功能相似的函数可以到这里来查阅~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值