第3章〓栈和队列 3.1练习题及参考答案 3.1.1练习题 1. 单项选择题 (1) 栈的“先进后出”特性是指()。 A. 最后进栈的元素总是最先出栈 B. 当同时进行进栈和出栈操作时,总是进栈优先 C. 每当有出栈操作时,总要先进行一次进栈操作 D. 每次出栈的元素总是最先进栈的元素 (2) 设一个栈的进栈序列是a,b,c,d(即元素a~d依次通过该栈),则借助该栈所得到的输出序列不可能是()。 A. abcdB. dcbaC. acdbD. dabc (3) 一个栈的进栈序列是a,b,c,d,e,则栈的不可能的输出序列是()。 A. edcbaB. decbaC. dceabD. abcde (4) 已知一个栈的进栈序列是1,2,3,…,n,其输出序列的第一个元素是i(1≤i≤n),则第j(1≤j≤n)个出栈元素是()。 A. iB. n-iC. j-i+1D. 不确定 (5) 设顺序栈st的栈顶指针top的初始值为-1,栈空间大小为MaxSize,则判定st栈为栈空的条件为()。 A. st.top==-1B. st.top!=-1 C. st.top!=MaxSizeD. st.top==MaxSize (6) 设顺序栈st的栈顶指针top的初始值为-1,栈空间大小为MaxSize,则判定st栈为栈满的条件是()。 A. st.top!=-1B. st.top==-1 C. st.top!=MaxSize-1D. st.top==MaxSize-1 (7) 当用一个数组data[0..n-1]存放栈中元素时,栈底最好()。 A. 设置在data[0]处B. 设置在data[n-1]处 C. 设置在data[0]或data[n-1]处D. 设置在data数组的任何位置 (8) 若一个栈用数组data[1..n]存储,初始栈顶指针top为0,则以下元素x进栈的正确操作是()。 A. top++; data[top]=x;B. data[top]=x; top++; C. top--; data[top]=x;D. data[top]=x; top--; (9) 若一个栈用数组data[1..n]存储,初始栈顶指针top为n,则以下元素x进栈的正确操作是()。 A. top++; data[top]=x;B. data[top]=x; top++; C. top--; data[top]=x;D. data[top]=x; top--; (10) 队列中元素的进出原则是()。 A. 先进先出B. 后进先出C. 栈空则进D. 栈满则出 (11) 栈和队列的不同点是()。 A. 都是线性表 B. 都不是线性表 C. 栈只能在一端进行插入/删除操作,而队列在不同端进行插入/删除操作 D. 没有不同点 (12) 元素a、b、c、d依次连续进入队列qu后,队头元素是(①),队尾元素是(②)。 A. aB. bC. cD. d (13) 一个队列的进列序列为1234,则队列可能的输出序列是()。 A. 4321B. 1234C. 1432D. 3241 (14) 在循环队列中,元素的排列顺序()。 A. 由元素进队的先后顺序确定B. 与元素值的大小有关 C. 与队头和队尾指针的取值有关D. 与队中数组大小有关 (15) 循环队列qu(队头指针front指向队首元素的前一位置,队尾指针rear指向队尾元素的位置)的队满条件是()。 A. (qu.rear+1)%MaxSize==(qu.front+1)%MaxSize B. (qu.rear+1)%MaxSize==qu.front+1 C. (qu.rear+1)%MaxSize==qu.front D. qu.rear==qu.front (16) 循环队列qu(队头指针front指向队首元素的前一位置,队尾指针rear指向队尾元素的位置)的队空条件是()。 A. (qu.rear+1)%MaxSize==(qu.front+1)%MaxSize B. (qu.rear+1)%MaxSize==qu.front+1 C. (qu.rear+1)%MaxSize==qu.front D. qu.rear==qu.front (17) 设循环队列中数组的下标是0~N-1,其头尾指针分别为f和r(队头指针f指向队首元素的前一位置,队尾指针r指向队尾元素的位置),则其元素个数为()。 A. r-fB. r-f-1 C. (r-f)%N+1D. (r-f+N)%N (18) 一个循环队列中用data[0..n-1]数组保存队中元素,另设置一个队尾指针rear和一个记录队中实际元素个数的变量count,则该队中最多可以存放的元素个数是()。 A. n-1B. n C. (rear+n) % nD. (n-rear) % n (19) 设栈S和队列Q的初始状态为空,元素e1~e6依次通过栈S,一个元素出栈后即进队列Q,若6个元素出队的序列是e2,e4,e3,e6,e5,e1,则栈S的容量至少应该是()。 A. 5B. 4C. 3D. 2 (20) 与顺序队相比,链队的()。 A. 优点是可以实现无限长队列 B. 优点是进队和出队时间性能更好 C. 缺点是不能进行顺序访问 D. 缺点是不能根据队首和队尾指针计算队的长度 2. 填空题 (1) 栈是一种特殊的线性表,允许插入和删除运算的一端称为(①)。不允许插入和删除运算的一端称为(②)。 (2) 若栈空间大小为n,则最多的连续进栈操作的次数为()。 (3) 一个栈的输入序列是12345,输出序列为12345,其进栈出栈的操作为()。 (4) 有5个元素,其进栈次序为a、b、c、d、e,在各种可能的出栈次序中,以元素c、d最先出栈(c第一个且d第二个出栈)的次序有()。 (5) 顺序栈用data[0..n-1]存储数据,栈顶指针为top,其初始值为0,则元素x进栈的操作是()。 (6) 顺序栈用data[0..n-1]存储数据,栈顶指针为top,其初始值为0,则出栈元素x的操作是()。 (7) ()是被限定为只能在表的一端进行插入运算,在表的另一端进行删除运算的线性表。 (8) 设有数组A[0..m]作为循环队列的存储空间,front为队头指针(它指向队首元素的前一位置),rear为队尾指针(它指向队尾元素的位置),则元素x执行进队的操作是()。 (9) 设有数组A[0..m]作为循环队列的存储空间,front为队头指针(它指向队首元素的前一位置),rear为队尾指针(它指向队尾元素的位置),则元素出队并保存到x中的操作是()。 (10) 设循环队列的大小为70,队头指针front指向队首元素的前一位置,队尾指针rear指向队尾元素位置。现经过一系列进队和出队操作后,有front=20,rear=11,则队列中的元素个数是()。 3. 简答题 (1) 简要说明线性表、栈与队的异同点。 (2) 当用一维数组实现顺序栈时,为什么一般将栈底设置在数组的一端,而不是设置在中间? (3) 在以下几种存储结构中,哪个最适合用作链栈? ① 带头结点的单链表; ② 不带头结点的循环单链表; ③ 带头结点的双链表。 (4) 在循环队列中插入和删除元素时,是否需要移动队中元素? (5) 顺序队的“假溢出”是怎样产生的?如何判断循环队列是空还是满? 4. 算法设计题 (1) 设计一个算法,利用一个顺序栈将字符数组a[0..n-1]的所有元素逆置。 (2) 设计一个算法,将一个十进制正整数d转换为相应的八进制数。 (3) 设计一个算法,利用栈的基本运算返回给定栈中的栈底元素,要求仍保持栈中元素次序不变。这里只能使用栈st的基本运算来完成,不能直接用st.data[0]来得到栈底元素。 (4) 设计一个算法,利用循环队列的基本运算和《教程》中例3.13求循环队列元素个数的算法,求指定循环队列中的队尾元素,要求队列中元素次序不改变,算法的空间复杂度为O(1)。 (5) 对于循环队列,假设队中所有元素为数字字符,利用循环队列的基本运算和《教程》中的例3.13求循环队列元素个数的算法,删除其中所有奇数字符元素。 3.1.2练习题参考答案 1. 单项选择题 (1) A(2) D(3) C(4) D(5) A (6) D(7) C(8) A(9) D(10) A (11) C(12) ① A ② D(13) B(14) A(15) C (16) D(17) D(18) B(19) C(20) D 2. 填空题 (1) ①栈顶②栈底 (2) n (3) 1进栈,1出栈,2进栈,2出栈,3进栈,3出栈,4进栈,4出栈,5进栈,5出栈 (4) cdbae、cdeba、cdbea (5) data[top]=x; top++; (6) top--; x=data[top]; (7) 队列 (8) rear=(rear+1)%(m+1); A[rear]=x; (9) front=(front+1)%(m+1); x=A[front]; (10) (rear-front+M)%M=(11-20+70)%70=61 3. 简答题 (1) 答: 相同点: 都属于线性结构,都可以用顺序表存储或链表存储; 栈和队列是两种特殊的线性表,即受限的线性表,只是对插入、删除运算加以限制。 不同点: ①运算规则不同,线性表为随机存取,而栈是只允许在一端进行插入、删除运算,因而是后进先出表LIFO; 队列是只允许在一端进行插入、另一端进行删除运算,因而是先进先出表FIFO。②用途不同,栈用于子程序调用和保护现场等,队列用于多道作业处理、指令寄存及其他运算等。 (2) 答: 栈的主要操作是进栈和出栈,栈底总是不变的,元素进栈是从栈底向栈顶一个方向伸长的,如果栈底设置在中间,伸长方向的另一端空间难以使用,所以栈底总是设置在数组的一端而不是中间。 (3) 答: 栈中元素之间的逻辑关系属线性关系,可以采用单链表、循环单链表和双链表之一来存储,而栈的主要运算是进栈和出栈,当采用①时,前端作为栈顶,进栈和出栈运算的时间复杂度为O(1)。当采用②时,前端作为栈顶,当进栈和出栈时,首结点都发生变化,还需要找到尾结点,通过修改其next域使其变为循环单链表,算法的时间复杂度为O(n)。当采用③时,前端作为栈顶,进栈和出栈运算的时间复杂度为O(1)。 但单链表和双链表相比,其存储密度更高,所以本题中最适合用作链栈的是带头结点的单链表即①。 (4) 答: 在循环队列中插入和删除元素时,不需要移动队中任何元素,只需要修改队尾或队头指针,并向队尾插入元素或从队头取出元素。 (5) 答: 采用一维数组实现队列时,当队尾指针已经到了数组的上界,不能再有进队操作,但其实数组中还有空位置,这就产生了“假溢出”,所以假溢出是队满条件设置不当造成的。 采用循环队列是解决假溢出的途径,解决循环队列是空还是满的办法如下。 ① 设置一个布尔变量以区别队满还是队空; ② 浪费一个元素的空间,用于区别队满还是队空; ③ 使用一个计数器记录队列中元素个数(即队列长度)。 通常采用方法②,让队头指针front指向队首元素的前一位置,队尾指针rear指向队尾元素的位置,这样判断循环队列队空的标志是front=rear,队满的标志是(rear+1)%MaxSize=front。 4. 算法设计题 (1) 解: 定义一个栈st,正向扫描a的所有字符,将每个字符进栈。再出栈所有字符,将其写入a[0..n-1]中。对应的算法如下。 void Reverse(char a[],int n)//逆置a[0..n-1] {int i; char e; SqStack st;//定义一个顺序栈st InitStack(st); for (i=0;i<n;i++)//依次将a[0..n-1]进栈 Push(st,a[i]); for (i=0;i<n;i++)//将a[n-1]~a[0]依次存放到a[0..n-1] {Pop(st,e); a[i]=e; } DestroyStack(st);//销毁栈st } (2) 解: 算法原理与《教程》中例3.9相同,仅将二进制改为八进制。对应的算法如下。 void trans(int d,char b[])//b用于存放d转换成的八进制数的字符串 {SqStack st;//定义一个顺序栈st InitStack(st);//栈初始化 char ch; int i=0; while (d!=0) {ch='0'+d%8;//求余数并转换为字符 Push(st,ch);//字符ch进栈 d/=8;//继续求更高位 } while (!StackEmpty(st)) {Pop(st,ch);//出栈并存放在数组b中 b[i]=ch; i++; } b[i]='\0';//添加字符串结束标志 DestroyStack(st);//销毁栈st } (3) 解: 对于给定的栈st,设置一个临时栈tmpst,先退栈st中所有元素并进入栈tmpst中,最后的一个元素即为所求,然后将临时栈tmpst中的元素逐一出栈并进入st中,这样恢复st栈中原来的元素。对应算法如下。 int GetBottom(SqStack st,ElemType &x)//x在算法返回1时保存栈底元素 {ElemType e; SqStack tmpst;//定义临时栈 InitStack(tmpst);//初始化临时栈 if (StackEmpty(st))//空栈返回0 {DestroyStack(tmpst); return 0; } while (!StackEmpty(st))//临时栈tmpst中包含st栈中逆转元素 {Pop(st,x); Push(tmpst,x); } while (!StackEmpty(tmpst))//恢复st栈中原来的内容 {Pop(tmpst,e); Push(st,e); } DestroyStack(tmpst); return 1;//返回1表示成功 } (4) 解: 由于算法要求空间复杂度为O(1),所以不能使用一个临时队列。先利用《教程》中例3.13的算法求出队列qu中的元素个数m。循环m次,将一个元素x出队,再将元素x进队。最后的x即为队尾元素。对应的算法如下。 int Count(SqQueue sq)//求循环队列qu中元素个数 { return (sq.rear+MaxSize-sq.front) % MaxSize; } int Last(SqQueue qu,ElemType &x)//求解算法 {int i,m=Count(qu); if (m==0) return 0;//队中元素个数为0返回0 for (i=1;i<=m;i++) {DeQueue(qu,x);//出队元素x EnQueue(qu,x);//将元素x进队 } return 1;//成功找到队尾元素返回1 } (5) 解: 先求出队列qu中的元素个数m。i从1~m循环,出队第i个元素e,若e不为奇数,将其进队。对应的算法如下。 int Count(SqQueue sq)//求循环队列qu中元素个数 { return (sq.rear+MaxSize-sq.front) % MaxSize; } void DelOdd(SqQueue &qu)//求解算法 {char e; int i,m=Count(qu); for (i=1;i<=m;i++)//出队m次 {DeQueue(qu,e); if ((e-'0')%2!=1)//将不是奇数的数字字符进队 EnQueue(qu,e); } } 3.2上机实验题及参考答案 3.2.1上机实验题 1. 基础实验题 (1) 设计字符顺序栈的基本运算程序,并用相关数据进行测试。 (2) 设计字符链栈的基本运算程序,并用相关数据进行测试。 (3) 设计字符循环队列的基本运算程序,并用相关数据进行测试。 (4) 设计字符链队的基本运算程序,并用相关数据进行测试。 2. 应用实验题 (1) 设计一个算法,利用顺序栈的基本运算删除栈st中所有值为e的元素(这样的元素可能有多个),并且保持其他元素次序不变。并用相关数据进行测试。 (2) 设计一个算法,利用顺序栈的基本运算求栈中从栈顶到栈底的第k个元素,要求仍保持栈中元素次序不变,并用相关数据进行测试。 (3) 设进栈序列是1,2,…,n(n为一个大于2的正整数),编写一个程序判断通过一个栈能否得到由a[0..n-1]指定出栈序列,如果能够得到指定的出栈序列,请给出栈操作的步骤,并用相关数据进行测试。 (4) 设计一个算法,利用循环队列的基本运算和《教程》中例3.13求循环队列元素个数的算法,删除指定队列中的队尾元素,要求算法的空间复杂度为O(1)。 (5) 设计一个循环队列,用front和rear分别作为队头和队尾指针,另外用一个标志tag标识队列可能空(tag=0)或可能满(tag=1),这样加上front==rear可以作为队空或队满的条件。要求设计队列的相关基本运算算法。 (6) 用循环队列求解约瑟夫问题: 设有n个人站成一圈,其编号从1~n。从编号为1的人开始按顺时针方向1、2……循环报数,数到m的人出列,然后从出列者的下一个人重新开始报数,数到m的人又出列,如此重复进行,直到n个人都出列为止。要求输出这n个人的出列顺序,并用相关数据进行测试。 3.2.2上机实验题参考答案 1. 基础实验题 (1) 解: 字符顺序栈的基本运算算法设计原理参见《教程》的3.1.2节。包含顺序栈基本运算函数的文件SqStack.cpp如下。 #include <stdio.h> #define MaxSize 100//顺序栈的初始分配空间大小 typedef char ElemType;//假设顺序栈中所有元素为char类型 typedef struct {ElemType data[MaxSize];//保存栈中元素 int top;//栈顶指针 } SqStack;//顺序栈类型 void InitStack(SqStack &st)//初始化顺序栈st { st.top=-1; } void DestroyStack(SqStack st)//销毁顺序栈st { } int Push(SqStack &st,ElemType x)//进栈元素x {if (st.top==MaxSize-1)//栈满 return 0; else {st.top++; st.data[st.top]=x; return 1; } } int Pop(SqStack &st,ElemType &x)//出栈元素x {if (st.top==-1)//栈空 return 0; else {x=st.data[st.top]; st.top--; return 1; } } int GetTop(SqStack st,ElemType &x)//取栈顶元素x {if (st.top==-1)//栈空 return 0; else {x=st.data[st.top]; return 1; } } int StackEmpty(SqStack st)//判断栈是否为空 {if (st.top==-1) return 1; else return 0; } 设计如下应用主函数。 #include "SqStack.cpp"//包含顺序栈基本运算函数 int main() {SqStack st; ElemType e; printf("初始化栈st\n"); InitStack(st); printf("栈%s\n",(StackEmpty(st)==1?"空":"不空")); printf("a进栈\n");Push(st,'a'); printf("b进栈\n");Push(st,'b'); printf("c进栈\n");Push(st,'c'); printf("d进栈\n");Push(st,'d'); printf("栈%s\n",(StackEmpty(st)==1?"空":"不空")); GetTop(st,e); printf("栈顶元素:%c\n",e); printf("出栈次序:"); while (!StackEmpty(st))//栈不空循环 {Pop(st,e);//出栈元素e并输出 printf("%c ",e); 图3.1实验程序的执行结果 } printf("\n销毁栈st\n"); DestroyStack(st); } 上述程序的执行结果如图3.1所示。 (2) 解: 字符链栈的基本运算算法设计原理参见《教程》的3.1.3节。包含链栈基本运算函数的文件LinkStack.cpp如下。 #include <stdio.h> #include <malloc.h> typedef char ElemType;//假设链栈中所有元素为char类型 typedef struct node {ElemType data;//存储结点数据 struct node *next;//指针域 } LinkStack;//链栈结点类型 void InitStack(LinkStack *&ls)//初始化链栈ls { ls=NULL; } void DestroyStack(LinkStack *&ls)//销毁链栈ls {LinkStack *pre=ls,*p; if (pre==NULL) return; p=pre->next; while (p!=NULL) {free(pre);//释放pre结点 pre=p;p=p->next;//pre、p同步后移 } free(pre);//释放尾结点 } int Push(LinkStack *&ls,ElemType x)//进栈元素x {LinkStack *p; p=(LinkStack *)malloc(sizeof(LinkStack)); p->data=x;//创建结点p用于存放x p->next=ls;//插入p结点作为栈顶结点 ls=p; return 1; } int Pop(LinkStack *&ls,ElemType &x)//出栈元素x {LinkStack *p; if (ls==NULL) //栈空,下溢出返回0 return 0; else//栈不空时出栈元素x并返回1 {p=ls;//p指向栈顶结点 x=p->data;//取栈顶元素x ls=p->next;//删除结点p free(p);//释放p结点 return 1; } } int GetTop(LinkStack *ls,ElemType &x)//取栈顶元素x {if (ls==NULL)//栈空,下溢出时返回0 return 0; else//栈不空,取栈顶元素x并返回1 {x=ls->data; return 1; } } int StackEmpty(LinkStack *ls)//判断栈是否为空栈 {if (ls==NULL) return 1; else return 0; } 设计如下应用主函数。 #include "LinkStack.cpp"//包含前面的链栈基本运算函数 int main() {LinkStack *st; ElemType e; printf("初始化栈st\n"); InitStack(st); printf("栈%s\n",(StackEmpty(st)==1?"空":"不空")); printf("a进栈\n");Push(st,'a'); printf("b进栈\n");Push(st,'b'); printf("c进栈\n");Push(st,'c'); printf("d进栈\n");Push(st,'d'); printf("栈%s\n",(StackEmpty(st)==1?"空":"不空")); GetTop(st,e); printf("栈顶元素:%c\n",e); printf("出栈次序:"); while (!StackEmpty(st))//栈不空循环 {Pop(st,e);//出栈元素e并输出 printf("%c ",e); } printf("\n销毁栈st\n"); DestroyStack(st); } 上述程序的执行结果如图3.1所示。 (3) 解: 字符循环队列的基本运算算法设计原理参见《教程》的3.2.2节。包含循环队列基本运算函数的文件SqQueue.cpp如下。 #include <stdio.h> #define MaxSize 100//循环队列的初始分配空间大小 typedef char ElemType;//假设循环队列中所有元素为char类型 typedef struct {ElemType data[MaxSize];//保存队中元素 int front,rear;//队头和队尾指针 } SqQueue; void InitQueue(SqQueue &sq)//初始化循环队列sq { sq.rear=sq.front=0;//指针初始化 } void DestroyQueue(SqQueue sq)//销毁循环队列sq { } int EnQueue(SqQueue &sq,ElemType x)//进队列元素x {if ((sq.rear+1) % MaxSize==sq.front)//队满上溢出 return 0; sq.rear=(sq.rear+1) % MaxSize;//队尾循环进1 sq.data[sq.rear]=x; return 1; } int DeQueue(SqQueue &sq,ElemType &x)//出队元素x {if (sq.rear==sq.front)//队空下溢出 return 0; sq.front=(sq.front+1) % MaxSize;//队头循环进1 x=sq.data[sq.front]; return 1; } int GetHead(SqQueue sq,ElemType &x)//取队头元素x {if (sq.rear==sq.front)//队空下溢出 return 0; x=sq.data[(sq.front+1) % MaxSize]; return 1; } int QueueEmpty(SqQueue sq)//判断循环队列sq是否为空 {if (sq.rear==sq.front) return 1; else return 0; } 设计如下应用主函数。 #include "SqQueue.cpp"//包含销毁队列的基本运算函数 int main() {SqQueue sq; ElemType e; printf("初始化队列"); InitQueue(sq); printf("队%s\n",(QueueEmpty(sq)==1?"空":"不空")); printf("a进队\n");EnQueue(sq,'a'); printf("b进队\n");EnQueue(sq,'b'); printf("c进队\n");EnQueue(sq,'c'); printf("d进队\n");EnQueue(sq,'d'); printf("队%s\n",(QueueEmpty(sq)==1?"空":"不空")); GetHead(sq,e); printf("队头元素:%c\n",e); printf("出队次序:"); while (!QueueEmpty(sq))//队不空循环 {DeQueue(sq,e);//出队元素e printf("%c ",e);//输出元素e 图3.2实验程序的执行结果 } printf("\n销毁队列\n"); DestroyQueue(sq); } 上述程序的执行结果如图3.2所示。 (4) 解: 字符链队的基本运算算法设计原理参见《教程》的3.2.3节。包含链队基本运算函数的文件LinkQueue.cpp如下。 #include <stdio.h> #include <malloc.h> typedef char ElemType;//假设链队中所有元素为char类型 typedef struct QNode {ElemType data; struct QNode *next; } QType;//链队中数据结点的类型 typedef struct qptr {QType *front;//队头指针 QType *rear;//队尾指针 } LinkQueue;//链队结点类型 void InitQueue(LinkQueue *&lq)//初始化链队lq {lq=(LinkQueue *)malloc(sizeof(LinkQueue)); lq->rear=lq->front=NULL;//初始时队头和队尾指针均为空 } void DestroyQueue(LinkQueue *&lq) //销毁链队lq {QType *pre=lq->front,*p; if (pre!=NULL)//非空队的情况 {if (pre==lq->rear)//只有一个数据结点的情况 free(pre);//释放pre结点 else//有两个或多个数据结点的情况 {p=pre->next; while (p!=NULL) {free(pre);//释放pre结点 pre=p; p=p->next;//pre、p同步后移 } free(pre);//释放尾结点 } free(lq);//释放链队结点 } } int EnQueue(LinkQueue *&lq,ElemType x)//进队元素x {QType *s; s=(QType *)malloc(sizeof(QType));//创建新结点,插入链队的末尾 s->data=x;s->next=NULL; if (lq->front==NULL)//原队为空队的情况 lq->rear=lq->front=s;//front和rear均指向s结点 else//原队不为空队的情况 {lq->rear->next=s;//将s链到队尾 lq->rear=s;//rear指向它 } return 1; } int DeQueue(LinkQueue *&lq,ElemType &x)//出队元素x {QType *p; if (lq->front==NULL)//原队为空队的情况 return 0; p=lq->front;//p指向队头结点 x=p->data;//取队头元素值 if (lq->rear==lq->front)//若原队中只有一个结点,删除后队列变空 lq->rear=lq->front=NULL; else//原队有两个或以上结点的情况 lq->front=lq->front->next; free(p); return 1; } int GetHead(LinkQueue *lq,ElemType &x)//取队头元素x {if (lq->front==NULL)//原队为队空的情况 return 0; x=lq->front->data;//原队不空的情况 return 1; } int QueueEmpty(LinkQueue *lq)//判断队列ls是否是空 {if (lq->front==NULL) return 1;//队空返回1 else return 0;//队不空返回0 } 设计如下应用主函数。 #include "LinkQueue.cpp"//包含链队的基本运算函数 int main() {LinkQueue *lq; ElemType e; printf("初始化队列\n"); InitQueue(lq); printf("队%s\n",(QueueEmpty(lq)==1?"空":"不空")); printf("a进队\n");EnQueue(lq,'a'); printf("b进队\n");EnQueue(lq,'b'); printf("c进队\n");EnQueue(lq,'c'); printf("d进队\n");EnQueue(lq,'d'); printf("队%s\n",(QueueEmpty(lq)==1?"空":"不空")); GetHead(lq,e); printf("队头元素:%c\n",e); printf("出队次序:"); while (!QueueEmpty(lq))//队不空循环 {DeQueue(lq,e);//出队元素e printf("%c ",e);//输出元素e } printf("\n销毁队列\n"); DestroyQueue(lq); } 上述程序的执行结果如图3.2所示。 2. 应用实验题 (1) 解: 给定栈st,建立一个临时栈tmpst并初始化。退栈st的所有元素x,当x不为e时将其进栈到tmpst中。将tmpst的所有元素退栈并进栈到st中。最后销毁临时栈tmpst。对应的实验程序如下。 #include "SqStack.cpp"//包含顺序栈的基本运算函数 void Dele(SqStack &st,ElemType e)//求解算法 {ElemType x; SqStack tmpst; InitStack(tmpst); while (!StackEmpty(st))//栈st不空循环 {Pop(st,x);//出栈元素x if (x!=e)Push(tmpst,x); } while (!StackEmpty(tmpst))//栈tmpst不空循环 {Pop(tmpst,x);//出栈元素x Push(st,x); } DestroyStack(tmpst);//销毁栈tmpst } int main() {SqStack st; printf("初始化栈st\n"); InitStack(st); printf("元素1,2,2,1,2,3依次进栈\n"); Push(st,'1'); Push(st,'2'); Push(st,'2'); Push(st,'1'); Push(st,'2'); Push(st,'3'); char e='2'; printf("删除所有元素%c\n",e); Dele(st,e); printf("出栈序列: "); while (!StackEmpty(st)) {Pop(st,e); printf("%c ",e); } printf("\n销毁栈\n"); DestroyStack(st); } 上述程序的执行结果如图3.3所示。 图3.3实验程序的执行结果 (2) 解: 设计Pushk(SqStack &st,int k,ElemType &e)算法,如果栈中没有第k个元素,返回0; 否则返回1,并通过引用型参数e保存第k个元素。 先建立并初始化一个临时栈tmpst,置表示栈中能否找到第k个元素的变量tag为0。出栈st中所有元素,并用i记录出栈元素x的序号,当i==k时置e=x和tag=1; 否则将元素x进栈到tmpst中。出临时栈tmpst的所有元素并进栈到st中,最后销毁tmpst并返回tag。对应的实验程序如下。 #include "SqStack.cpp"//包含顺序栈的基本运算函数 int Pushk(SqStack &st,int k,ElemType &e)//求解算法 {int i=0,tag=0; ElemType x; SqStack tmpst;//定义临时栈 InitStack(tmpst);//初始化临时栈 while (!StackEmpty(st))//将st中所有元素出栈并进栈到tmpst中(除第k个元素外) {i++; Pop(st,x); if (i==k) {e=x; tag=1;//存在第k个元素 } else Push(tmpst,x); } while (!StackEmpty(tmpst))//出栈tmpst元素并进栈st {Pop(tmpst,x); Push(st,x); } DestroyStack(tmpst);//销毁临时栈 return tag; } int main() {SqStack st; printf("初始化栈st\n"); InitStack(st); printf("元素6到1依次进栈\n"); Push(st,'6'); Push(st,'5'); Push(st,'4'); Push(st,'3'); Push(st,'2'); Push(st,'1'); int k=5; char e; printf("删除第%d个元素,",k); if (Pushk(st,k,e)) printf("对应的元素是:%c\n",e); else printf("该元素不存在\n"); printf("出栈序列: "); while (!StackEmpty(st)) {Pop(st,e); printf("%c ",e); } printf("\n销毁栈\n"); DestroyStack(st); } 上述程序的执行结果如图3.4所示。 图3.4实验程序的执行结果 (3) 解: 采用一个临时栈st(用于暂时存放还没有与a中元素匹配的整数),从i=1~n循环,j=0扫描数组a的元素。先将i进栈,栈不空时取栈顶元素e,若a[j]==e,则元素e退栈,j++继续栈元素的判断; 若a[j]≠e,退出栈不空的循环,让i++继续下一个i的判断。整个循环结束,栈空表示可以产生a的出栈序列; 否则不能产生a的出栈序列。 说明: 由于SqStack.cpp文件中的顺序栈对应的栈元素类型为char,这里要求栈元素类型为int,将SqStack.cpp复制到SqStack1.cpp文件,将其中的ElemType声明改为int即可。 对应的实验程序如下。 #include "SqStack1.cpp"//包含顺序栈(栈元素为int类型)的基本运算函数 int Validseq(int a[],int n)//求解算法 {int i,j,e; SqStack st;//定义一个顺序栈st InitStack(st);//初始化栈st j=0; for(i=1;i<=n;i++)//处理进栈序列1,2,…,n {Push(st,i);//整数i进栈 printf("%d进栈\n",i); while (!StackEmpty(st))//栈不空循环 {GetTop(st,e);//取栈顶元素e if (a[j]==e)//匹配的情况 {Pop(st,e);//退栈元素e printf("%d退栈\n",e); j++; } else break;//不匹配时退出while循环 } } if (StackEmpty(st))//栈空返回true;否则返回false {DestroyStack(st);//销毁栈st return true; } else {DestroyStack(st);//销毁栈st return false; } } void dispa(int a[],int n)//输出a {for (int i=0;i<n;i++) printf("%d ",a[i]); printf("\n"); } void display(int a[],int n)//测试一个序列a {printf("测试 a:"); dispa(a,n); if (Validseq(a,n)) printf("a是合法序列\n"); else printf("a不是合法序列\n"); } int main() {printf("测试结果:\n"); int n=5;//假设测试的进栈序列为1~5 图3.5实验程序的执行结果 int a[]={2,1,5,4,3}; display(a,n); int a1[]={5,1,2,3,4}; display(a1,n); } 上述程序的执行结果如图3.5所示。 (4) 解: 由于算法要求空间复杂度为O(1),所以不能使用临时队列。先求出队列qu中的元素个数m。用i循环m次,每次出次一个元素x,若i<m则将元素x进队; 否则元素x不进队,从而删除了队尾元素。对应的实验程序如下。 #include "SqQueue.cpp"//包含循环队列的基本运算 //函数 int Count(SqQueue sq)//求循环队列qu中元素个数 { return(sq.rear+MaxSize-sq.front) % MaxSize; } int DelLast(SqQueue &qu,ElemType &x)//求解算法 {int i,m=Count(qu); if (m==0) return 0; for (i=1;i<=m;i++) {DeQueue(qu,x);//出队元素x if (i<m) EnQueue(qu,x);//将元素x进队 } return 1; } int main() {SqQueue qu; printf("初始化循环队列qu\n"); InitQueue(qu); printf("元素1到5依次进队\n"); EnQueue(qu,'1'); EnQueue(qu,'2'); EnQueue(qu,'3'); EnQueue(qu,'4'); EnQueue(qu,'5'); char e; DelLast(qu,e); printf("删除队尾元素%c\n",e); printf("出队序列: "); while (!QueueEmpty(qu)) {DeQueue(qu,e); printf("%c ",e); 图3.6实验程序的执行结果 } printf("\n销毁队列\n"); DestroyQueue(qu); } 上述程序的执行结果如图3.6所示。 (5) 解: 设计本实验题中队列的类型如下。 typedef struct {ElemType data[MaxSize]; int front,rear;//队头和队尾指针 int tag;//为0表示可能队空,为1时表示可能队满 } QueueType;//循环队列类型 初始时tag=0,进行任何一次成功的进队操作后tag=1(因为只有在进队操作后队列才有可能满),进行任何一次成功的出队操作后tag=0(只有在出队操作后队列才有可能空),因此,这样的队列的基本要素如下。 ① 初始时: tag=0,front=rear。 ② 队空条件: qu.front==qu.rear && qu.tag==0。 ③ 队满条件: qu.front==qu.rear && qu.tag==1。 对应的实验程序如下。 #include <stdio.h> #define MaxSize 100 typedef char ElemType; typedef struct {ElemType data[MaxSize]; int front,rear;//队头和队尾指针 int tag;//为0表示可能队空,为1时表示可能队满 } QueueType;//循环队列类型 void InitQueue1(QueueType &qu)//初始化队列 {qu.front=qu.rear=0; qu.tag=0;//为0表示可能为空 } void DestroyQueue1(QueueType qu)//销毁队列 { } int QueueEmpty1(QueueType qu)//判队空 { return(qu.front==qu.rear && qu.tag==0); } int QueueFull1(QueueType qu)//判队满 { return(qu.tag==1 && qu.front==qu.rear); } int EnQueue1(QueueType &qu,ElemType x)//进队算法 {if (QueueFull1(qu)==1)//队满则返回0 return 0; qu.rear=(qu.rear+1)%MaxSize; qu.data[qu.rear]=x; qu.tag=1;//至少有一个元素,可能满 return 1;//成功进队,返回1 } int DeQueue1(QueueType &qu,ElemType &x)//出队算法 {if (QueueEmpty1(qu)==1)//队空则返回0 return 0; qu.front=(qu.front+1)%MaxSize; x=qu.data[qu.front]; qu.tag=0;//出队一个元素,可能空 return 1;//成功出队,返回1 } int main() {QueueType qu; printf("初始化队列qu\n"); InitQueue1(qu); printf("元素1到5依次进队\n"); EnQueue1(qu,'1'); EnQueue1(qu,'2'); EnQueue1(qu,'3'); EnQueue1(qu,'4'); EnQueue1(qu,'5'); char e; printf("出队序列: "); while (!QueueEmpty1(qu)) {DeQueue1(qu,e); printf("%c ",e); } printf("\n销毁队列\n"); DestroyQueue1(qu); } 图3.7实验程序的执行结果 上述程序的执行结果如图3.7所示。 (6) 解: 约瑟夫问题已在《教程》例2.21中采用循环单链表求解,这里采用循环队列求解。 定义一个整数队列sq,先将1~n(对应n个人的编号)依次进队,计数器i置为0。在队不空时循环: 连续出队元素p,用i累计报数的次数,当i%m≠0时表示还没有数到为m的人,将出队的p进队; 当i%m=0时表示恰好数到为m的人,将p不进队并出列。对应的算法如下。 说明: 由于SqQueue.cpp文件中的循环队列对应的队列元素类型为char,这里要求队列元素类型为int,将SqQueue.cpp复制到SqQueue1.cpp文件,将其中ElemType声明改为int即可。 对应的实验程序如下。 #include "SqQueue1.cpp"//包含循环队列(队列元素为int类型)基本运算函数 void Josephus(int n,int m)//用队列求解约瑟夫问题 {int i,p; SqQueue sq;//定义一个顺序队sq InitQueue(sq);//初始化队列 for (i=1;i<=n;i++)//n个人进队 EnQueue(sq,i); printf(" 出列顺序:"); i=0; while (!QueueEmpty(sq))//队不空循环 {DeQueue(sq,p);//出队元素p i++;//出队个数计数 if (i % m==0)//循环报数到m printf("%d ",p);//出列p else EnQueue(sq,p);//将p进队 } printf("\n"); DestroyQueue(sq);//销毁队列 } void display(int n,int m)//测试一个约瑟夫问题 {printf("n=%d,m=%d ",n,m); Josephus(n,m); } int main() {printf("测试结果:\n"); display(6,3); display(6,5); display(5,8); display(4,2); } 上述程序的执行结果如图3.8所示。 图3.8实验程序的执行结果