数据结构与算法(C语言版)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.4 应用实例

3.4.1 约瑟夫环问题

本章的引言部分给出了约瑟夫环问题及其数据模型,下面考虑算法设计与程序实现。

【算法】 由于约瑟夫环问题本身具有循环性质,考虑采用循环单链表。约瑟夫环问题的基本思想是设置一个计数器count和工作指针p,当计数器累加到m时删除结点p。为了统一对表中任意结点的操作,循环单链表不带头结点;为了便于删除操作,设两个工作指针为pre和p,pre指向结点p的前驱结点;为了使计数器从1开始计数,采用尾指针指示的循环单链表,将pre初始化为指向终端结点,将p初始化为指向开始结点,如图3-33所示。

图3-33 约瑟夫环的初始状态

用函数Joseph求解约瑟夫环问题,算法用伪代码描述如下 :

算法:Joseph(rear,m)

输入:尾指针指示的循环单链表rear,密码m

输出:约瑟夫环的出环顺序

1.初始化:pre=rear;p=rear->next;count=1;

2.重复下述操作,直到链表中只剩一个结点:

2.1 如果count小于m,则:

2.1.1 工作指针pre和p后移;

2.1.2 count++;

2.2 否则,执行下述操作:

2.2.1 输出结点p的数据域;

2.2.2 删除结点p;

2.2.3 p指向pre的后继结点;count=1重新开始计数;

3.输出结点p的数据域,删除结点p;

【程序】 主函数首先输入约瑟夫环的长度n和密码m,然后调用函数Creat建立由尾指针rear指示的循环单链表,最后调用函数Joseph打印出环的顺序。程序如下:

            #include<stdio.h>                         //使用库函数printf和scanf
            #include<malloc.h>                        //使用malloc等库函数实现动态存储分配
            typedefstructNode                           //定义单链表的结点Node
            {
                  intdata;
                  structNode*next;
            }Node;
            Node*Creat(intn);                        //函数声明,构造尾指针指示的约瑟夫环
            voidJoseph(Node*rear,intm);             //函数声明,打印出环的顺序
                                                        //以下是主函数
            intmain()
            {
                  intn,m;
                  Node*rear=NULL;                      //定义尾指针rear并初始化为空
                  printf("请输入约瑟夫环的长度:");//输出提示信息
                  scanf("%d",&n);
                  printf("请输入密码:");          //输出提示信息
                  scanf("%d",&m);
                  rear=Creat(n);                     //函数调用,返回的尾指针赋给rear
                  Joseph(rear,m);                   //函数调用,实参rear是尾指针
                  return0;                             //将0返回操作系统,表明程序正常结束
            }
                                                        //以下是其他函数定义
            Node*Creat(intn)                          //函数定义,返回值是循环单链表的尾指针
            {
                  Node*rear=NULL,*s;                  //定义尾指针rear并初始化为空
                  inti;
                  rear=(Node*)malloc(sizeof(Node));//申请结点,rear指向该结点
                  rear->data=1;                        //结点rear的数据域为1
                  rear->next=rear;                     //建立长度为1的循环单链表
                  for(i=2;i<=n;i++)                 //依次插入数据域为2,3,…,n的结点
                  {
                      s=(Node*)malloc(sizeof(Node));//申请结点,s指向该结点
                      s->data=i;                        //结点s的数据域为i
                      s->next=rear->next;              //将结点s插入尾结点rear的后面
                      rear->next=s;
                      rear=s;                            //指针rear指向当前的尾结点
                  }
                  returnrear;                            //结束函数的执行,并将尾指针rear返回到调用处
            }
            voidJoseph(Node*rear,intm)
            {                   //函数定义,形参rear为循环单链表的尾指针,形参m为密码
                  Node*pre=rear,*p=rear->next,*q;     //初始化工作指针p和pre
                  intcount=1;                            //初始化计数器count
                  printf("出环的顺序是:");
                  while(p->next!=p)                   //循环,直到循环链表中只剩一个结点
                  {
                      if(count<m)                      //计数器未累加到密码值
                      {
                  pre=p;p=p->next;                     //将工作指针pre和p分别后移
                  count++;                               //计数器加1
                      }
                      else                                //计数器已经累加到密码值
                      {
                  printf("%-3d",p->data);          //宽度3位左对齐打印出环的编号
                  q=p;                                   //指针q暂存即将删除的结点
                  pre->next=p->next;                   //将结点p摘链
                  p=pre->next;                          //工作指针p后移,但pre不动
                  free(q);                             //释放指针q指向的存储单元
                  count=1;                               //计数器从1开始重新计数
                      }
                  }
                  printf("%-3d\n",p->data);        //宽度3位左对齐输出最后一个结点的编号
                  free(p);                             //释放最后一个结点
                  return;                                //结束函数Joseph的执行
            }

