《数据结构与算法》第二版-陈卫卫 参考答案 第1-2章

《数据结构与算法》(第二版)陈卫卫-高等教育出版社

    陆军工程大学811数据结构教材    第1-2章 参考答案

第一章 概述

1-1  (1)名称、数量、特征、性质的 (2)处理对象和计算结果

1-2  (1)数据结点  (2)类型相同

1-3  (1)存储结构  (2)物理结构  (3)存储一个数据结点及其与其他结点关系的一组存储单元  (4)空结点

1-4  (1)表结构、树结构 、图结构、散结构  (2)表结构  (3)一  (4)树结构  (5)多  (6)一  (7)表结构   (8)散列结构  (9)表结构

1-5  (1)有穷规则的集合,而规则规定了解决某一特定问题的  (2)正确性、有效性、可行性、输入、输出

1-6  (1)描述形式 (2)程序形式 (3)描述形式 (4)描述形式

1-7  (1)算法分析  (2)有效性  (3)操作界面、健壮性、可读性和易维护性

1-8  (1)对所有输入数据都能给出正确结果  (2)验证算法有错  (3)证明算法无错

1-9 (1)空间 (2)T(n)cf(n) (3)T(n)是否在多项式范围内。

1-10  (1)对于具有相同输入数据量的不同输入数据,算法时间用量的最大值  (2)对于所有相同输入数据量的各种不同输入数据,算法时间用量的平均值

1-11  (1)实现的难易度、使用频度、运行环境  (2)支配语句试题法、分段计算法、分层计算法、递推式方法、数学模型法

1-12  (1)T(n)=O(n2)  (2)S(n)=O(n)

1-13  (1)A  (2)B

1-14  C

1-15  B

1-16  D

1-17  A

1-18  D

1-19  B

1.2.3 基本概念题

1-20 分别用文字叙述形式和程序形式描述求a[n]中最大值、最小值和平均值的算法。

答案

文字形式描述如下:

    (1)开始,最大值、最小值均设为a[0],和s置为0;

(2)将a[1],a[2],……,a[n-1]依次和最大值、最小值比较:比最大值大,则替换最大值;比最小值小,则替换最小值;并将a[1],a[2],……,a[n-1]依次累加到和s中;

(3)计算平均值avg=s/n。

具体程序如下:

void max_min_avg(int a[],int n)

{

int i, max,min;

float s,avg;

s=0.0;

max=a[0];

min=a[0];

for(i=1;i<n;i++)

{

s=s+a[i];

if(a[i]>max)max=a[i];

else if(a[i]<min) min=a[i];

}

avg=s/n;

printf("max=%d,min=%d,avg=%f\n",max,min,avg);

}

1-21对下面的程序段(汽泡排序算法):

(1)计算时间复杂性T(n)。

(2)计算对数组a[n]排序时,最多比较次数和交换次数,以及平均比较次数和交换次数。

   for(j=n-2; j>=0; j--)

     for(i=0; i<=j; i++)

       if(a[i]>a[i+1])) w=a[i], a[i]=a[i+1], a[i+1]=w;

答案

(1)时间复杂性T(n)=O(n2);

(2)最坏情况下:如在倒序的情况下,外循环每执行一次,内循环都要执行j次,因此,内循环的执行次数为:

所以,最坏情况下,算法的比较次数和交换次数都是时间复杂度为:

平均情况下的时间复杂度分析如下:

设输入数据完全不同,且输入数据的n!种排列出现的可能性相等,由于一次相邻交换消除一对逆序,因此,总的交换次数即为原序列的逆序数。而一个序列的逆序数与其倒序序列的逆序数之和为

因此,平均交换次数为:

而平均比较次数仍为:

所以平均情况下的时间复杂度为:

1.2.4 算法设计题

1-24 求两个正整数m和n的最大公约数的欧几里得算法,可用下面的公式描述:

(1)将其写成程序形式,同时“顺便地”求出m和n最小公倍数。

(2)证明欧几里得算法是正确的。

答案

(1)具体算法如下:

算法(一)递归算法求最大公约数。

int gcd(int a,int b)

{

if(b==0)return a;

return gcd(b,a%b);

}

void gcd_lcm(int m,int n)

{

int mgcd,lcm;//mgcd:最大公约数;lmd:最小公倍数

mgcd=gcd(m,n);

lcm=m*n/mgcd;

printf("最大公约数为gcd=%d\n",mgcd);

printf("最小公倍数为lcm=%d\n",lcm);

}

算法(二)非递归算法求最大公约数。

void gcd_lcm(int m,int n)

{

int a,b,r;

int gcd,lcm;//gcd:最大公约数;lmd:最小公倍数

if(n<m){a=m;b=n;}

else{a=n;b=m;}//a为被除数,b为除数

if(a%b==0){gcd=b;lcm=a;}

else

{

while(b)

{

r=a%b;a=b;b=r;

}

gcd=a;

lcm=m*n/gcd;

}

printf("最大公约数为gcd=%d\n",gcd);

printf("最小公倍数为lcm=%d\n",lcm);

}

(2)证明如下:现假设有整数a>b>0,要求出gcd(a,b)。因为a=q1b+r1,其中q1为正整数,0≤r1<b,因此,若有整数n|a,且n|b,则n| r1,而r1=a- q1b,同样道理,若有整数m| r1,m|b,则m|a,所以a,b的公因子与b,r1的公因子相同,因而,gcd(a,b)=gcd(b,r1)。如此类推有下列各式:

a=q1b+r1,其中,0≤r1<b,

b=q2 r1+r2,其中,0≤r2< r1,

r1=q3 r2+r3,其中,0≤r3< r2,

……,

rk-2=qk rk-1+ rk,其中,0≤rk< rk-1,

……,

rn-2=qn rn-1+ rn,其中,0≤rn < rn-1,

rn-1=qn+1 rn+ rn+1。

由于a>b> r1> r2>…,则最终的余数为0,假设rn+1=0,则有rn-1=qn+1 rn。因为rn|rn-1,故rn是rn-1,rn的最大公因子,所以rn也是a,b的最大公因子。

故上述欧几里得算法是正确的。

1-25 若正整数n出现在n2的尾部则称n是同构数。例如1,5,25都是同构数。试写出判断n是否是同构数的算法。

答案

具体算法如下:

void isomorph(int n)

{

int m,a;

a=n;

if(n==1){  printf("%d是同构数\n",a);  return; }

if((n%10==0)||(n%10==1)||(n%10==5)||(n%10==6))

    //同构数的个位数只可能为0、1、5、6

{

m=n*n;

while((n%10)==(m%10)){n=n/10;m=m/10;}

if(n)printf("%d不是同构数\n",a);

else printf("%d是同构数\n",a);

}

else printf("%d不是同构数\n",a);

  }

或:(此算法依据:若n是个k位数,则(n2-n)%(10k)=0)

void isomorph(int n)

{

int m=10,a;

a=n;

if((n%10==0)||(n%10==1)||(n%10==5)||(n%10==6))

    //同构数的个位数只可能为0、1、5、6

{

while(n/10){n=n/10;m=m*10;}

if((a*a-a)%m)printf("%d不是同构数\n",a);

else printf("%d是同构数\n",a);

}

}

    1. 实践

1.3.1 基础实验题

