1.01k likes | 1.18k Views
数据结构 Data Structure. 精品课程. 第 2 章要点回顾. 顺序表 的类型定义. 顺序表的建立、输出、查找、插入、删除操作. 单链表 的类型定义. 单链表的建立、输出、查找、插入、删除操作. 第 3 章 栈与队列. 第 3 章 栈与队列. 3.1 栈. 3.2 栈的应用. 3.3 队列. 3.4 队列的应用. 3.1.1 栈的定义及基本操作 3.1.2 顺序栈 3.1.3 链栈. 3.1 栈. 入栈. 出栈. 栈顶. 栈顶元素. an. ……. (限定操作 一端).
E N D
数据结构 Data Structure 精品课程
第2章要点回顾 顺序表的类型定义 顺序表的建立、输出、查找、插入、删除操作 单链表的类型定义 单链表的建立、输出、查找、插入、删除操作
第3章 栈与队列 3.1栈 3.2 栈的应用 3.3 队列 3.4 队列的应用
3.1.1 栈的定义及基本操作 3.1.2 顺序栈 3.1.3 链栈 3.1 栈
入栈 出栈 ... 栈顶 栈顶元素 an ……... (限定操作 一端) s=(a1,a2,……,an) a2 栈底 a1 3.1.1 栈的定义及基本操作 1、栈的定义 栈:限定仅在表尾进行插入或删除操作的线性表。 表尾—栈顶,表头—栈底,不含元素的空表称空栈
3.1.1 栈的定义及基本操作 1、栈的定义 栈的特点: 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。 也就是说,栈是一种后进先出的线性表,简称为LIFO表。
3.1.1 栈的定义及基本操作 2、栈的基本操作 (1)初始化栈:InitStack(&s) 将栈s置为一个空栈(不含任何元素)。 (2)进栈:Push(&s,x) 将元素X插入到栈s中,也称为 “入栈”、 “插入”、 “压入”。 (3)出栈: Pop(&s) 删除栈s中的栈顶元素,也称为“退栈”、 “删除”、 “弹出”。
3.1.1 栈的定义及基本操作 2、栈的基本操作 (4)取栈顶元素: GetTop(S,&e) 取栈S中栈顶元素。 (5)判栈空: StackEmpty(S) 判断栈S是否为空,若为空,返回值为true,否则返回值为false。
3.1.1 栈的定义及基本操作 2、栈的基本操作 【例】对于一个栈,给出输入项A、B、C,如果输入项序列由ABC组成,试给出所有可能的输出序列。 A进 A出 B进 B出 C进 C出 ABC A进 A出 B进 C进 C出 B出 ACB A进 B进 B出 A出 C进 C出 BAC A进 B进 B出 C进 C出 A出 BCA A进 B进 C进 C出 B出 A出 CBA 可能产生输出序列CAB?
3.1.1 栈的定义及基本操作 【例】一个栈的输入序列是12345,若在入栈的过程中允许出栈,则栈的输出序列43512可能实现吗?12345的输出呢? 2、栈的基本操作 43512不可能实现,主要是其中的12顺序不能实现; 12345的输出可以实现,只需压入一个立即弹出一个即可。 答:
3.1.1 栈的定义及基本操作 2、栈的基本操作 【例】如果一个栈的输入序列为123456,能否得到435612和135426的出栈序列? 答: 435612中到了12顺序不能实现; 135426可以实现。
3.1.1 栈的定义及基本操作 2、栈的基本操作 【例】设依次进入一个栈的元素序列为c,a,b,d,则可得到出栈的元素序列是: A)a,b,c,d B)c,d,a,b C)b,c,d,a D)a,c,d,b 答: A、D可以( B、C不行)。
3.1.2 顺序栈 由于栈是运算受限的线性表,因此线性表的存储结构对栈也适应。 1、顺序栈的存储结构 栈的顺序存储结构简称为顺序栈,它是运算受限的线性表。因此,可用数组来实现顺序栈。因为栈底位置是固定不变的,所以可以将栈底位置设置在数组的两端的任何一个端点;栈顶位置是随着进栈和退栈操作而变化的,故需用一个整型变量top来指示当前栈顶的位置,通常称top为栈顶指针。
3.1.2 顺序栈 顺序栈的类型定义只需将顺序表的类型定义中的last域改为top即可。顺序栈的类型定义如下: 1、顺序栈的存储结构 # define StackSize 100 typedef struct { ElemType data[StackSize]; int top; }SeqStack;
F E 5 5 5 top top top top top top top D 4 4 4 C 3 3 3 B top top top top top top 2 2 2 A 1 1 1 top=-1 0 0 0 栈空 3.1.2 顺序栈 栈空 栈顶top 的值指示栈顶元素在栈中的位置。 栈满 F E D C B A 出栈 进栈 设数组下标最大值为M top=-1,栈空,此时出栈,则下溢 top=M,栈满,此时入栈,则上溢 栈顶指针top,指向实际栈顶位置,初值为-1
3.1.2 顺序栈 设S是SeqStack类型的指针变量。若栈底位置在向量的低端,即s–>data[0]是栈底元素,那么栈顶指针s–>top是正向增加的,即: 进栈时需将s–>top加1 ; 退栈时需将s–>top 减1 因此,s–>top=-1表示空栈 s–>top =StackSize-1表示栈满。 当栈满时再做进栈运算必定产生空间溢出,简称“上溢”; 当栈空时再做退栈运算也将产生溢出,简称“下溢”。
3.1.2 顺序栈 (1)初始化栈 2、顺序栈的基本运算 void InitStack(SeqStack *s) { s–>top=-1; } (2)判断栈空 int StackEmpty(SeqStack *s) { return(s–>top==-1); }
3.1.2 顺序栈 (3)判断栈满 2、顺序栈的基本运算 int StackFull(SeqStack *s) { return(s–>top==StackSize-1); } (4)进栈 void Push(SeqStack *s,ElemType x) { if (StackFull(s)) error(“Stack overflow”); s–>data[++s–>top]=x; }
3.1.2 顺序栈 (5)退栈 2、顺序栈的基本运算 void Pop(SeqStack *s,ElemType *x) { if (StackEmpty(s)) error(“Stack underflow”); *x=s–>data[top]; s–>top--; }
3.1.2 顺序栈 (6)取栈顶元素 2、顺序栈的基本运算 ElemType GetTop(SeqStack *s) { if (StackEmpty(s)) error(“Stack is empty”); return s–>data[s–>top]; }
3.1.3 链栈 1、链栈的存储结构 栈的链式存储结构称为链栈,它是运算受限的单链表,插入和删除操作仅限制在表头位置上进行。由于只能在链表头部进行操作,故链表没有必要像单链表那样附加头结点。栈顶指针就是链表的头指针。
3.1.3 链栈 1、链栈的存储结构 链栈的类型定义 typedef struct node { ElemType data; struct node *next; } Node, *LinkStack;
3.1.3 链栈 2、链栈的基本运算 (1)链栈的进栈 LinkStack *PushLStack(LinkStack *top, ElemType x) { LinkStack *p; p=malloc(sizeof(LinkStack)); p->data=x; p->next=top; return p; }
3.1.3 链栈 (2)链栈的出栈 LinkStack *PopLStack(LinkStack *top, ElemType *datap) { LinkStack *p; if (top==NULL) { printf(“under flow\n”); return NULL;} else { *datap=top->data; p=top; top=top->next; free(p); return top;} }
3.2.1 数制转换 3.2.2 文字编辑器 3.2.3 表达式求值 3.2.4 基于栈的迷宫求解 3.2 栈的应用
3.2.1 数制转换 十进制N和其它进制数的转换是计算机实现计算的基本问题,其解决方法很多,其中一个简单算法基于下列原理: N=(n div d)*d+n mod d ( 其中:div为整除运算,mod为求余运算)
计算顺序 输出顺序 3.2.1 数制转换 如:(1348)10=(2504)8,其运算过程如下: N N div 8 余数1348 168 4168 21 021 2 52 0 2
3.2.1 数制转换 void conversion( ) { InitStack(&s); scanf (“%d”,&n); while(n) { Push(&s,n%8); n=n/8; } while(! StackEmpty(&s)) { Pop(&s,&e); printf(“%d”,e); } }
3.2.2 文字编辑器 功能:接受用户从终端输入的程序或数据,并存入用户数据区。 较好做法:设立输入缓冲区,接受一行字符,有错可及时纠正。 #:退格符; @:退行符
3.2.2 文字编辑器 SqStack s; EDIT() { char c; InitStack(&s); c=getchar(); while (c!=‘`’) { if (c==‘#’) Pop(&s); else if (c==‘@’) InitStack(&s); else Push(&s,c); c=getchar();}}
3.2.3 表达式求值 ( 这是栈应用的典型例子 ) 这里,表达式求值的算法是 “算符优先法”。 如:3*(7 – 2 ) (1)要正确求值,首先了解算术四则运算的规则: 从左算到右;先乘除,后加减;先括号内,后括号外 由此,此表达式的计算顺序为: 3*(7 – 2 )= 3 * 5 = 15
Ø1Ø2 + - * / ( ) # + - * / ( ) # > > < < < > > > > < < < > > > > > > < > > > > > > < > > < < < < < = > > > > > > < < < < < = 3.2.3 表达式求值 (2)根据上述三条运算规则,在运算的每一步中,对任意相继出现的算符1和2,都要比较优先权关系。 算符优先法所依据的算符间的优先关系见下表 算符间的优先关系
3.2.3 表达式求值 • (3)算法思想: • 设定两栈:操作符栈 OPTR ,操作数栈 OPND • 栈初始化:设操作数栈 OPND 为空;操作符栈 OPTR 的栈底元素为表达式起始符 ‘#’; • 依次读入字符:是操作数则入OPND栈,是操作符则要判断: • 若操作符 < 栈顶元素,则退栈、计算,结果压入OPND栈; • 操作符 = 栈顶元素且不为‘#’,脱括号(弹出左括号); • 操作符 > 栈顶元素,压入OPTR栈。
OPTR OPND INPUT OPERATE # 3*(7-2)# Push(opnd,’3’) # 3 *(7-2)# Push(optr,’*’) #,* 3 (7-2)# Push(optr,’(’) #,*,( 3 7-2)# Push(opnd,’7’) #,*,( 3,7 -2)# Push(optr,’-’) #,*,(,- 3,7 2)# Push(opnd,’2’) )# #,*,(,- 3,7,2 Operate(7-2) #,*,( 3,5 )# Pop(optr) #,* 3,5 # Operate(3*5) # 15 # GetTop(opnd) 3.2.3 表达式求值
3.2.3 表达式求值 Status EvaluateExpression( OperandType &result) { InitStack(OPND); InitStack(OPTR);Push(OPTR ,’#’);c=getchar(); while((c!=‘#’)||(GetTop(OPTR)!=‘#’)) { if (!In(c,OP) { Push(OPND,c); c=getchar();} else switch(compare(c,GetTop(OPTR))) {case ‘>’ : Push(OPTR , c); c=getchar();break; case ‘=’: Pop(OPTR);c=getchar();break; case ‘<’ : temat=Pop(OPTR); b=Pop(OPND);a=Pop(OPND); result= Operate(a,temat,b);Push(OPND,result);
3.2.3 表达式求值 c=getchar();break; } //switch }//while result=GetTop(OPND); }//EvaluateExpression
3.2.3 表达式求值 float Operate(float a,unsigned char theta, float b) { switch(theta) { case '+': return a+b; case '-': return a-b; case '*': return a*b; case '/': return a/b; default : return 0; } } Status In(char Test,char* TestOp) { bool Find=false; for (int i=0; i< OPSETSIZE; i++) { if (Test == TestOp[i]) Find= true; } return Find; }
3.2.3 表达式求值 int ReturnOpOrd(char op,char* TestOp) { int i; for(i=0; i< OPSETSIZE; i++) { if (op == TestOp[i]) return i; } return 0; } char precede(char Aop, char Bop) { return Prior[ReturnOpOrd(Aop,OPSET)][ReturnOpOrd(Bop,OPSET)]; }
3.2.3 表达式求值 #define OPSETSIZE 7 unsigned char Prior[7][7] = { // 表3.1 算符间的优先关系 '>','>','<','<','<','>','>', '>','>','<','<','<','>','>', '>','>','>','>','<','>','>', '>','>','>','>','<','>','>', '<','<','<','<','<','=',' ', '>','>','>','>',' ','>','>', '<','<','<','<','<',' ','=‘ }; float Operate(float a, unsigned char theta, float b); char OPSET[OPSETSIZE]={'+' , '-' , '*' , '/' ,'(' , ')' , '#'}; Status In(char Test,char* TestOp); char precede(char Aop, char Bop);
3.2.4 基于栈的迷宫求解 通常用的是“穷举求解”的方法
3.2.4 基于栈的迷宫求解 求迷宫路径算法的基本思想是:(回溯) 若当前位置“可通”,则纳入路径,继续前进; 若当前位置“不可通”,则后退,换方向继续探索; 若四周“均无通路”,则将当前位置从路径中删除出去并退回到前一位置继续下一方向的探索。 为了保证在任何位置上都能沿原路退回,显然需要用一个后进先出的结构来保存从入口到当前位置的路径。因此,在本算法中应用“栈”是很自然的事。
3.2.4 基于栈的迷宫求解 需要解决的4个问题: (1)表示迷宫的数据结构。设迷宫为M行N列,利用maze[M][N]来表示一个迷宫,maze[i][j]=0或1,其中0表示通道,1表示墙(不通)。当从某点向下试探时,中间点有8个方向可以试探,而4个角点有3个方向,其他边缘点有5个方向,为使问题简单化我们用maze[M+2][N+2]来表示迷宫,而迷宫四周的值全部为1。这样,每个点都有8个试探方向,不用再判断当前点的试探方向有几个,同时与迷宫周围是墙壁这一实际问题相一致。
3.2.4 基于栈的迷宫求解 设入口坐标为(1,1),出口坐标为(6,8) 迷宫的定义如下: #define M 6 //迷宫的实际行 #define N 8 //迷宫的实际列 int maze [M+2][N+2];
3.2.4 基于栈的迷宫求解 (2)试探方向。在上述迷宫中,每个点有8个方向可以试探,若当前点的坐标为(x,y),则与其相邻的8个点的坐标都可根据与该点的相邻方位而得到。因为出口在(M,N),因此试探顺序规定为:从当前位置向前试探的方向为从正东沿顺时针方向进行。为了简化问题,方便地求出新点的坐标,将从正东开始沿顺时针进行的这8个方向的坐标增量放在一个结构体数组move[8]中,在move数组中,每个元素由两个域组成,x:横坐标增量,y:纵坐标增量。
3.2.4 基于栈的迷宫求解 move数组定义如下: typedef struct { int x, y; } item ; item move[8] ; 这样可以很方便地求出从某点(x,y)按某一方向v(0≤v≤7)到达的新点(i,j)的坐标:i=x+move[v].x;j=y+move[v].y。
3.2.4 基于栈的迷宫求解 (3)栈的设计。当到达了某点而无路可走时需返回前一点,再从前一点开始向下一个方向继续试探。因此,压入栈中的不仅是顺序到达的各点的坐标,而且还要有从当前点到达下一点的方向序号。对于前述迷宫,依次入栈的顺序如下图