• 1. 第一章作业 9月30日截止 已交34份 第二章作业 10月14日截止(选择题6可以先不做) 实验一 原线性表实验(例2-1、2-2)改为课下练习 本学期实验一改为一元多项式 要求用顺序表和链表两种方式实现缺项多项式相加 第六周周二实验课上检查基本功能完成情况 实验报告10月21日截止作业与实验说明
    • 2. 截止9月25日 实交88份 共89人,1人未交 互评作业提交81人,8人未提交 完成情况 2人作业无有效内容或提交错误文件,记0分 互评作业,部分同学无有效内容,很大一部分同学没有参加互 实验一 第六周周二实验课上检查基本功能完成情况 实验报告10月15日截止 第一章作业
    • 3. 第三章 栈和队列
    • 4. 43.3 栈与递归的实现 **3.1 栈3.2 栈的应用举例3.4 队列3.5 离散事件模拟(略) 引言
    • 5. 线性表 栈和队列的基本操作是线性表操作的子集,是限定性(操作受限制)的数据结构。 特征 栈和队列是限定插入和删除只能在表的“端点”进行的线性表。 栈:仅在表尾进行操作,后进先出 队列:表尾进表头出,先进先出概述栈和队列是两种常用的数据结构 线性表 栈 队列 Insert(L, i, x) Insert(S, n+1, x) Insert(Q, n+1, x) 1≤i≤n+1 Delete(L, i) Delete(S, n) Delete(Q, 1) 1≤i≤n
    • 6. 3.1 栈定义:是限定仅在表尾进行插入或删除操作的线性表。 栈底 (base) :是表头。 栈顶 (top):是表尾。基本操作: 栈的插入操作,常称入栈、进栈、压栈;对删除操作,常称出栈、退栈、弹栈。 栈又称后进先出表,简称LIFO表(LIFO:Last In First Out)。
    • 7. 7 ADT Stack { 数据对象: D={ ai | ai ∈ElemSet, i=1,2,...,n, n≥0 } 数据关系: R1={ <ai-1, ai >| ai-1, ai∈D, i=2,...,n } 约定an 端为栈顶,a1 端为栈底。 基本操作: InitStack(&S), DestroyStack(&S), StackEmpty(S) , ClearStack(&S), GetTop(S ,&e), StackLength(S) , Push(&S, e): 完成在表尾插入一个元素e. Pop(&S,&e): 完成在表尾删除一个元素。 } ADT Stack抽象数据类型栈的定义
    • 8. 8栈的表示和实现 与线性表类似,栈也有顺序存储和链式存储两种表示方法,并分别称为顺序栈和链栈。 由于栈的插入和删除操作固定在表的一端,因此,更多的时候是使用顺序栈,而较少使用链栈。
    • 9. 9顺序栈—静态分配方式 可用一维数组S[1..n]来实现顺序栈,其中n是栈容量。可以将S[1]作栈底,并用一个称作“栈顶指针”的变量top来指示栈顶的当前位置,当top=0时表示栈空,当top=n时表示栈满。【演示】
    • 10. 10顺序栈—动态分配方式 顺序栈的静态分配方式限定了栈容量,这给很多应用带来不便,故常采用动态分配方式实现顺序栈,栈满之后,可再追加栈空间即为动态栈。#define STACK_INIT_SIZE 100 //初始容量 #define STACKINCREMENT 10 //容量增量 typedef struct { SElemType *base; //栈底指针 SElemType *top; //栈顶指针(指向栈顶的后一个位置) int stacksize; //当前容量 }SqStack;【详见“SqStack.h”】【演示】
    • 11. 顺序栈中base和top的特点(P46页图3.2):(1)base称为栈底指针,在顺序栈中,它始终指向栈底的位置。若base的值为NULL,则表明栈结构不存在;若栈存在,则base始终指向栈的首地址(基址)。简言之非空的栈顶指针始终在栈顶元素的下一个位置。(2)top为栈顶指针,其初值指向栈底,即top=base可作为栈空的标记。(3)当插入一个新的栈顶元素时,指针top增1;删除栈顶元素时,指针top减1,
    • 12. Status InitStack(Stack *S) { S.base=(ET *)malloc(STACK_INIT_SIZE *sizeof(ET)); /*为栈分配空间*/ if(!S.base) exit(OVERFLOW); /*存储分配失败*/ S.top=S.base; //初始栈顶 S.stacksize=STACK_INIT_SIZE; //初始容量 return OK; } 建立空栈
    • 13. Status GetTop(SqStack S,SElemType &e) { if( S.top==S.base) return ERROR; /*栈空*/ e=*(S.top-1); return OK; } 取栈顶元素
    • 14. 出栈(弹栈)操作PopStatus Pop(Stack *S,ET *e){ if( S.top==S.base ) return ERROR; --S.top; *e=* S.top; //或者*e=*--S.top; return OK; }出栈操作topABYtopABYbasebase重要规律:出栈时先动指针,后操作元素。注意操作符优先级顺序
    • 15. 进栈(压栈)操作PushStatus Push(Stack *S,ET e) { if( S->top-S->base>=S-> stacksize) { S->base= (ET *) realloc(S-> base, (S- > stacksize + STACKINCREMENT) * sizeof(ET)); if(!S->base) exit(OVERFLOW); S->top=S->base+S->stacksize; S->stacksize+=STACKINCREMENT; } //空间不够时增加空间 *S->top++=e; return OK; }压栈操作topABbasetopABebase重要规律:入栈时先放元素再动指针。
    • 16. void Destroy_Stack(Stack *S) { if(S->base) free(S->base); S->base=NULL; S->top=NULL; S->stacksize=0; } 栈的销毁
    • 17. void Clear_Stack(Stack *S){ S->top=S->base; }栈的清除判断栈是否为空Status StackEmpty(Stack *S) { if(S->top==S->base) return TRUE; else return FALSE; }
    • 18. 思考:栈的链式表示如何实现?
    • 19. 19链栈 可用不带头结点的单链表来实现链栈【实现见“LkStack.h”】
    • 20. 课堂练习题3.2写出下列程序段的输出结果(栈的元素类型Selem,实际类型为char)void main() { Stack S; char x,y; InitStack(S); x=‘c’;y=‘k’; Push(S,x); Push(S, ‘a’); Push(S,y);Pop(S,x);Push(S, ‘t’);Push(S,x); Pop(S,x);Push(S, ‘s’); while(!StackEmpty(S)) { Pop(S,y); printf((y); } printf(x); }
    • 21. 21十进制数N转换为d进制数算法原理: N = (N div d) * d + N mod d (1348)10=(2504)8 运算过程:【算法见“A3_1.cpp”】NN div 8N mod 8134816841682102125202应用举例-特点:计算过程从低位到高位产生,输出从高位到低位。
    • 22. 22例二 括号匹配的检验正确: ([]())或[([ ][ ])] 错误: [( ])或([( ))或 (()])[紧转下页]检验括号是否匹配的方法可用 “期待的急迫程度”这个概念来描述。
    • 23. 23例二 括号匹配的检验分析可能出现的不匹配的情况:到来的右括号是“不速之客” 到来的右括号并非“期待” 直到结束,也没有到来所“期待”如:考虑下列括号序列: [ ( [ ] [ ] ) ] 1 2 3 4 5 6 7 8[紧转下页]
    • 24. 24例二 括号匹配的检验 栈的应用举例【算法实现见“Matching.cpp”】算法思路:1)凡出现左括弧,则进栈;2)凡出现右括弧,首先检查栈是否空 若栈空,则表明该“右括弧”多余, 否则和栈顶元素比较, 若相匹配,则“左括弧出栈” , 否则表明不匹配。3)表达式检验结束时, 若栈空,则表明表达式中匹配正确, 否则表明“左括弧”有余。
    • 25. 25例三 行编辑程序 功能:接收输入,存入用户数据区。 由于输入可能出错,若“每接受一个字符即存入用户数据区”显然不恰当。 合理的作法是: 设立一个输入缓冲区,用以接受用户输入的一行字符,然后逐行存入用户数据区。
    • 26. 26例三 行编辑程序 栈的应用举例假设“#”为退格符,“@”为退行符。 若从终端接受了这样两行字符: whli##ilr#e (s#*s) outcha@putchar(*s=#++); 则实际有效的是下列两行: while (*s) putchar(*s++); 为此,可用栈作为输入缓冲,有算法3.2:
    • 27. 27void LineEdit(…) {//算法3.2 InitStack(S); ch = getchar(); while (ch != EOF) { //EOF为全文终结符 while (ch != EOF && ch != '\n') {//2 switch (ch) { case '#' : Pop(S, c); break; case '@': ClearStack(S); break; default : Push(S, ch); break; }//end switch ch = getchar(); }//end while 2 将从栈底到栈顶的字符送至数据区; ClearStack(S); if (ch != EOF) ch = getchar(); }//end while(ch != EOF) DestroyStack(S); }//LineEdit【详见“LineEdit.cpp”】
    • 28. 28例四 迷宫求解 栈的应用举例###########→ ↓ #$$$###↓ #$$$###↓ ← $$####↓ ######→ → ↓ #####→ → ↓ #######↓ ####→ → → ◎ ###########【演示】
    • 29. 29算法(粗) :若当前位置“可通”,则纳入路径,继续前进;若当前位置“不通”,则原路后退,换方向继续探索;若四周“均无通路”,则将当前位置从路径中删除出去。例四 迷宫求解 栈的应用举例【算法(细)见“迷宫算法.htm”】【算法实现见“…\例题源代码\A3_3\”】
    • 30. 30算术四则运算规则:例五 表达式求值 根据运算的优先关系可以实现对表达式的解释及求值,这就是所谓算符优先法。先乘除,后加减 先左后右 先括号内,后括号外
    • 31. 31设一操作数栈和一运算符栈从左到右扫描表达式的符号,若是操作数则进栈;若是运算符,则根据最近两个运算符的优先关系来决定是进栈还是作别的处理例五 表达式求值 栈的应用举例算符优先法基本思路:【算法实现见“…\例题源代码\A3_4\”】【演示】
    • 32. 32函数调用的信息交换方式 栈与递归的实现**将所有的实在参数、返回地址等信息传递给被调用函数保存 调用函数与被调用函数之间的控制转移和信息交换是通过栈来进行的。调用之前,需先完成三件事:将控制转移到被调用函数的入口为被调用函数的局部变量分配存储区
    • 33. 33函数调用的信息交换方式 栈与递归的实现** 保存被调函数的计算结果 释放被调函数的数据区 依照被调函数保存的返回地址将控制转移到调用函数 被调用函数执行后需返回到调用函数,在返回之前,应该完成:多个函数嵌套调用的规则是: 后调用先返回 此时的内存管理实行“栈式管理” 递归是函数嵌套调用的特例
    • 34. 例2:汉诺塔例2:Hanoi问题:设有三座塔,依次命名为X、Y、Z,有n个直径不相同的圆盘,由小到大依次编号为1,2,3,….n,开始时,它们前部按大小递减的顺序插在塔座X上,现在要求把n个圆盘按大小递减的顺序放在塔座Z上。其移动规则如下: 1、每次只能移动一个圆盘; 2、圆盘可以放在任一个塔座上; 3、任何时刻不能将大圆盘压在小圆盘上;123123XYZXYZ
    • 35. 35例 Hanoi塔问题 栈与递归的实现**变递归为非递归采用了如下比较直观的方法: 1.把要解决的问题即任务放入“任务栈” 2.若任务栈已空,则结束;否则从栈顶取出一个任务 3.若取出的任务不能直接完成,则将其分解为几个小点的子任务并按子任务的执行顺序之反序入栈,到2 4.若取出的任务能直接完成,则完成它,到2【图示】【递归算法代码见“…\Hanoi-递归\”】【非递归算法代码见“…\Hanoi-非递归\”】
    • 36. 递归过程(3,x,y,z)(2,x,z,y)(1,x,y,z) —— move(x,1,z)move(x,2,y)(1,z,x,y) —— move(z,1,y)move(x,3,z)(2,y,x,z)(1,y,z,x) —— move(y,1,x)move(y,2,z)(1,x,y,z) —— move(x,1,z)
    • 37. int count=0; void hanoi(int n,char x,char y, char z) { if(n == 1) move(x,1,z); else{ hanoi(n-1,x,z,y); /*递归调用*/ move(x,n,z); /*将x上编号为n的盘放到z上*/ hanoi(n-1,y,x,z); } } void move(char x, int n, char z) { printf(“%d: move disk %d from %c to %c\n”,++count, n,x,z); return; } 递归算法
    • 38. InitStack(S); //初始化任务栈 //初始任务e: e.n = n; //盘数 e.x = x; //源塔 e.y = y; //辅助塔 e.z = z; //目标塔 e.h = n; //(最底层)盘号 Push(S, e); //任务入栈 while (Pop(S, e) == OK) //取出当前任务 {//只要栈中有任务便继续: if (e.n == 1) //若n=1,(可直接解决) move(e.x, e.h, e.z); //则移x的h号盘到z else //e.n > 1 {//无法直接解决,分解任务为3个子任务: // 1. hanoi(n-1, x, z, y) 即移x上的n-1个盘到y, z作辅助塔 // 2. move(x, n, z) 即移x上的1个盘(n号盘)到z // 3. hanoi(n-1, y, x, z) 即移y上的n-1个盘到z, x作辅助塔 e1.n = e.n-1; e1.x = e.x; e1.y = e.z; e1.z = e.y; e1.h = e.n-1; //子任务1 e2.n = 1; e2.x = e.x; e2.y = e.y; e2.z = e.z; e2.h = e.n; //子任务2 e3.n = e.n-1; e3.x = e.y; e3.y = e.x; e3.z = e.z; e3.h = e.n-1; //子任务3 //然后子任务反序入栈。 Push(S, e3); //子任务3入栈 Push(S, e2); //子任务2入栈 Push(S, e1); //子任务1入栈 } //end if }//end while DestroyStack(S); //销毁栈 }//hanoi 非递归算法
    • 39. 练习作业实现3.2.3行编辑程序尝试实现3.2.5表达式求值尝试思考并实现3.2.4迷宫求解思考实现3.2.2括号匹配的检验算法,能处理包括()、[]和 {}的三类括号。算法3.4中对于操作数的假设是否合理,如何处理?
    • 40. 复习回顾栈的定义和特点顺序栈中base和top的特点顺序栈中的出栈算法和入栈算法栈的应用
    • 41. 3.3 栈与递归的实现递归函数的定义:一个直接调用自己或通过一系列的调用语句间接地调用自己的函数。将所有的实在参数、返回地址等信息传递给被调用函数保存;调用函数前,系统需先完成三件事:为被调用函数的局部变量分配存储区;将控制转移到被调用函数的入口。任意两个函数之间调用的运行过程
    • 42. 保存被调函数的计算结果;从被调用函数返回调用函数之前,系统需先完成三件事:释放被调函数的数据区;依照被调函数保存的返回地址将控制转移到调用函数。函数之间的信息传递和控制转移必须通过“栈”来实现:系统将整个程序运行时所需的数据空间安排在一个栈中,每当调用一个函数时,就为它在栈顶分配一个存储区;每当从一个函数退出时,就释放它的存储区,则当前正运行的函数的数据区必在栈顶。 ( Page 56 图3.6 )结论:系统中函数调用是通过栈来实现的。 函数的嵌套调用时,按照“后调用先返回”的原则
    • 43. 栈的应用一个递归函数的运行过程类似于多个函数的嵌套调用,只是调用函数和被调用函数是同一个函数。因为递归函数调用每次运行的函数名一样,因此需要用的运行的“层次”加以区分。
    • 44. 例1.已知f(m,n)函数(m,n均为自然数)的定义如下: m+n+1 (m*n=0) f(m,n)= f(m-1,f(m,n-1)) (m*n≠0) (1)写出计算f(m,n)的递归算法。 (2)利用堆栈改写出非递归算法。 (3)根据非递归算法,画出计算f(2,1)时实参、局部变量以及堆栈的变化情况。 例1:Ackerman函数
    • 45. int f(int m,int n){ if(m*n==0) return(m+n+1); else return( f (m-1, f(m,n-1) ) ); } 递归算法
    • 46. 1 , 20 , 1, 10, 2,12 , 01 , 1,00,F函数递归示意图 (每个箭头代表调用一次f )32345int f(int m,int n){ if(m*n==0) return(m+n+1); else { x=f(m,n-1) ; y=f(m-1, x) ; return(y);} } }53453234
    • 47. F函数的非递归算法int result; int F(int m,int n){ if(m>=0 && n>=0){ InitStack(S); push(S, m , n); while(!StackEmpty(S) ){ pop(S, m , n); if (n==-1) n=result; if (m*n= =0) {result = m+n+1;} else {push(S,m-1,-1);push(S,m,n-1);} } return result; } } //typedef struct{int m,n;}SElemType;
    • 48. while(!StackEmpty(S) ) { pop(S, m , n); if(n==-1) n=result; if(m*n= =0) result = m+n+1; else { push(S,m-1,-1); push(S,m,n-1); } }标号MNResultS1,2条语句212,1第一次循环212,0 1,-1第二次循环2031,-1第三次循环1331,2 0,-1第四次循环1231,1 0,-1 0,-1第五次循环1131,0 0,-1 0,-1 0,-1第六次循环1020,-1 0,-1 0,-1第七次循环0230,-1 0,-1第八次循环0340,-1第九次循环045NULL
    • 49. 3.4 队列定义:是一种先进先出(FIFO)的线性表,它只允许在表的一端进行插入,而在另一端删除元素。队头(front)——是删除元素(出队操作)端; 队尾(rear)——是插入元素(入队操作)端。队中的元素个数称队长,队列中不含元素时称空队。插入操作称入队,删除操作称出队。 【演示】
    • 50. 50 ADT Queue { 数据对象: D={ai | ai∈ElemSet, i=1,2,...,n, n≥0} 数据关系: R1={ <a i-1,ai > | ai-1, ai ∈D, i=2,...,n} 约定其中a1 端为队头, an 端为队尾基本操作:P59} ADT Queue抽象数据类型队列的定义
    • 51. 基本操作基本操作: InitQueue(&Q) DestroyQueue(&Q) ClearQueue(&Q) QueueEmpty(Q) QueueLength(Q) GetHead(Q,&e) EnQueue(&Q, e) DeQueue(&Q,&e)
    • 52. 头5836 ^front rearQ.头^front rearQ.LinkQueue Q ;队列:出队端入队端空队列:采用链式存储结构表示的队列简称为链队列或链队。一个链队需要一个头指针和一个尾指针才能唯一确定。为了操作方便,在队首元素前附加一个头结点,队列的头指针就指向头结点。【链队实现见“LkQueue.h”】
    • 53. 链式队列typedef int QElemType; /* 队列元素类型 */ typedef struct QNode{ QElemType data; struct QNode *next; } Qnode; /* 队列结点类型 */ typedef struct Queue{ Qnode *rear ; Qnode *front ; } LinkQueue; /* 链式队列类型 */
    • 54. 链式队列的基本操作创建一个空队列Status InitQueue(LinkQueue *Q){ p=(Qnode *)malloc(sizeof(Qnode)); if(!p) exit(OVERFLOW); p->next = NULL; Q->front =p; Q->rear =p; return OK; } //只有头结点
    • 55. 队首队尾frontrear……^队首队尾frontrear……^入队操作Status EnQueue(LinkQueue *Q,QElemType e) { p=(Qnode *)malloc(sizeof(Qnode)); if(!p) return (OVERFLOW); p->data=e; p->next=NULL; Q->rear->next=p; Q->rear=p; return OK; }
    • 56. 队首队尾frontrear……^队首front……队尾^rear出队操作Status DeQueue(LinkQueue *Q,QElemType *e) { /* 在front 处删除一个元素,把值送入e中*/ if(Q->front==Q->rear) return ERROR; p = Q->front->next; *e=p->data; Q->front->next=p->next; if(Q->rear= =p) Q->rear=Q->front; free(p); return OK; }
    • 57. 循环队列_队列的顺序表示和实现 若简单地用向量表示队列,初始化建空队时,令front=rear=0。入队时,rear++;出队时,front++。则可能出现“假满”现象。【演示】 对假满问题的一个解决办法是将向量想象为一个环形,称之为循环队列。入队时,尾指针rear加1,但当 rear = MAXQSIZE – 1 (其中,MAXQSIZE是队列容量)时,rear不再加1,而是令其为0。 两种情况可统一为:rear = (rear + 1) % MAXQSIZE 类似地,出队时,front加1,但当front = MAXQSIZE - 1 时,front不再加1,而是令其为0。 两种情况可统一为:front = (front + 1) % MAXQSIZE
    • 58. 58 新的问题:怎样判断队空和队满? 解决办法:少用一个空间。【演示】队满的条件就变成:front = (rear + 1) % MAXQSIZE 而队空条件则是:front = rear循环队列_队列的顺序表示和实现
    • 59. #define MAXQSIZE 100 typedef struct{ QElemType *base; /*初始化的动态分配存储空间*/ int front; /*头指针,若队列不空,指向队列头元素*/ int rear; /*尾指针,队列不空时指向队尾的下一个元素*/ } SqQueue; /*队列名称*/ 代码Sqqueue.c
    • 60. ……必须熟练掌握这些算法…... InitQueue(&Q) DestroyQueue(&Q) ClearQueue(&Q) QueueEmpty(Q) QueueLength(Q) GetHead(Q,&e) EnQueue(&Q, e) DeQueue(&Q,&e) 循环队列的基本操作
    • 61. Status InitQueue(SqQueue *Q) { Q->base=(ET *)malloc(MAXQSIZE*sizeof(ET)); if(!Q->base) exit(OVERFLOW); Q->front=Q->rear=0; return OK; } 初始化队列
    • 62. 入队列Status EnQueue(SqQueue *Q,ET e) { if((Q->rear+1)%MAXQSIZE==Q->front) return ERROR; Q->base[Q->rear]=e; Q->rear=(Q->rear+1)%MAXQSIZE; return OK; } Q.front=0 Q.rear=3J1J2J4J30123 队满,不能插入,此方法少用一个元素空间。
    • 63. 出队列Status DeQueue(SqQueue *Q,ET *e){ if(Q->front==Q->rear) return ERROR; *e=Q->base[Q->front]; Q->front=(Q->front+1)%MAXQSIZE; return OK;}Q.front=1 Q.rear=3J1J20123J4J3
    • 64. Status QueueLength(SqQueue *Q) { return (Q->rear-Q->front+MAXQSIZE)%MAXQSIZE; }Status GetHead(SqQueue *Q,ET *e){ if(Q->front==Q->rear) return ERROR; *e=Q->base[Q->front]; return OK; }求队列长度获取队列首元素
    • 65. void menu() { printf("\n* * * * * * * * * * * * * * * * * * * * \n\n"); printf(" 1 ------- EnQueue a data in the Queue.\n"); printf(" 2 ------- DeQueue the Queuefront data in the Queue.\n"); printf(" 3 ------- PrintFront.\n"); printf(" 6 ------- Exit.\n"); printf("* * * * * * * * * * * * * * * * * * * * \n\n"); }代码
    • 66. void menuselect(SqQueue *Q) { int k,done=1; ET e; while (done) { menu(); printf("please choose:"); scanf("%d",&k); getchar(); switch(k){ case 1: printf("Input the data you want to Insert:"); e=getchar(); printf("\n"); EnQueue(Q,e); break; case 2:DeQueue(Q,&e);printf("e = %c\n",e); break; case 3:if(GetHead(Q,&e)==ERROR) printf("It is empty!\n"); else printf("e = %c\n",e);break; case 6:done=0; } } }
    • 67. void main() { SqQueue Q; int i,j; ET e; InitQueue(&Q); printf("Input the number of the Queue data:"); scanf("%d",&i); getchar(); printf("\n"); printf("Input the data of the Queue:\n"); for (j=1;j<=i;j++){ e=getchar(); EnQueue(&Q,e); } menuselect(&Q); }
    • 68. 算法练习请编写循环链队的出、入队算法。
    • 69. 复习回顾队的定义和特点顺序队列中队头和队尾的特点一般顺序队列的缺点循环顺序队列的特点如何让顺序队列实现循环,代码如何表示?如何判断循环顺序队列满和空,代码如何表示?
    • 70. 本章重点堆栈和队列的基本概念和基本操作;堆栈和队列分别用顺序结构和链式结构实现基本操作;堆栈及队列的应用及程序设计。
    • 71. 栈的作业实现3.2.3行编辑程序并写报告尝试实现3.2.5表达式求值尝试思考并实现3.2.4迷宫求解思考实现3.2.2括号匹配的检验算法能处理包括()、[]和 {}三类括号的算法。
    • 72. 如果希望循环队列中的元素都能得到利用,则需设置一个标志域tag,并以tag的值为0或1来区分,尾指针和头指针值相同时的队列状态是“空”还是“满”。试编写与此结构相应的入队列和出队列的算法,并从时间和空间角度讨论设标志和不设标志这两种方法的使用范围(如当循环队列容量较小时,队列中每个元素占的空间较多时,哪一种方法较好。)。 队的作业