1-26 整数集合A和集合B分别存储在数组a[n]和b[m]中。试分别写出求C=A∩B和D=A∪B的算法,集合C与D也用数组存储(注:同一集合中,元素不重复出现)。

(1)A、B都是无序集合(元素任意排列)。

(2)A、B都是有序集合(元素由小到大排列)。

答案

(1)由于A、B都是无序集合,因此,求交集和并集时必须逐个进行比较,也即A中的每个元素都必须判定是否在B中。具体算法如下:

void inter_union(int a[],int b[],int n,int m)

{

int c[n],d[m+n];

int i,j,k=0,p,f;

for(p=0;p<m;p++)d[p]=b[p];  //先置d=b;

for(i=0;i<n;i++)

{

f=1;

for(j=0;j<m;j++)

if(a[i]==b[j]){c[k++]=a[i];f=0;break;} //C为a、b的交集

if(f)d[p++]=a[i];  //d增加a中不同的元素得到a、b的并集

}

printf("输出a、b的交集c:");

for(i=0;i<k;i++)printf("%d  ",c[i]);

printf("\n");

printf("输出a、b的并集d:");

for(i=0;i<p;i++)printf("%d  ",d[i]);

printf("\n");

}

(2)由于,A、B都是有序集合,因此,求交集时,在将A中元素与B中元素进行比较时,只需要比较到一个元素比其大为止,求并集时,最后没有比较完的集合中的元素全部并入。具体算法如下:

void inter_union_order(int a[],int b[],int n,int m)

{

int c[n],d[m+n];

int i=0,j=0,k=0,p=0;

while((i<n)&&(j<m))

{

if(a[i]<b[j]) d[p++]=a[i++];

else if(a[i]==b[j]){c[k++]=a[i];d[p++]=a[i];i++;j++;}//相同同时放入交、并集中

     else d[p++]=b[j++];//不同只放入并集中

}

while(i<n)d[p++]=a[i++];//将a中余下的元素直接加入d中

while(j<m)d[p++]=b[j++];//将b中余下的元素直接加入d中

printf("输出a、b的交集c:");

for(i=0;i<k;i++)printf("%d  ",c[i]);

printf("\n");

printf("输出a、b的并集d:");

for(i=0;i<p;i++)printf("%d  ",d[i]);

printf("\n");

}

第2章  表结构

  1. 习题
  1.   问答题
  1. (1)如果对线性表的主要操作是存取表中的元素,而极少进行插入和删除,那么应该选用什么样的存储结构?影响元素存取速度的主要因素是什么?

(2)如果对线性表的主要操作是插入和删除,而且插入、删除位置是不确定的,那么应该选用什么样的存储结构?影响元素存取速度的主要因素是什么?

(3)如果对线性表的操作仅限于在端点处进行插入删除,那么应该选用什么样的存储结构?影响元素存取速度的主要因素是什么?

以上3点均要简述理由。

[答案]  (1)顺序存储结构。插入删除时需移动大量数据。

(2)链式存储结构。查找时需从头至尾查找,不能随机访问。

(3)栈或队列。

  1. (1)单向链表中,能否从当前结点出发访问任一结点?

(2)双向链表中,能否从当前结点出发访问任一结点?

以上2点均要说明理由。

[答案]  (1)不能。因为单向链表中,仅有一个后继链域,从当前结点出发只能访问其后的结点,不能访问其前的结点。只能从头结点出发访问任一结点。

(2)可以。因为双向链表中有指向前趋也有指向后继的链域,可以通过当前结点的前驱和后继链域访问任一结点。

  1. 在如图3所示的栈结构中。

(1)若编号为1,2,3的三节车箱按不同的进退栈次序穿过栈,可以得到哪几种不同次序的排列。

(2)若编号为1,2,3,4的四节车箱按不同的进退栈次序穿过栈,可得到哪几种排列?

*(3)一般地,若编号为1,2,…,n的 n节车箱,按不同的进退栈次序穿过栈,可以得到多少种排列?即推导出通项公式。

提示:考虑由n个1和n个0组成的长度为2n的0/1序列。其中1表示进栈操作,0表示退栈操作。任何时刻退栈操作不多于进栈操作。

*(4)试分析那些能得到的排列具有什么特点,并证明之。

[答案]  (1)5种   123  132   213  231  321

(2)14种

1234  1243  1324  1342  1432

2134  2143  2341  2431  2314

3214  3241  3421  4321

(3)

(4)不存在“大小中”形式,证明过程从略。

  1. 在如图4所示的双向队结构中,若入口处有编号为1,2,……,n的n节车依次穿过双向队到达出口。分析下列4种情况下,各有哪些排列不可能产生。以不能产生所有“全排列”的最小n为例,说明不能产生的排列具有什么特点。

(1)若上下两条路都不通(一入一出)。

(2)若上边的输入线不通(一入二出)。

(3)若下边的输出线不通(二入一出)。

(4)若上下两条路都通(二入二出)。

[答案]  (1)1种,只能出现123……n的顺序排列

    (2) 以1234为例,不能得到4213,4231,不存在“大小中”形式的子集中有“中小大”或“中大小”形式。

(3) 以1234为例,不能得到4132,4231,不存在“大小中”形式的子集中有“小大中”或“中大小”形式。

(4)可产生全排列

  1. 对于下三角形矩阵An×n,采用按行顺序压缩存储时,元素aij(0≤j≤i≤n-1)存于b[k]中,存储公式为k=i(i+1)/2+j。现由给定的k,确定b[k]中所存储的矩阵元素aij的行列下标i和j的计算公式。

[答案]       j=k-i(i+1)/2

  1. 对于上三角形矩阵,以及如图5所示的三对角形矩阵和反三对角形矩阵,分别采用按行顺序压缩存储于一维数组b中。设非0元素aij存储于b[k]。

(1)由给定非0元素aij的行列号i和j,求k。

(2)由给定的k,求b[k]中所存储的矩阵元素aij的行列下标i和j。

说明:对于反三对角形矩阵,三条斜线表示三条反对角线上元素非0,其余元素值为0。在回答问题(2)时,可按照行号i的不同取值,分别给出计算公式。

图5

[答案]  对于三对角形矩阵:(1)k=(3i-1)+(j-i+1)=2i+j

(2)i=(k+1)/3,   j=k-2i

       对于反三对角矩阵:

(1)  

    (2)

 

  1. 以h1为首指针的链表中,结点值依次为:1,2,3,……,n(n某个具体整数值)。试写出执行下面的程序段之后,以h1为首指针的链表和以h2为首指针的链表中结点值的排列次序。

   h2=new snode;

   q=h2;   p=h1;

   while(p)

    if(p->next==NULL)p=NULL;

      else q->next=p->next,q=q->next,p->next=q->next,p=p->next;

   q->next=NULL;

   q=h2;

   h2=h2->next;

   delete q;

[答案]  (1)当n为偶数时

h1: 1,3,5……,n-1

h2: 2,4,6……,n

(2)当n为奇数时

h1: 1,3,5……,n

h2: 2,4,6……,n-1

  1. 试写出下面程序的输出结果。

#include  <stdio.h>

#define  N  12

#define  M  5