3.4.2 一元多项式求和

【问题】 设A(x)=a0+a1x+a2x2+…+anxn,B(x)=b0+b1x+b2x2+…+bmxm,并且多项式的指数可能很高且变化很大,例如:A(x)=7+12x3-2x8+5x12,B(x)=4x+6x3+2x8+5x20+7x28,求两个一元多项式的和,即求A(x)+B(x)。

【想法】 由于一元多项式A(x)=a0+a1x+a2x2+…+anxn由n+1个系数唯一确定,因此,可以用一个线性表(a0,a1,a2,…,an)来表示,每一项的指数i隐含在其系数ai的序号里。如果多项式的指数可能很高且变化很大,则一元多项式对应的线性表中就会存在很多零元素。一个较好的存储方法是只存储非零项,但是需要在存储非零系数的同时存储相应的指数。这样,一元多项式的每一个非零项可由系数和指数唯一表示。例如,一元多项式A(x)=7+12x3-2x8+5x12就可以用线性表((7,0),(12,3),(-2,8),(5,12))来表示。

由数学知识,一元多项式求和实质上是合并同类项的过程,也就是将两个一元多项式对应的线性表进行合并的过程。例如,A(x)=7+12x3-2x8+5x12和B(x)=4x+6x3+2x8+5x20+7x28的求和,即是将线性表((7,0),(12,3),(-2,8),(5,12))和((4,1),(6,3),(2,8),(5,20),(7,28))进行合并,结果为((7,0),(4,1),(18,3),(5,12),(5,20),(7,28))。

【算法】 如何存储多项式对应的线性表呢?对于指数相差很多的两个一元多项式,相加会改变多项式的系数和指数。若相加的某两项的指数不等,则两项应分别加在结果中,将引起线性表的插入;若某两项的指数相等,则系数相加,若相加结果为零,将引起线性表的删除。由于在线性表的合并过程中需要频繁地执行插入和删除操作,因此考虑采用单链表存储。

在表示一元多项式的单链表中,每一个非零项对应单链表中的一个结点,且单链表应按指数递增有序排列。结点结构如图3-34所示。其中

图3-34 一元多项式链表的结点结构

coef:系数域,存放非零项的系数;

exp:指数域,存放非零项的指数;

next:指针域,存放指向下一结点的指针。

下面分析多项式求和的执行过程。设单链表A和B分别存储两个多项式,求和结果存储在单链表A中,设两个工作指针p和q分别指向两个单链表的开始结点。两个多项式求和实质上是对结点p的指数域和结点q的指数域进行比较,有下列三种情况:

(1)若p->exp小于q->exp,则结点p应为结果链表中的一个结点,将指针p后移,如图3-35所示。

图3-35 第一种情况示意图

(2)若p->exp大于q->exp,则结点q应为结果中的一个结点,将q插入到第一个单链表中结点p之前,并将指针q指向单链表B中的下一个结点,如图3-36所示。为此,在单链表A中应该设置两个工作指针pre和p,使得pre指向p的前驱结点。

图3-36 第二种情况示意图

(3)若p->exp等于q->exp,则p与q所指为同类项,将q的系数加到p的系数上。若相加结果不为0,则将指针p后移,并删除结点q,为此,在单链表B中应该设置两个工作指针qre和q,使得qre指向q的前驱结点,如图3-37(a)所示;若相加结果为0,则表明结果中无此项,删除结点p和结点q,并将指针p和指针q分别后移,如图3-37(b)所示。

图3-37 第三种情况示意图

综合上述三种情况,一元多项式相加算法用伪代码描述如下 :

算法:AddPolynomial(A,B)

输入:两个单链表A和B

输出:单链表A和B的合并结果

1.初始化工作指针pre,p,qre,q;

2.while(p存在且q存在)执行下列三种情形之一:

2.1 如果p->exp小于q->exp,则指针pre和p后移;

2.2 如果p->exp大于q->exp,则:

2.2.1 将结点q插入到结点p之前;

2.2.2 指针q指向原指结点的下一个结点;

2.3 如果p->exp等于q->exp,则:

