680 likes | 964 Views
第 4 章 栈和队列. 栈 栈的应用 队列 队列的应用. 栈的定义. 栈: 限定仅在表尾进行插入和删除的线性表 ,又称后进先出( Last In First 0ut 或简称 LIFO )的线性表。 栈顶: 允许进行插入和删除的一端。 栈底: 不允许插入和删除的另一端 。 实例:. 栈的运算. 初始化: 建立一个空栈。 入栈 : 在栈中加入一个新元素。 出栈: 删除栈中的栈顶元素。 取栈顶: 读栈中的栈顶元素。 判空: 测试栈是否为空。. 栈的表示方式. 静态的数组表示: 栈的顺序存储结构,常常以一个固定大小的数组来表示栈。
E N D
第4章 栈和队列 栈 栈的应用 队列 队列的应用
栈的定义 栈:限定仅在表尾进行插入和删除的线性表 ,又称后进先出(Last In First 0ut或简称LIFO)的线性表。 栈顶:允许进行插入和删除的一端。 栈底:不允许插入和删除的另一端 。 实例:
栈的运算 初始化:建立一个空栈。 入栈 :在栈中加入一个新元素。 出栈:删除栈中的栈顶元素。 取栈顶:读栈中的栈顶元素。 判空:测试栈是否为空。
栈的表示方式 静态的数组表示:栈的顺序存储结构,常常以一个固定大小的数组来表示栈。 动态的链表表示:用链表的结构来表示栈。
栈的顺序存储结构 顺序栈:用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,设指针top指示栈顶元素在顺序栈中的位置。 顺序栈数据结构可表示为: Typedef struct { int stacksize; Selemtype *bottom; SelemType *top; }SqlStack; /*顺序栈类型定义*/ Sqlstack *S; /*S是顺序栈类型指针*/
静态数组实现栈结构 #define maxsize 64 /* 栈的最大容量*/ typedef datatype int; /*栈元素的数据类型*/ typedef struct { datatype data[maxsize]; int top; int base; } seqstack; /*顺序栈定义*/ seqstack *s; /*顺序栈的实现 */
顺序栈的模块说明 置空栈(栈的初始化)操作: initstack(s) seqstack *s; { datatype data[maxsize]; s->top=0; s->base=0; }
判栈空操作: int empty(s) seqstack *s; { if(s->top==s->base) return true; else return false; }
进栈操作: seqstack *Push(s,x) /* 将元素x插入顺序栈s的顶部*/ seqstack *s; datatype x; { if (s->top==maxsize) { printf("overflow"); return NULL; } else { s->data[s->top]=x; s->top++; } return s; }
出栈操作: Datatype Pop(s,e) /*若栈非空,删除栈顶元素,用e返回其值*/ seqstack *s;datatype e; { if (empty(s)) { printf("underflow");return NULL; /*下溢*/ } else { s->top--; e= s->data[s->top]; return(e); } }
取栈顶操作: Datatype GetTop(s) /*取顺序栈s的栈顶*/ seqstack *s; { if (empty(s)) { printf("stack is empty");/*空栈*/ return null; } else return(s->data[--s->top]); }
多栈共享空间 实例:假定有两个栈共享一个数组S[0,…,MAXSIZE-1]使第一个栈使用数组空间的前面部分,并使栈底在前;而使第二个栈使用数组空间的后面部分,并使栈底在后,这样实现了多栈共享空间;其空间分配示意图如图所示。
共享空间存储结构的定义: typedef datatype int; /*栈元素的数据类型*/ #define maxsize 64 /* 栈的最大容量*/ typedef struct { datatype data[maxsize]; int top1,top2; }dstack; 初始化操作: InitDstack(dstack *s) { s->top1=0; s->top2=maxsize-1; }
进栈操作: PushDstack(dstack*s,char ch,datatype x) {/*把数据元素x压入栈s的左栈或右栈*/ if (s->top2 - s->top1==1) return 0;/*栈已满*/ if(ch==’s1’) { s->data[s->top1]=x; s->top1= s->top1+1; return 1; }/*进栈s1*/ if(ch==’s2’) { s->data[s->top2]=x; s->top2= s->top2-1; return 1; }/*进栈s2*/ }
出栈操作: popdstack(dstack *s,char ch) {/*从栈S1或S2取出栈顶元素并返回其值*/ if (char=’s1’) { if(s->top1==0) return null;/*栈s1已空*/ else { s->top1= s->top1-1; return(s->data[s->top1]); } }/*s1出栈*/
if(char=’s2’) { if(s->top2>maxsize-1) return null;/*栈s2已空*/ else { s->top2= s->top2+1; return (s->data[s->top2]); } }/*s2出栈*/ }
栈的链式存储结构 链栈 :即栈的链式存储结构 ;是运算受限制的单链表,其插入和删除操作仅限于表头位置上进行。 语法说明: Typedef datatype int; Typedef struct node { datatype data; struct node *next; }linkstack;/*链栈结点类型*/ linkstack *top;
进栈操作: Linkstack pushlinkstack(top,w) /*将元素w插入链栈top的栈顶*/ Linkstack *top; Datatype X; { linkstack *p; p=malloc(sizeof(linkstack));/*生成新结点*p */ p->data=w; p->next=top; return p;/*返回新栈顶指针*/ }/*pushlinkstack*/
出栈操作: linkstack poplinkstack(top,x) /*删除链栈top的栈顶结点*/ linkstack top; datatype *x; /*让x指向栈顶结点的值,返回新栈指针*/ { linkstack *p; if (top==null) { printf(“空栈,下溢”);return null; } else { *x=top->data; /*将栈顶数据存入*x */ p=top; /*保存栈顶结点地址*/ top=top->next; /*删除原栈顶结点*/ free(p); /*释放原栈顶结点*/ return top; /*返回新栈顶指针*/ } }/*poplinkstack*/
置栈空: Void InitStack(LinkStack *S) { S->top=NULL; } 判栈空: int StackEmpty(LinkStack *S) { if ( S->top==NULL) return 0; else return 1; }
取栈顶元素: datatype StackTop(LinkStack *S) { if(StackEmpty(S)) Error("Stack is empty.") return S->top->data; }
顺序栈和链式栈的比较 顺序栈和链式栈的所有操作的时间相同,都是常数级的时间。 初始化一个线性栈必须首先声明一个固定长度,在栈不够满时,就浪费了一部分存储空间;而链式栈无此问题。 两个栈共享空间时,顺序栈发生上溢的概率比链式栈要小得多。
栈的应用 迷宫问题 算术表达式求值 子程序的调用和返回 数制转换 行编辑
迷宫问题 迷宫的表示方法: 用一个二维数组maze[Max_Row][Max_Col]来表示一个迷宫,其中数组元素0代表可通行的路径,1代表障碍;定义左上角为入口,右下角为出口。 例:
当位于(i,j)时,可能的移动方向: 移动方向的声明方式: Typedef struct { int vert; int horiz; }offsets; offsets move[4];
移动方向的数组表示: 计算下一位置: 假设现在的位置在maze[row][col],下一步的位置在maze[next_row][next_col],则可利用direct变量知道next_row和next_col两个值: next_row=row+move[direct].vert; next_col=col+move[direct].horiz;
迷宫问题中涉及的方法 试错法:如果新的位置没有障碍,则将这个位置记录下来,反之,则尝试下一个方向,如果每一个方向都走不通的话,则表示这个结点是一个死点,将此结点删除。由此可见,由起点走到终点的是经过一连串的尝试错误,才找到通路的,这种查找方法称为试错法。 回溯法:当从某一点开始,找不到一组解时,就回到前一个结点,再重新从这点出发去尝试下一个未尝试的可能路径。
算术表达式求值 表达式:由运算符、运算对象和界限符组成的有意义的式子,如:5十4*3一9/3。 算符优先法:根据运算优先关系的规定来实现对表达式的编译或解释执行。
设置工作栈: StackR:用于寄存运算符 StackD:用于寄存运算对象或运算结果 算法思想: 首先置StackD为空栈,起始符“$”为运算符栈的栈底元素。 依次读入表达式中每个字符,若是运算对象则进StackD栈;若是运算符,则和StackR栈的栈顶运算符比较优先权,若优先级高于栈顶元素则进栈,否则输出栈顶元素。 从StackD中相应的输出两个运算对象作相应运算。 然后再与StackR中的栈顶元素进行优先级比较,以此类推,直至整个表达式求值完毕。
实例:利用上述算法对算术表达式4*(8+3)求值 ,其操作如下:
子程序的调用和返回 主调函数运行时调用另一个函数,在程序流转入被调函数之前,系统会作: 将调用函数时的所有实参、临时变量,返回地址等保存。 为被调函数的变量分配存储空间。 执行被调函数。 算法思想: 系统将程序运行的数据存入栈中。 当函数调用时,把该函数的实参、临时变量,返回地址等数据压入栈顶。 当调函数执行完毕时:系统将运行时刻栈栈顶的活动结构退栈,并根据退栈的活动结构中所保存的返回地址将程序的控制权转移给调用者继续执行。
数制转换 定义:将十进制数转换成其它进制的数的方法,利用公式N=(N div d)*d +N mod d 。 算法: void conversion() { {//假设输入是非负的十进制整数,输出等值的8进制数 Linkstack S; node e; int n; InitStack(&S); scanf("%d",&n); Push(S,0);
while(n) { //从右向左产生8进制的各位数字,并将其进栈 Push(S,n%8); n=n/8; } printf("the result is: ",n); while(!StackEmpty(*S)) { //栈非空时退栈输出 Pop(S,&e); printf("%d",e); } }
行编辑 定义:接受用户从终端输入的程序或数据,并存入用户的数据区,允许用户输入出错时可以及时更正;可以约定#为退格符,以表示前一个字符无效,@为退行符,表示当前行所有字符均无效。 行编辑算法: void LineEdit() { SqlStack S,T; char str[1000]; int strlen=0; char e; char ch; InitStack(&S); InitStack(&T); ch=getchar();
while(ch!=EOF) { while(ch!=EOF&&ch!='\n') { switch(ch) { case '#': Pop(S,&ch); break; case '@': ClearStack(S); break; default: Push(S,ch); break; } ch=getchar(); } if(ch=='\n') Push(S,ch); while(!StackEmpty(*S)) { Pop(S,&e); Push(T,e); } while(!StackEmpty(*T)) { Pop(T,&e); str[strlen++]=e; } if(ch!=EOF) ch=getchar(); } //end while(ch!=EOF) str[strlen]='\0'; printf("\n%s",str); }
队列 概念:队列是一种先进先出(FIRST IN FIRST OUT 简称FIFO)的线性表;只允许在表的一端进行插入,在另一端删除元素;允许插入的一端称为队尾(Rear),允许删除的一端称为队头(Front)。 示意图:
队列的基本操作: 构造空队列InitQueue(Q) 队列销毁DestroyQueue(Q) 队列清空ClearQueue(Q) 取头元素GetHead(Q, e) 队列插入Enqueue(Q,e) 队头删除Dequeue(Q,e) 双端队列:限定插入和删除操作在表的两端进行的线性表。
队列的顺序存储 顺序队列:即队列的顺序存储结构 ,用一维数组来表示 ;附设两个指针front指向队列头元素的位置,rear指针指向队列尾元素的位置。
顺序队列类型说明: typedef datatype int; #define maxsize 66 /*队列的最大长度*/ typedef struct { datatype data[maxsize]; int front,rear; /* 确定队头队尾位置的两个变量*/ }sequeue;/*顺序队列的类型*/ sequeue *q; 初始化操作: initseq(sequeue *q) { datatype data[maxsize]; q.front=-1; q.rear=-1; }
队头删除操作: Delseq(sequeue *q) { if (q->front==q->rear) printf(“sequeue empty!”); else { q->front++; return(q->data[q->front]); } }
队尾插入操作 : insertseq(sequeue *q,int x) { if(q->rear >= maxsize-1) return Null;/*队列已满*/ else { (q->rear)++; q->data[q->rear]=x; return OK; } }
队列假溢解决算法: void seq_full(sequeue *q,int x) { int i; if(q->rear-q->front=maxsize) printf(“sequeue overflow!!”);/*溢出*/ else { /*所有数据向前移*/ for(i=0;i<q->rear-q->front;i++) q->data[i]= q->data[q->front+i+1]; q->rear=q->rear-q->front; q->front=-1; } }
循环队列:顺序队列臆造为一个环状的空间。 实例: 队尾添加操作,队尾指针加1: q->rear=(q->rear+1)%maxsize 队头删除操作,队头指针1: q->front=(q->front+1)%maxsize
循环队列的头尾指针分布: 判断队满条件: q->front=(q->rear+1)% maxsize 判断队空条件: q->front= q->rear
循环队列运算算法 置空队: InitQueue(q) sequeue *q; { datatype data[maxsize]; q->front=-1; q->rear=-1; } 判队空: int QueueEmpty(q) sequeue *q; { if(q->rear==q->front) return OK; else return Null; }
取队头元素: datatype GetHead(q) sequeue *q; { if (empty(q)) { print("sequeue is empty"); return Null; } else return (q->front+1) % maxsize; }
入队操作: int InQueue(q,x) /*将新元素x插入队列*q的队尾*/ sequeue *q; datatype x; { if(q->front==(q->rear+1) % maxsize) { print("queue is full"); return NUll; } else { q->rear=(q->rear+1) % maxsize; q->data[q->rear]=x; } }