void  main( )

 {  int  next[N],i,j,k,p=0;

    int d[M]={5,2,4,3,6};

    for(i=0;i<N-1;i++)next[i]=i+1;

    next[N-1]=0;

    j=N-1;

    for(i=1;i<N;i++)

      { for(k=1;k<d[p];k++)j=next[j];

        printf("%4d", next[j]);

        next[j]=next[next[j]];

        p=(p+1)%M;

      }

   printf("%4d\n", j);

  }

[答案]  4  6  10  1  9  5  8  3  0  11  2  7

功能:实现数组next中的数按数组d中的要求取数,注意第一次取数从next中的最后一位开始数数,循环地按数组d中的值分别取第5,2,4,3,6个数,其中已取出的数必须跳过。

  1. 实践
  1.   算法设计题
  1. 试写出将数组a[n]中的元素逆转(即元素次序与原次序相反)的算法,并计算元素移动次数。

[答案]  依次首尾互换即可

void traverse(int a[],int n)

{

int i,temp;

i=0;

for(i=0;i<n/2;i++)

{

temp=a[i];

a[i]=a[n-1-i];

a[n-1-i]=temp;

}

}

  1. 为了减少元素移动次数,提高效率,当删除顺序存储的线性表中的一个元素时并不立即作压缩,而是设置删除标记,比如把此元素值改为“无效”元素值INEFFICACY。等到无效元素过多,影响查找效率时,再集中压缩。

试写出集中压缩算法。假定线性表元素顺序存储在数组a[m]中。

[答案]  

int compress(int a[],int n)

{

int i,j;

j=0;

for(i=0;i<n;i++)

{

if(a[i]!=INEFFICACY)  //INEFFICACY表示无效值

a[j++]=a[i];

}

return j;//j为压缩后的表长

}

  1. 试写出递归的二分查找函数。

[答案]  

int Binary_Search(int a[],int x,int left,int right)

{

int mid;

if(left>right)

return -1;

mid=(left+right)/2;

if(x==a[mid])

return mid;

if(x<a[mid])

return Binary_Search(a,x,left,mid-1);

else

return Binary_Search(a,x,mid+1,right);

}

  1. [Fibonacci斐波那契查找]满足递推式fi=fi-1+fi-2(i≥2)的序列称Fibonacci序列,其中,f0=0,f1=1。

查找长度为n=ft-1(对某个t≥1)的有序表时,选择测试点为ft-1。该点左侧子表长度为ft-1-1,右侧子表长度为ft-2-1。如果本次查找不成功,则递归地对左侧(或右侧)子表作查找。

如果初始表长不是某个Fibonacci数减1,则可以延长表的长度,比如可在尾部加“无穷大”元素,使之成为某个最小的ft-1。

试写出Fibonacci查找算法。

[答案]   斐波那契序列为1,1,2,3,5,8,13,…,先使表长扩展至某个斐波那契数f[t]-1,然后根据f[t-1]进行查找测试。

int fib_Search(int a[],int x,int n)//数组a必须定义为动态数组

{

int *f,t,j,i,mid;

    f=(int *)calloc(n,sizeof(int));

f[0]=0;f[1]=1;

t=1;

do

{

t++;

f[t]=f[t-1]+f[t-2];

}while(f[t]<n+1);

if(f[t]>n+1)//延长表长,使初始表长满足斐波那契数-1

{

a=(int *)realloc(a,(f[t]-1)*sizeof(int));

for(j=n;j<f[t]-1;j++)

a[j]=MAX;  //MAX表示无穷大数

}

i=t-1;

mid=f[i]-1;

while(i>1)

{

if(x==a[mid])

return mid;

if(x<a[mid])

{

i=i-1;

mid=mid-f[i-1];

}

else

{

i=i-2;

mid=mid+f[i];

}

}

return -1;

}

  1. 试分别写出在顺序存储和链式存储下带自动调频机制的顺序查找算法。

[答案]  

int tp_Search(int a[],int n,int x)//永远将查找频率高的放在表头

{

int i,j;

i=0;

while(i<n && a[i]!=x)

i++;

if(i==n)

return 0;

for(j=i;j>0;j--)

a[j]=a[j-1];

a[0]=x;

return 1;

}

typedef struct node

{

int data;

struct node *next;

}snode,*ptr;

ptr tp_Search(ptr h,int x)//h为带表头监督元的链表

{

ptr p,q;

p=h;

q=h->next;

while(q!=NULL && q->data!=x)

{

p=q;

q=q->next;

}

if(q!=NULL)

{

p->next=q->next;

q->next=h->next;

h->next=q;

}

    return q;

}

  1. 已知两个有序表A=(a1,a2,…,am)和B=(b1,b2,…,bn),分别用顺序法存储在数组a[m]和b[n]中。

按下述要求,分别写出算法,将A和B合并成一个有序表C=(c1,c2,…,cm+n)。设合并后表C顺序地存储在数组C[m+n]中。

(1)合并时,不去掉重复元素。

(2)若A和B均无重复元素,但A与B可能存在重复元素,要求合并后C无重复元素,即相等元素只保留一个。

[答案]  

 (1)

int tbl_merge(int a[],int n,int b[],int m,int c[])

{

int i,j,k;

i=j=k=0;

while(i<n && j<m)

{

if(a[i]<=b[j])

c[k++]=a[i++];

else

c[k++]=b[j++];

}

while(i<n)

c[k++]=a[i++];

while(j<m)

c[k++]=b[j++];

return k;//k为数组c的长度

}

(2)

int tbl_merge(int a[],int n,int b[],int m,int c[])

{

int i,j,k;

i=j=k=0;

while(i<n && j<m)

{

if(a[i]<b[j])

c[k++]=a[i++];

else if(a[i]>b[j])

c[k++]=b[j++];

else

{

c[k++]=a[i++];

j++;

}

}

while(i<n)

c[k++]=a[i++];

while(j<m)

c[k++]=b[j++];

return k;//k为数组c的长度

}

  1. 设A是一个长度为n的线性表,x是表的首元素。

按下述要求,分别试写一个算法,将A分裂成两个线性表B和C。使得A表中小于x的元素都放在B表中,而大于或等于x的元素都放在C表中。要求:

(1)若A,B,C分别用数组a[n],b[n]和c[n]顺序存储。

(2)若A用数组a[n]顺序存储,但B和C不另外存储,而是将B的元素存储在数组的前半段,C存储在后半段。

(3)A用单向简单链表存储,B和C也用同样结构存储,同时B和C不另行分配存储结点,占用A的原来结点。

[答案]  

(1)

void seperate(int a[],int n,int b[],int &j,int c[],int &k)

{

int x,i;

x=a[0];

j=k=0;

for(i=0;i<n;i++)

{

if(a[i]<x)

b[j++]=a[i];//j为数组b的长度

else

c[k++]=a[i];//k为数组c的长度

}

}

(2)用两个指针i,j分别从前和从后查找,先从后往前找,找到小于x的放在i上,然后从前向后找,找到大于或等于x的放到j处,重复查找,直到i,j相遇,该位置即为x存放的位置。

void seperate(int a[],int n)

{

int i,j,x;

x=a[0];

i=0;

j=n-1;

while(i<j)

{

while(i<j && a[j]>=x)

j--;

if(i<j)

a[i++]=a[j];

while(i<j && a[i]<x)

i++;

if(i<j)

a[j--]=a[i];

}

a[i]=x;

}