2.3.1 p->coef=p->coef+q->coef;

2.3.2 如果p->coef等于0,则执行下列操作,否则,指针p后移;

2.3.2.1 删除结点p;

2.3.2.2 使指针p指向它原指结点的下一个结点;

2.3.3 删除结点q;

2.3.4 使指针q指向它原指结点的下一个结点;

3.如果q不为空,将结点q链接在第一个单链表的后面;

【程序】 主函数首先接收从键盘输入的一元多项式各项的系数和指数,分别建立多项式A(x)和B(x),然后调用函数AddPolynomial实现两个一元多项式相加,最后调用函数Print打印相加结果。程序如下:

            #include<stdio.h>                       //使用库函数printf和scanf
            #include<malloc.h>                      //使用malloc等库函数实现动态存储分配
            typedefstructNode                         //定义单链表结点
            {
                  intcoef,exp;                      //coef表示系数,exp表示指数
                  structNode*next;
            }Node;
            Node*AddPolynomial(Node*A,Node*B);    //函数声明,多项式相加
            Node*Creat();                          //函数声明,建立单链表表示一元多项式
            voidPrint(Node*first);                 //函数声明,打印一元多项式
                                                      //空行,以下为主函数
            intmain()
            {
                  Node*A=NULL,*B=NULL;
                  A=Creat();Print(A);           //输入多项式A(x)建立单链表,头指针为A
                  B=Creat();Print(B);           //输入多项式B(x)建立单链表,头指针为B
                  A=AddPolynomial(A,B);           //相加结果为头指针A指示的单链表
                  printf("结果是:");
                  Print(A);                        //输出单链表A,即相加结果
                  return0;                           //将0返回操作系统,表明程序正常结束
            }
                                                      //以下是其他函数定义
            Node*Creat()                            //建立单链表,返回头指针
            {
                  Node*first=NULL,*r=NULL,*s=NULL;
                  intcoef,exp;
                  first=(Node*)malloc(sizeof(Node));//申请头结点
                  r=first;                               //尾插法建立单链表
                  printf("请输入系数和指数:");
                  scanf("%d%d",&coef,&exp);        //输入第一项的系数和指数
                  while(coef!=0)                       //循环结束的条件是输入系数为0
                  {
                      s=(Node*)malloc(sizeof(Node));
                      s->coef=coef;s->exp=exp;
                      r->next=s;r=s;                   //将结点s插入单链表的尾部
                      printf("请输入系数和指数:");
                      scanf("%d%d",&coef,&exp);
                  }
                  r->next=NULL;                         //置单链表的尾标志
                  returnfirst;                           //结束函数的执行并返回头指针
            }
            Node*AddPolynomial(Node*A,Node*B)
            {                                             //多项式相加,头指针分别为A和B
                  Node*pre=A,*p=pre->next;             //工作指针pre和p初始化
                  Node*qre=B,*q=qre->next;             //工作指针qre和q初始化
                  Node*v=NULL;
                  while(p!=NULL&&q!=NULL)
                  {
                      if(p->exp<q->exp)              //第1种情况
                      {
                  pre=p;p=p->next;
                      }
                      elseif(p->exp>q->exp)         //第2种情况
                  {
                            v=q->next;
                            pre->next=q;               //将结点q插入到结点p之前
                            q->next=p;
                            q=v;
                  }
                  else                                   //第3种情况
                  {
                            p->coef=p->coef+q->coef; //系数相加
                            if(p->coef==0)            //系数为0
                            {
                                pre->next=p->next;    //则删除结点p
                                free(p);
                                p=pre->next;
                            }
                            else                         //系数不为0
                            {
                                pre=p;p=p->next;
                            }
                            qre->next=q->next;       //第3种情况都要删除结点q
                            free(q);
                            q=qre->next;
                  }
                  }
                  if(q!=NULL)pre->next=q;          //将结点q链接在第一个单链表的后面
                  free(B);                           //释放第二个单链表的头结点所占的内存
                  returnA;
            }
            voidPrint(Node*first)
            {
                  Node*p=first->next;
                  if(p!=NULL)                        //输出第一项
                      printf("%dx%d",p->coef,p->exp);
                  p=p->next;
                  while(p!=NULL)
                  {
                      if(p->coef>0)                 //输出系数的正号或负号
                  printf("+%dx%d",p->coef,p->exp);
                      else
                  printf("%dx%d",p->coef,p->exp);
                      p=p->next;
                  }
                  printf("\n");
            }