(3)

typedef struct node

{

int data;

struct node *next;

}snode,*ptr;

void seperate(ptr headA,ptr &headB,ptr &headC)//headA、headB、headC均为不带表头监督元的链表

{

ptr p,q;

int x;

p=headA;

x=headA->data;

while(p!=NULL)

{

if(p->data<x)

{

q=p;

p=p->next;

q->next=headB;

headB=q;

}

else

{

q=p;

p=p->next;

q->next=headC;

headC=q;

}

}

}

  1. 对下列形式的链表分别写出:初始链表的构造、查找、插入、删除、输出等全套函数,并配备具有模拟调用环境功能的主控函数。

其中,构造无序链表时,结点在链表中的排列次序与元素输入次序一致,其后的插入,不受此限制;构造有序链表时,输入的元素序列是杂乱的。

(1)不加头的无序循环链表。

(2)加头有序循环链表。

[答案]  

(1)不加头的无序循环链表。

ptr  creatlinked( ) //构造简单无序循环链表

{ ptr  head,last,p; int x;

head=last=NULL;

printf("请输入链表元素,0:结束。\n");

scanf("%d",&x);

while(x)

    { p=new snode;

p->data=x;

if(head==NULL)

   head=last=p;

else

{ last->next=p;

last=p;

}

scanf("%d",&x);

    }

if(last) last->next=head;

   return  head;

}

void insertA(ptr &h)//表尾插入法

{ ptr p,last;

int x;

    printf("请输入要插入的元素,x=");

    scanf("%d",&x);

p=new snode;

p->data=x;

if(h==NULL)

{ h=p;

p->next=h;

return;

}

last=h;

while(last->next!=h)

last=last->next;

p->next=h;

last->next=p;

}

void delA(ptr &h)//删除的主控函数

{

ptr p,q;

int x;

printf("请输入要删除的元素,x=");

scanf("%d",&x);

if(h==NULL)

{printf("链表空,不能删除。\n");return; }

p=h;

if(p->data==x)//删除第一个元素

{

if(p->next==h)//链表中仅一个元素

{ h=NULL;

delete p;

printf("删除成功!\n");return;

}

q=p;

p=p->next;

while(p!=h)

{ q=p;

p=p->next;

}

h=h->next;

q->next=p->next;

delete p;

printf("删除成功!\n");return;

}

    q=p;

p=p->next;

while(p!=h && p->data!=x)

{ q=p;

p=p->next;

}

if(p==h)

printf("表中没有%d删除失败!\n",x);

else

{ q->next=p->next;

delete p;

printf("删除成功!\n");

}

}

void searchA(ptr h) //查找的主控函数

{ int x;ptr p;

printf("请输入要查找的元素,x=");

scanf("%d",&x);

if(h==NULL)

{ printf("链表空,不能查找\n");  return;}

p=h;

while(p->next!=h && p->data!=x)

p=p->next;

if(p->data!=x)

printf("没找到%d\n",x);

else

printf("找到%d\n",x);

}

void  print(ptr  h) //输出链表

{ ptr  p;

  p=h;

  if(h==NULL)

  {printf("表为空!\n"); return;}

  do

  {  printf("%4d",p->data);

     p=p->next;

  }while(p!=h);

  printf("\n");

}

插入、删除时需考虑表空、在表头插入、删除的情况。

 (2)加头有序循环链表。

void  insert(ptr &h, int  x) //插入函数

{  ptr f,s,p;

   p=new snode,p->data=x;

   f=h,s=h->next;

   while(s->data<x) f=s,s=s->next;

   p->next=s;f->next=p;return;

}

ptr  creatlinked( ) //构造加头有序循环链表

{ ptr  head; int x;

   head=new snode;

   head->data=MAX, head->next=head;

   printf("请输入链表元素,0:结束。\n");

   scanf("%d",&x);

   while(x)

    { insert(head,x);

     scanf("%d",&x);

    }

   return  head;

}

void insertA(ptr h) //插入的主控函数

{  int x;

    printf("请输入要插入的元素,x=");

    scanf("%d",&x);

   insert(h, x);

}

void del(ptr h,int x) //删除函数

{  ptr f,s;

   if(h==h->next){printf("链表空,不能删除。\n");return; }

   f=h,s=h->next;

   while(s->data<x)f=s,s=s->next;

   if(s->data==x)

    { f->next=s->next;delete s; printf("删除成功!\n");return;}

  printf("表中没有%d 删除失败!\n",x);

}

void delA(ptr &h) //删除的主控函数

{  int x;

   printf("请输入要删除的元素,x=");

   scanf("%d",&x);

   del(h,x);

}

void search(ptr h,int x) //查找函数

{  if(h==h->next){printf("链表空,不能查找\n");  return;}

   h=h->next;

   while(h->data<x)h=h->next;

   if(h->data==x)printf("找到%d\n",x);else printf("没找到%d\n",x);

}

void searchA(ptr h) //查找的主控函数

{ int x;

  printf("请输入要查找的元素,x=");

  scanf("%d",&x);

  search(h, x);

}

void  print(ptr  h) //输出加头循环链表

{ ptr  p;

  if(h==h->next){printf("表为空!\n"); return;}

   p=h->next;

    while(p!=h)

      {  printf("%4d",p->data);

         p=p->next;

      }

   printf("\n");

}

  1. (1)设计将单向链表逆转的函数。

(2)设计将单向循环链表逆转的函数。

[答案]   将表中元素按表头插入法依次插入。

 (1) 

ptr traverse(ptr p)

{

ptr q,head;

head=NULL;

while(p!=NULL)

{

q=p;

p=p->next;

q->next=head;

head=q;

}

return head;

}

(2)

ptr traverse(ptr h)

{

ptr p,q,head,last;

head=last=NULL;

p=h;

if(p!=NULL)

{

do

{

q=p;

p=p->next;

if(head==NULL)

head=last=q;

else

{

q->next=head;

head=q;

}

last->next=head;

}while(p!=h);

}

return head;

}

  1. (1)设ha和hb分别是两个加头循环链表的首指针。试写出一个算法,把hb接在ha的结点之后,并去掉多余的表头结点hb。拼接后ha仍是加头循环链表。

(2)重做(1),若ha和hb分别是两个加头循环链表的尾指针。

[答案]  

(1)

void conject(ptr Ha,ptr Hb)

{

ptr p,q;

p=Ha;

while(p->next!=Ha)

p=p->next;

q=Hb;

while(q->next!=Hb)

q=q->next;

if(q!=Hb)

{

p->next=Hb->next;

q->next=Ha;

}

free(Hb);

}

(2)

void conject(ptr &Ha,ptr Hb)

{

ptr p,q;

p=Ha->next;

q=Hb->next;

if(q!=Hb)

{

Ha->next=q->next;

Hb->next=p;

Ha=Hb;

}

free(q);

}

  1. 写出算法,把一个简单链表分裂成两个链表,使两个链表长度相等,或其中一个链表的结点数比另一个多1。

[答案]  

思想:用两个指针p,h指向链表,初始时p指向表头结点,h指向第二个结点,然后p指针每次走一步,h指针每次走两步,这样当h走到表尾时,p正好走到中间,因此一个链表就是原链表中从头结点到p结束,另一个链表就是从p以后开始的链表。

ptr split(ptr h)

{

ptr f,p;

if(h==NULL)

return NULL;

p=h;

h=h->next;

while(h!=NULL)

{

if(h->next==NULL)

break;

p=p->next;

h=h->next->next;

}

f=p->next;

p->next=NULL;

return f;

}

  1. 写出算法,将两个简单链表合并成一个简单链表,使得两个链表中的结点交替地在新链表中出现。但是若原来某个链表具有较多的结点,则把多余的结点接在新链表的尾部。

[答案]  

void merge(ptr &h,ptr q)

{

ptr pre,qre;

    p=h;

if(p==NULL)

p=q;

while(p!=NULL && q!=NULL)

{

qre=q;

q=q->next;

pre=p;

p=p->next;

qre->next=p;

pre->next=qre;

}

if(q!=NULL)

qre->next=q;

    q=NULL;

}

  1. 写出算法,把一个简单链表中值为偶数的结点都排在链表的前半段,而值为奇数的结点留在表的后半段,并且使同奇偶的结点保持原来的相对次序。

[答案]  在链表中从前向后查找第一个奇数的结点,标记其位置,然后重复向后查找偶数结点,将其移动到第一个奇数结点的前面。

void sort(ptr &head)

{

ptr h,hre,p,pre,q;

h=head;

while(h!=NULL && h->data%2==0)//扫描结点,直到空或值为奇数的结点

{

hre=h;

h=h->next;

}

p=h;

while(p!=NULL)

{

if(p->data%2==1)

{

pre=p;

p=p->next;

}

else

{

q=p;

p=p->next;

pre->next=p;

if(h==head)

{

q->next=h;

head=q;

hre=q;

}

else

{

q->next=h;

hre->next=q;

hre=q;

}

}

}

}

  1. 已知非空简单链表的首指针为head,指针p指向链表中某个结点。试写出算法用O(1)时间将新结点x插在p结点之左侧。注意,不提供p的前驱指针。

[答案]  记下p结点的值为y,将p结点的值改为x,然后在p结点之右插入新结点y。

void insert(ptr p,int x) //修改结点p的值为x

{

int y;

ptr q;

y=p->data;

p->data=x;

q=new snode;

q->data=y;

q->next=p->next;

p->next=q;

}

  1. 设两个栈S1和S2以顺序方式共同存储在数组a[MAX]中,栈底分别固定在数组的两端,使它们迎面增长。

分别写出共用的进栈、退栈、显示各栈当前元素的函数。最后给出完整程序。

[答案]  

typedef struct stack

{

int a[MAX];

int top1,top2;

}stack;

stack s;

void initial()

{

s.top1=-1;

s.top2=MAX;

}

void push(int flag,int x)//flag标志从表头还是表尾进栈

{

if(s.top1+1==s.top2)

{

printf("栈满\n");

exit(0);

}

if(flag==0)//从表头进

s.a[++s.top1]=x;

else

s.a[--s.top2]=x;

}

void pop(int flag,int &x)

{

if(flag==0)

    {

if(s.top1==-1)

   {

     printf("栈空,不能取数据\n");

  exit(0);

   }

x=s.a[s.top1--];

    }

else

{

if( s.top2==MAX)

   {

     printf("栈空,不能取数据\n");

  exit(0);

   }

   x=s.a[s.top2++];

}

}

int showpop(int flag)//显示栈顶元素

{

int i;

if(flag==0)

  return s.a[s.top1];

else

  return s.a[s.top2];

}

  1. 分别按下述方式完成循环队q[MAX]的进、出队算法设计。

(1)如图6所示的首指针前置法。

(2)保留首尾指针,用当前队长度作为队空/满的判断条件。

(3)保留首尾指针,用变量flag标记队是否空。0为空,1为不空。

[答案]   注意初始位置first=last=MAX-1

(1) 需禁用一个单元

int addq(int q[],int x,int first,int &last)

{

if(first==(last+1)%MAX)

return 0;

last=(last+1)%MAX;

q[last]=x;

return 1;

}

int delq(int q[],int &x,int &first,int last)

{

if(first==last)

return 0;

first=(first+1)%MAX;

x=q[first];

return 1;

}

 (2) 可禁用一个单元进行操作,也可用计数器count,进队时count++,出队时count--。count==0表示队空,count==MAX表示队满。

下面的答案给出的是禁用一个单元的操作。

int addq(int q[],int x,int first,int &last)

{

int n;

n=(last-first+MAX)%MAX;

if(n==MAX-1)

return 0;

last=(last+1)%MAX;

q[last]=x;

return 1;

}

int delq(int q[],int &x,int &first,int last)

{

int n;

n=(last-first+MAX)%MAX;

if(n==0)

return 0;

first=(first+1)%MAX;

x=q[first];

return 1;

}

(3)全部空间可进行操作的队空队满条件如下:

队空:flag==0 && last==first

队满:flag==1 && last==first

int addq(int q[],int x,int first,int &last,int &flag)

{

if(flag==1 && last==first)

return 0;

last=(last+1)%MAX;

q[last]=x;

    flag=1;

return 1;

}

int delq(int q[],int &x,int &first,int last,int &flag)

{

if(flag==0 && last==first)

return 0;

first=(first+1)%MAX;

x=q[first];

    flag=0;

return 1;

}

  1. 根据下面给出的显示循环队q[m]当前所有的元素函数,为其配备进出队函数。

void disply(int  q[ ], int first,int last) //first和last分别是首尾指针

{  int i;

  if(first==last)

  {printf("当前队空!\n");return; }

  printf("当前队中的元素:\n");

  for(i=first; ; )

  {i=(i+1)%m;printf("%5d",q[i]);

  if(i==last)break;}

  printf("\n");

}

[答案]  从上面的函数可看出队列中first指向队头的前一个元素,last指向队尾元素。

//进队函数

int  addq(int q[ ],int first, int& last,int x)//fisrt指向队头前一个元素,last指向队尾元素

{ if((last+1)%m==first)  return 0;

  last=(last+1)%m;

  q[last]=x;

  return 1;

}

//出队函数

int  delq(int q[ ],int &first,int last,int &x)

{ if(first==last) return 0;

  first=(first+1)%m;

  x=q[first];

  return 1;

}

  1. 用单向循环链表存储队列,只设队尾指针last,不设队首指针。试分别设计进队函数、出队函数,以及显示当前队中所有元素的函数。

[答案]  因为其为单向循环链表,因此进队就是在last后插入新结点,出队就是删除last的后一个结点,即首结点。

//进队函数:

void  Qinsert (ptr &last, int x)

 { ptr p;

  p=(ptr)malloc(sizeof(snode));

  p->data=x;

  if(last==NULL)

  { last=p;

   last->next=last;

    return;

   }

  p->next=last->next;

  last->next=p;

  last=p;

}

//出队函数

void Qdelete(ptr &last)

{  ptr p;

   if(last==NULL)

   {   printf("队列为空\n");

   return;

   }

   p=last->next;

   if(p==last)

   last=NULL;

   else

      last->next=p->next;

   free(p);

}

//显示函数:

void  print(ptr  last)

  { ptr s;

    if(last==NULL)

    {  printf("队列为空!\n");

      return;

    }

    s=last->next;

    while(s!=last)

      {  printf("%4d",s->data);

         s=s->next;

      }

   printf("%4d\n",s->data);

  }

  1. 按下述要求,分别写出算法,回收以head为首指针的静态链表中的全部结点,其中av是自由队列的首指针。

(1)链表head是单向简单链表。

(2)链表head是一个循环链表。

[答案]  

void takeback(int p)//回收结点

{

a[p].next=av;

av=p;

}

(1)

void takebackall(int &h)//回收head首指针的静态链表

{

int q;

while(h!=NIL)

{

q=h;

h=a[q].next;

takeback(q);

}

}

(2)用首尾指针方便循环链表回收。如果可无顺序回收,则可仅用首指针,每次回收第二个结点即可,最后回收首指针。

void takebackall(int &h,int &la)//回收h为首指针的循环链表

{

int q;

if(h==NIL)

return;

while(la!=h)

{

q=h;

h=a[q].next;

a[la].next=h;

takeback(q);

}

if(la==h)

{

q=h;

h=la=NIL;

takeback(q);

}

}

  1. 编号为0~n-1的n只猴子排成一个圆圈,顺时针反复1~m报数,凡报数为m者退到圈外。按退出的次序输出这些猴子的编号。

[答案]  让猴子手拉手围成一圈,当圈内猴子数小于m时,会出现重复报数现象,则此时或置m=m%n1。

void main()

{

const int n=13;

const int m=5;

int next[n],i,j,k,n1,m1;

printf("%d个猴子1至%d报数,输出结果:\n",n,m);

for(i=0;i<n-1;i++)

next[i]=i+1;

next[n-1]=0;

j=n-1;

n1=n;//猴子数

m1=m;

for(i=0;i<n;i++)

{

if(n1<m)

m1=m%n1;

for(k=1;k<m1;k++)

j=next[j];

printf("%4d",next[j]);

next[j]=next[next[j]];

n1--;

}

printf("\n");

}

  1. 若n×n的对称矩阵按下三角形矩阵形式压缩存储,写出输出第i行所有元素的算法。

[答案]  找出对称矩阵变换为下三角矩阵压缩的规律为k=i*(i+1)/2+j(0≤j≤i<N)

void convert(int a[N][N],int b[])

{

int i,j,k;

for(i=0;i<N;i++)

for(j=0;j<=i;j++)

{

k=i*(i+1)/2+j;

b[k]=a[i][j];

}

}

void output(int b[],int i)//输出第i(0-N-1)行所有元素

{

int j;

for(j=0;j<=i;j++)

printf("%4d",b[i*(i+1)/2+j]);

printf("\n");

}

  1. 写出稀疏矩阵Am×n压缩顺序存储下,确定元素aij之值的算法。

[答案]  

typedef struct

{

int row,col;

int val;

}elem;

void output(elem b[],int blen,int i,int j)//i,j为数组a的下标

{

int k;

k=0;

while(k<blen && (b[k].row!=i || b[k].col!=j))

k++;

if(k>=blen)

printf("数组a中第%d行%d列的值为.\n",i,j);

else

printf("数组a中第%d行%d列的值为%d\n",i,j,b[k].val);

}

  1. 若矩阵Am×n中存在一个元素aij,使得aij是A的第i行中最小值,同时又是第j列中的最大值,则称aij是A的一个鞍点。

(1)试证明A不可能存在两个鞍点,除非它们的值相等。

(2)若A的元素均不同,试写出求A的鞍点的算法。若无鞍点,请给出信息。

*(3)若A的元素可以相同,试写出求A的所有鞍点的算法。若无鞍点,请给出信息。

[答案]  

(1)

假设A存在两个鞍点Amn、Aij,且Amn≠Aij。

由题意可得

Amn在m行中最小,在n列中最大

则Ain≤Amn    Amj≥Amn

Aij在i行中最小,在j列中最大

则Ain≥Aij      Amj≤Aij

由上可得

     Aij≤Ain≤Amn

Amn≤Amj≤Aij

且Amn≠Aij

则Aij<Amn

又Amn<Aij矛盾

所以A不可能存在两个鞍点,除非它们的值相等。

(2)从第一行起找该行的最小值,然后判断其是否为该列的最大值,如果是则为鞍点,结束。否则则转到下一行继续查找。如果所有行全查找完毕仍无鞍点则给出矩阵中无鞍点存在的信息。

void find_ad(int a[M][N])//M行N列

{

int i,j,k,t;

i=0;

while(i<M)

{

k=0;

for(j=1;j<N;j++)

{

if(a[i][j]<a[i][k])

k=j;

}

t=0;

while(t<M)

{

if(t==i)

t++;

else if(a[t][k]<a[i][k])

t++;

else

break;

}

if(t==M)

{

printf("第%d行%d列为鞍点%d\n",i,k,a[i][k]);

break;

}

i++;

}

if(i==M)

printf("矩阵中不存在鞍点\n");

}

(3) 从第一行起找该行的最小值x,然后判断其是否为该列的最大值,如果是则为鞍点,否则继续查找本行的最小值x,判断其是否为该列的最大值,直到第一行的最后一列。然后转到下一行继续按上面的步骤查找。如果所有行全查找完毕仍无鞍点则给出矩阵中无鞍点存在的信息。

void find_ad(int a[M][N])

{

int i,j,k,t,flag;

i=0;

flag=0;

while(i<M)

{

k=0;

for(j=1;j<N;j++)

{

if(a[i][j]<a[i][k])

k=j;

}

for(j=k;j<N;j++)

{

if(a[i][j]==a[i][k])

{

t=0;

while(t<M)

{

if(t==i)

t++;

else if(a[t][j]<=a[i][j])

t++;

else

break;

}

if(t==M)

{

printf("第%d行%d列为鞍点%d\n",i,j,a[i][j]);

flag=1;

}

}

}

i++;

}

if(flag==0)

printf("矩阵中不存在鞍点\n");

}

  1. (1)写出算法,确定模式串p[n]在正文串a[m]中一共出现多少次。

(2)写出算法,将正文串a[m]中出现的模式串p[n1]全部换成另一个串q[n2]。

[答案]  

(1)将正文串与模式串从前向后按位比较,如相等都向后移一位,如果模式串走到最后表明匹配,次数加1。如果不匹配,将正文串指针回溯,模式串指针指向第一位,重新开始按上述要求进行比较,直到正文串走到最后。

int total(char a[],int m,char p[],int n)

{

int i,j,num;

i=j=num=0;

while(i<m && j<n)

{

if(a[i]==p[j])

{

i++;

j++;

}

else

{

i=i-j+1;

j=0;

}

if(j==n)

{

num++;

j=0;

}

}

return num;

}

(2)思路同(1),差别之处在于当匹配时不是记数,而是用新串取代。为了简便程序,我们用一个新数组b辅助空间来存放新生成的串,最后将b中元素全部复制到原数组中即可。

void repla(char a[],int m,char p[],int n1,char q[],int n2)

{

char b[M];

int i,j,k,t,s;

i=j=k=t=0;

while(i<m && j<n1)

{

if(a[i]==p[j])

{

i++;

j++;

}

else

{

i=i-j+1;

j=0;

}

if(j==n1)

{

j=0;

for(s=t;s<i-n1;s++)

b[k++]=a[s];

for(s=0;s<n2;s++)

b[k++]=q[s];

t=i;

}

}

for(s=t;s<m;s++)

b[k++]=a[s];

b[k]='\0';

for(i=0;i<=k;i++)

a[i]=b[i];

}

  1.   基础实验题
  1. 用无序链表完成下面的链表综合练习。

其中的链表可以选用动态的,也可选用静态的。

由m个链表组成一个链表组,指针数组h[m]是这组链表的首指针,其中h[i]是第i个链表的首指针,表中的元素类型为int。

按照下述要求,完成构造链表,输出链表,链表的查找、插入、删除和合并。

(1)将有关数据结构初始化,例如将各表头指针置空。

(2)按下述要求读入数据,构造初始链表组。

每次读两个整数i和x(其中0≤i≤m-1)表示要将元素x插在第i个链表中。当读到的i<0,则表示初始输入数据结束。

要求输入数据时,i的值在0到m-1之间无规律的随机变化,且同一个元素x只能插入一次。要求,程序要具有“检查输入数据”的功能。

(3)按i从0到m-1的次序,输出上一步所构造的各个初始链表。

(4)按照下述要求,完成链表操作。

每次读三个数据,其中第一个是一个字符型数据,表示操作码,后面两个是整数i和x(i和x的含义与前述相同)。

(4.1)若读到的操作码是字符f,则要求在第i个链表中查找值为x的结点。若找到,则输出x在链表中的相对序号,即x在第i个链表中是第几个结点;若没找到,则输出“在i链表中没有x”式样的信息。

(4.2)若读到的操作码是字符d,则要求在第i个链表中找到并删除结点x。但若x不在第i个链表中,应输出“在i链表中没有x”式样的信息。

(4.3)若读到的操作码是字符i,则要求在第i个链表中插入结点x。但是如果x原来已经在第i个链表中,就不能再作插入,并且要给出“x已在第i个链表中”式样的信息。

(4.4)若读到的操作码是字符j,字符j后面两个整数是a和b,则要求将第b个链表中的结点统统合并到第a个链表中。

(4.5)若读到的操作码不是上述四个字符(字母f,d,i,j),则表示对链表的操作结束。

要求:查找、插入、删除运算穿插进行多次,而且分别含有成功的和不成功的查找、插入、删除。合并操作至少做一次。

(5)像步骤3那样,输出最后的结果链表组。

[答案]  

void init(ptr a[])//初始化

{

int i;

for(i=0;i<M;i++)

a[i]=NULL;

}

void build(ptr a[])//构造链表组

{

int i,x;

ptr p;

printf("请输入插入的位置和数据,以,间隔,当位置<0或>%d表示输入结束:\n",M-1);

scanf("%d,%d",&i,&x);

while(i>=0 && i<M)

{

p=newsnode();

p->data=x;

p->next=a[i];

a[i]=p;

        printf("请输入插入的位置和数据,以,间隔,当位置<0或>%d表示输入结束:\n",M-1);

    scanf("%d,%d",&i,&x);

}

}

void output(ptr a[])//输出链表组

{

int i;

ptr p;

for(i=0;i<M;i++)

{

        printf("第%d行数据为:",i);

p=a[i];

while(p!=NULL)

{

printf("%4d",p->data);

p=p->next;

}

printf("\n");

}

}

void operate(ptr a[])//完成链表的各项操作

{

char ch;

int i,x,num;

ptr p,pre;

    printf("请输入请输入位置(0~%d),操作码(f-查找、d-删除、i-插入、j-合并,输入其他为操作结束)和数据(当操作码为j时数据变为位置),以,间隔\n",M-1);

scanf("%d,%c,%d",&i,&ch,&x);

while(ch=='f'||ch=='d'||ch=='i'||ch=='j')

{

if(ch=='f' && i>=0 && i<M)

{

num=1;

p=a[i];

while(p!=NULL && p->data!=x)

{

p=p->next;

num++;

}

if(p==NULL)

printf("在第%d个链表中没有数据%d\n",i,x);

else

printf("数据%d在第%d个链表中是第%d个结点\n",x,i,num);

}

else if(ch=='d' && i>=0 && i<M)

{

p=a[i];

pre=p;

while(p!=NULL && p->data!=x)

{

pre=p;

p=p->next;

}

if(p==NULL)

printf("在第%d个链表中没有数据%d\n",i,x);

else

{

if(pre==p)

a[i]=p->next;

else

pre->next=p->next;

free(p);

printf("删除成功\n");

}

}

else if(ch=='i' && i>=0 && i<M)

{

p=a[i];

while(p!=NULL && p->data!=x)

p=p->next;

if(p==NULL)

{

pre=newsnode();

pre->data=x;

pre->next=a[i];

a[i]=pre;

printf("插入成功\n");

}

else

printf("数据%d已在第%d个链表中\n",x,i);

}

else if(ch=='j' && i>=0 && i<M && x>=0 && x<M)

{

p=a[i];

if(p==NULL)

a[i]=a[x];

else

{

while(p!=NULL)

{

pre=p;

p=p->next;

}

pre->next=a[x];

}

a[x]=NULL;

printf("合并成功\n");

}

printf("请输入请输入位置(0~%d),操作码(f-查找、d-删除、i-插入、j-合并,输入其他为操作结束)和数据(当操作码为j时数据变为位置),以,间隔\n",M-1);

scanf("%d,%c,%d",&i,&ch,&x);

}

}

  1.   综合实验题
  1. 编写完成模拟小型计算器功能的程序。

注意,只能从键盘读字符,包括数字符和运算符等,而不能直接读整数或实数。即,需要将读入的数字串拼成数值。

为了方便设计,可按下述要求,分为几个层次,逐步完成。

(1)完成对正整数的加减乘除运算,不进行错误检查。

输入一个数、一个运算符和另一个数,按等于号和回车键,输出计算结果。

例如,输入

123+305=

按回车键后,输出计算结果。

注意,这一层主要练习编写正整数的拼数函数。

(2)完成正整数的混和四则运算,要考虑优先级,但算式中不带括号,程序不作错误检查。

例如,输入

12+34*54-21/3=

按回车键后,输出计算结果。

这一层主要练习运算符的优先级和栈的应用。

(3)完成实数的混和四则运算,不作错误检查。

这一层主要练习如何拼实数。

(4)完成带括号的混和四则运算,不作错误检查。

例如,输入

12*(2+4)/5=

按回车键后,输出计算结果。

这一层主要练习如何处理括号。

(5)完成带负数,带数学函数的混和四则运算。

例如,输入

-12.3+sin(0.12)=

按回车键后,输出计算结果。

这一层主要练习如何处理负数和函数等“一目”运算。

(6)完成带错误检查的,用于计算任一数学表达式的计算。

当检查到错误时,最好要指明错误位置和出错原因。

例如,在第5个字符处少右括号、或少运算符、多运算符、数值中出现字母等非数值字符等。

[答案]  

int FullStack(int top)//判栈满

{

    if(top==MAXS-1) //MAXS栈空间最大值

return FULL; //FULL栈满

    else

return 0;

}

int EmptyStack(int top)//判栈空

{

    if(top==-1)

return EMPTY; //EMPTY栈空

    else

return 0;

}

void PushOptr(char op)//入操作符栈

{

    if(FullStack(OptrTop))

    {

       printf("操作符栈满!\n");

       flag=ENDFLAG;

       return;

    }

    Optr[++OptrTop]=op;

    return;

}

char PopOptr()//出操作符栈

{

    if(EmptyStack(OptrTop))

    {

       printf("操作符栈空!\n");

       flag=ENDFLAG; //ENDFLAG结束标记

       return ERROPTR; //ERROPTR操作符栈出错

    }

    return Optr[OptrTop--];

}

char GetOptr()//取操作符栈顶算符

{

    if(EmptyStack(OptrTop))

    {

       printf("操作符栈空!\n");

       flag=ENDFLAG;

       return ERROPTR;

    }

    else

return Optr[OptrTop];

}

void PushOpnd(double op)//入操作数栈

{

    if(FullStack(OpndTop))

    {

       printf("操作数栈满!\n");

       flag=ENDFLAG;

       return;

    }

    Opnd[++OpndTop]=op;

    return;

}

double PopOpnd()//出操作数栈

{

    if(EmptyStack(OpndTop))

    {

       printf("操作数栈空!\n");

       flag=ENDFLAG;

       return ERROR; //ERROR操作出错

    }

    return Opnd[OpndTop--];

}

char oplist[OPTRNUM][OPTRNUM] //OPTRNUM算符个数

//'^' '*' '/' '+' '-' '(' ')' 'i' 's' 'c' 'm' '#'

={'<','>','>','>','>','<','>','<','<','<','e','>',//'^

  '<','>','>','>','>','<','>','<','<','<','e','>',//'*'

  '<','>','>','>','>','<','>','<','<','<','e','>',//'/'

  '<','<','<','>','>','<','>','<','<','<','e','>',//'+'

  '<','<','<','>','>','<','>','<','<','<','e','>',//'-'

  '<','<','<','<','<','<','=','<','<','<','<','e',//'('

  '>','>','>','>','>','e','>','e','>','>','e','>',//')'

  '>','>','>','>','>','e','>','e','e','e','e','>',//'i'

  '>','>','>','>','>','<','>','e','>','>','e','>',//'s'

  '>','>','>','>','>','<','>','e','>','>','e','>',//'c'

  '<','<','<','>','>','<','>','<','<','<','e','>',//'m'

  '<','<','<','<','<','<','e','<','<','<','<','=',//'#'

};//算符优先表 s:sin c:cos m:-(求负数)

int iop(char op)//求算符在算符表中对应的下标

{

    switch(op)

    {

    case '^':

       return 0;

    case '*':

       return 1;

    case '/':

       return 2;

    case '+':

       return 3;

    case '-':

       return 4;

    case '(':

       return 5;

    case ')':

       return 6;

    case 's':

       return 8;

    case 'c':

       return 9;

    case 'm':

       return 10;

    case '#':

       return 11;

    default:

       return 7;

    }

}

double dstof(char ds[],int l)//将数字串转换成相应的实数

{

    int k=0,j;

    double i,i1=0,i2=0;

    while((k<l)&&(ds[k]!='.'))

    {

       i1=i1*10+(ds[k]-'0');

   k++;

    }

    for(j=l-1;j>k;j--)

i2=(i2+(ds[j]-'0'))/10;

    i=i1+i2;

    return i;

}

double func(double t1,char c)//计算函数sin(t1)||cos(t1), 函数值返回计算结果;

{

    double t3=0;

    switch(c)

    {

       case 's':

           t3=sin(t1);

           printf("sin(%f)=%f\n",t1,t3);

           break;

       case 'c':

           t3=cos(t1);

           printf("cos(%f)=%f\n",t1,t3);

           break;

       default:

           t3=-t1;

           printf("-%f=%f\n",t1,t3);

           break;

    }

    return t3;

}

double expression(double t1,double t2,char c)//计算表达式t1 c t2 函数值返回计算结果;

{

    double t3=0;

    switch(c)

    {

       case '^':

           t3=pow(t1,t2);//计算t3=t1^t2

           break;

       case '*':

           t3=t1*t2;

           break;

       case '/':

           if(fabs(t2)<=1.0E-10)

           {

              printf("表达式中除数为0,出错!\n");

              flag=ENDFLAG;

           }

           else t3=t1/t2;

           break;

       case '+':

           t3=t1+t2;

           break;

       case '-':

           t3=t1-t2;

           break;

       default:

           printf("算术操作符有错!\n");

           flag=ENDFLAG;

           break;

    }

    return t3;

}

void main()

{

    …

    init();//初始化

    printf("请输入要计算的表达式,以#结束:\n");

    ch=getchar();

    while(flag!=ENDFLAG) //算术表达式计算结束!

    {

       if(isdigit(ch)||(ch=='.'))//识别输入串中的一个实数

       {

           while(isdigit(ch)||(ch=='.'))

           {

              inds[i++]=ch;

  ch0=ch;

  ch=getchar();

  first=0;

           }

           val=dstof(inds,i);//将数字串转换成相应实数

           PushOpnd(val);//操作数入数栈

           i=0;

       }

       else//输入字符是操作符

       {

           op=ch;

           if((op=='s')||(op=='c'))//识别输入操作符是否为sin,cos函数

           {

              ch1=getchar();

  ch2=getchar();

  first=0;

              switch(op)

              {

                  case 's'://sin函数

                     if((ch1!='i')||(ch2!='n'))

 flag=ENDFLAG;

                     break;

                  default://cos函数

                     if((ch1!='o')||(ch2!='s'))

 flag=ENDFLAG;

                     break;

              }

           }

           else

              if(op=='-')//识别是否为负号“-”

                if((ch0=='(')||(first==1))

op='m';

           csop=GetOptr();//取栈顶符号

           compc=oplist[iop(csop)][iop(op)];//比较栈顶操作符与输入操作符的优先级

           switch(compc)

           {

              case '<'://输入操作符入栈

                  PushOptr(op);

                  ch0=ch;

                  ch=getchar();

                  first=0;

                  break;

              case '>'://取操作符栈顶符号进行计算

                  csop=PopOptr();

                  if((csop=='s')||(csop=='c')||(csop=='m'))//进行sin,cos函数运算, “负”运算

                  {

  op1=PopOpnd();

                      op3=func(op1,csop);//计算sin(op1)或cos(op1)或“负”运算

                  }

                  else//进行其他操作符运算

                  {

                     op2=PopOpnd();//取第二个操作数

                     op1=PopOpnd();//取第一个操作数

                     op3=expression(op1,op2,csop);//计算op1 csop op2

                     printf("%f%c%f=%f\n",op1,csop,op2,op3);

                  }

                  PushOpnd(op3);//将运算结果入栈

                  break;

              case '=':

                  if(op==')')//括号匹配

                  {

                     csop=PopOptr();

                     ch0=ch;

                     ch=getchar();

                     first=0;

                  }

                  else//表达式结束

                  {   

                     result=PopOpnd();

                     if(EmptyStack(OpndTop))

 printf("result=%f\n",result);//输出最终计算结果

                     else

 printf("表达式输入有错!\n");

                     flag=ENDFLAG;

                  }

                  break;

              default://对应符号优先表中的错误'e'

                  printf("算术表达式输入有错!\n");

                  flag=ENDFLAG;

                  break;

           }

       }//endif

    }//endwhile

}//endmain

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值