栈的定义(精)

栈的定义(精)
栈的定义(精)

一、栈

1. 栈的定义

栈(Stack)又称堆栈,它是一种运算受限的线性表,其限制是仅允许在表的一端进行插入和删除运算。人们把此端称为栈顶,栈顶的第一个元素被称为栈顶元素,相对地,把另一端称为栈底。向一个栈插入新元素又称为进栈或入栈,它是把该元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称为出栈或退栈,它是把栈顶元素删除掉,使其下面的相邻元素成为新的栈顶元素。

在日常生活中,有许多类似栈的例子,如刷洗盘子时,依次把每个洗净的盘子放到洗好的盘子上,相当于进栈;取用盘子时,从一摞盘子上一个接一个地向下拿,相当于出栈。又如向枪支弹夹里装子弹时,子弹被一个接一个地压入,则为进栈;射击时子弹总是从顶部一个接一个地被射出,此为子弹出栈。

由于栈的插入和删除运算仅在栈顶一端进行,后进栈的元素必定先出栈,所以又把栈称为后进先出表(Last In First Out, 简称LIFO)。

例如,假定一个栈S为(a,b,c),其中字符c的一端为栈顶,字符c为栈顶元素。若向S压入一个元素d,则S变为(a,b,c,d),此时字符d为栈顶元素;若接着从栈S中依次删除两个元素,则首先删除的是元素d,接着删除的使元素c,栈S变为(a,b),栈顶元素为b。

2. 栈的存储结构

栈既然是一种线性表,所以线性表的顺序存储和链接存储结构同样适用于栈。

(1) 栈的顺序存储结构

栈的顺序存储结构同样需要使用一个数组和一个整型变量来实现,利用数组来顺序存储栈中的所有元素,利用整型变量来存储栈顶元素的下标位置。假定栈数组用stack[StackMaxSize]表示,指示栈顶位置的整型变量用top表示,则元素类型为ElemType 的栈的顺序存储类型可定义为:

ElemType stack[StackMaxSize];

int top;

其中,StackMaxSize为一个整型全局常量,需事先通过const语句定义,由它确定顺序栈(即顺序存储的栈)的最大深度,又称为长度,即栈最多能够存储的元素个数;由于top用来指示栈顶元素的位置,所以把它称为栈顶指针。

栈的顺序存储结构同样可以定义在一个记录类型中,假定该记录类型用Stack表示,则定义为:

struct Stack {

ElemType stack[StackMaxSize];

int top;

};

在顺序存储的栈中,top的值为-1表示栈空,每次向栈中压入一个元素时,首先使top 增1,用以指示新的栈顶位置,然后再把元素赋值到这个位置上,每次从栈中弹出一个元素时,首先取出栈顶元素,然后使top减1,指示前一个元素成为新的栈顶元素。由此可知,对顺序栈的插入和删除运算相当于是在顺序表(即顺序存储的线性表)的表尾进行的,其时间复杂度为O(1)。

设一个栈S为(a,b,c,d,e),对应的顺序存储结构如图4-1(a)所示。若向S中插入一个元素f,则对应如图4-1(b)所示。若接着执行两次出栈操作后,则栈S对应如图4-1(c)所示。若依次使栈S中的所有元素出栈,则s变为空,如图4-1(d)所示。在图4-1中栈是垂直画出的,并且使下标编号向上递增,这样可以形象地表示出栈顶在上,栈底在下。

图4-1 栈的顺序存储结构及操作过程

在一个顺序栈中,若top已经指向了StackMaxSize-1的位置,则表示栈满,若top的值已经等于-1,则表示栈空。向一个满栈插入元素和从一个空栈删除元素都是不允许的,应该停止程序运行或进行特别处理。

(2) 栈的链接存储结构

栈的链接存储结构与线性表的链接存储结构相同,是通过由结点构成的单链表实现的,此时表头指针被称为栈顶指针,由栈顶指针指向的表头结点被称为栈顶结点,整个单链表被称为链栈,即链接存储的栈。当向一个链栈插入元素时,是把该元素插入到栈顶,即使该元素结点的指针域指向原来的栈顶结点,而栈顶指针则修改为指向该元素结点,使该结点成为新的栈顶结点。当从一个链栈中删除元素时,是把栈顶元素结点删除掉,即取出栈顶元素后,使栈顶指针指向原栈顶结点的后继结点。由此可知,对链栈的插入和删除操作是在单链表的表头进行的,其时间复杂度为O(1)。

设一个栈为(a,b,c),当采用链接存储时,对应的存储结构示意图如图4-2(a)所示,其中HS表示栈顶指针,其值为存储元素c结点的地址。当向这个栈插入一个元素d后,对应如图4-2(b)所示。当从这个栈依次删除两个元素后,对应如图4-2(c)所示。当链栈中的所有元素全部出栈后,栈顶指针HS的值为空,即常量NULL所表示的数值0。

图4-2 栈的链接存储结构及操作过程

3. 栈的抽象数据类型

栈的抽象数据类型中的数据部分为具有ElemType元素类型的一个栈,它可以采用任一种存储结构实现;操作部分包括元素进栈、出栈、读取栈顶元素、检查栈是否为空等。下面给出栈的抽象数据类型的具体定义。

ADT STACK is

Data:

采用顺序或链接方式存储的栈,假定其存储类型用StackType

标识符表示。

Operation:

void InitStack(StackType& S);

//初始化栈S,即把它置为空

void ClearStack(StackType& S);

//清除栈S中的所有元素,使之成为一个空栈

int StackEmpty(StackType& S);

//判断S是否为空,若是则返回1,否则返回0

ElemType Peek(StackType& S)

//返回S的栈顶元素,但不移动栈顶指针

void Push(StackType& S, const ElemType& item)

//元素item进栈,即插入到栈顶

ElemType Pop(StackType& S)

//删除栈顶元素并返回之

int StackFull(Stack& S)

//若栈已满则返回1,否则返回0,此函数为顺

//序存储的栈所特有

end STACK

对于判断栈是否为空和返回栈顶元素的两种操作,由于它们不改变栈的状态,所以可在参数类型说明前使用常量定义符const。

假定栈a的元素类型为int,下面给出调用上述栈操作的一些例子。

(1)InitStack(a); //把栈a置空

(2) Push(a,35); //元素35进栈

(3) int x=48; Push(a,x); //48进栈

(4) Push(a,x/2); //x除以2的值24进栈

(5) x=Pop(a); //栈顶元素24退栈并赋给x

(6) x=Peek(a); //读取栈顶元素48并赋给x

(7) Pop(a); //栈顶元素48退栈

(8) StackEmpty(a); //因栈非空,应返回0

(9) cout<

(10) x=StackEmpty(a); //因栈为空,应返回1,然后赋给x

4. 栈运算的实现

栈运算(操作)的具体实现取决于栈的存储结构,存储结构不同,其算法描述也不同。下面分别给出栈的运算在顺序存储结构和链接存储结构上实现的具体算法。

(a) 栈的操作在顺序存储结构上的实现

假定采用顺序存储结构定义的栈用标识符S 表示,其类型为已经给出过的Stack记录类型。

(1)初始化栈

void InitStack(Stack& S)

//初始化栈S,即把它置为空

{

S.top=-1;

}

(2) 把一个栈清除为空

在顺序存储方式下,同初始化栈的算法相同。

void ClearStack(Stack& S)

//清除栈S中的所有元素,使之成为一个空栈

{

S.top=-1;

}

(3) 检查一个栈是否为空

int StackEmpty(Stack& S)

//判断S是否为空,若是则返回1,否则返回0

{

return S.top==-1;

}

(5) 读取栈顶元素

ElemType Peek(StackType& S)

//返回S的栈顶元素,但不移动栈顶指针

{

//若栈为空则终止程序运行

if(S.top==-1) {

cerr<<"Stack is empty!"<

exit(1);

}

//返回栈顶元素的值

return S.stack[S.top];

}

(5) 向栈中插入元素

void Push(Stack& S, const ElemType& item)

//元素item进栈,即插入到栈顶

{

//若栈已满则终止程序运行

if(S.top==StackMaxSize-1) {

cerr<<"Stack overflow!"<

exit(1);

}

//将栈顶指针后移一个位置

S.top++;

//将item的值赋给新的栈顶位置

S.stack[S.top]=item;

}

(6) 从栈中删除元素

ElemType Pop(StackType& S)

//删除栈顶元素并返回之

{

//若栈为空则终止程序运行

if(S.top==-1) {

cerr<<"Stack is empty!"<

exit(1);

}

//暂存栈顶元素以便返回

ElemType temp=S.stack[S.top];

//栈顶指针前移一个位置

S.top--;

//返回原栈顶元素的值

return temp;

}

从出栈算法可以看出,原栈顶元素的值没有被改变,所以可以不使用临时变量保存它,返回语句中返回S.stack[S.top+1]的值即可。

(7) 检查栈是否已满

int StackFull(Stack& S)

//若栈已满则返回1,否则返回0,此函数为顺序栈所特有

{

return S.top==StackMaxSize-1;

}

(b) 栈的操作在链接存储结构上的实现

假定链栈中的结点仍采用在第二章中已经定义的LNode结点类型,并假定栈顶指针用HS表示,下面给出对由HS所指向的链栈进行每一种栈操作的算法。

(1) 初始化链栈

void InitStack(LNode*& HS)

{

HS=NULL; //将链栈置空。

}

(2) 清除链栈为空

void ClearStack(LNode*& HS)

{

LNode *cp, *np;

//用cp作为指向待删除的结点,np指向cp的后继结点。

cp=HS; //给cp指针赋初值,使之指向栈顶结点。

while(cp!=NULL)

{ //从栈顶到栈底依次删除每个结点

np=cp->next;

delete cp;

cp=np;

}

HS=NULL; //置链栈为空

}

(3) 检查链栈是否为空

int StackEmpty(LNode* HS)

//HS为值参或引用形参均可

{

return HS==NULL;

}

(4) 读取栈顶元素

ElemType Peek(LNode* HS) //HS为值参或引用形参均可

{

if(HS==NULL) {

cerr<<"Linked stack is empty!"<

exit(1);

}

return HS->data;

}

(5) 向链栈中插入一个元素

void Push(LNode*& HS, const ElemType& item)

{

//为插入元素获取动态结点

LNode* newptr= new LNode;

if(newptr==NULL) {

cerr<<"Memory allocation failare!"<

exit(1);

}

//给新分配的结点赋值

newptr->data=item;

//向栈顶插入新结点

newptr->next=HS;

HS=newptr;

}

(6) 从链栈中删除一个元素

ElemType Pop(LNode*& HS)

{

if(HS==NULL) {

cerr<<"Linked stack is empty!"<

exit(1);

}

LNode* p=HS; //暂存栈顶结点

HS=HS->next; //使栈顶指针指向其后继结点

ElemType temp=p->data; //暂存原栈顶元素

delete p; //回收原栈顶结点

return temp; //返回原栈顶元素

}

5. 栈的简单应用举例

例1. 从键盘上输入一批整数,然后按照相反的次序打印出来。

根据题意可知,后输入的整数将先被打印出来,这正好符合栈的后进先出的特点。所以此题很容易用栈来解决。参考程序如下:

#include

#include

const int StackMaxSize=30;

typedef int ElemType; //定义元素类型为整型

struct Stack {

ElemType stack[StackMaxSize];

int top;

};

#include"stack.h"

//假定对顺序栈操作的算法已经存于"stack.h"头文件中

void main()

{

Stack a;

InitStack(a);

int x;

cin>>x;

while(x!=-1) {

//假定用-1作为终止键盘输入的标志,输入的整数个数

//不能超过StackMaxSize所规定的值

Push(a,x);

cin>>x;

}

while(!StackEmpty(a)) //栈不为空时依次退栈打印出来

cout<

cout<

}

假定从键盘上输入为:

78 63 45 82 91 34 -1

则输出为:

34 91 82 45 63 78

例2. 堆栈在计算机语言的编译过程中用来进行语法检查,试编写一个算法,用来检查一个C++语言程序中的花括号、方括号和圆括号是否配对,若能够全部配对则返回1,否则返回0。

在这个算法中,需要扫描待检查程序中的每一个字符,当扫描到每个花、中、圆左括号时,令其进栈,当扫描到每个花、中、圆右括号时,则检查栈顶是否为相应的左括号,若是则作退栈处理,若不是则表明出现了语法错误,应返回0。当扫描到程序文件结尾后,若栈为空则表明没有发现括号配对错误,应返回1,否则表明栈中还有未配对的括号,应返回0。另外,对于一对单引号或双引号内的字符不进行括号配对检查。

根据分析,编写出算法如下:

int BracketsCheck(char* fname)

//对由fname所指字符串为文件名的文件进行括号配对检查

{

ifstream ifstr(fname, ios::in|ios::nocreate);

//用文件输入流对象ifstr打开以fname所指字符串为文件名的文件

if(!ifstr) {

cerr<<"File"<<"\'"<

exit(1);

}

Stack a; //定义一个顺序栈

InitStack(a); //栈a初始化

char ch;

while(ifstr>>ch) //顺序扫描文件中的每一个字符

{

if(ch==39) { //单引号内的字符不参与配对比较

while(ifstr>>ch)

if(ch==39) //39为单引号的ASCII值

break;

if(!ifstr)

return 0;

}

else if(ch==34) { //双引号内的字符不参与配对比较

while(ifstr>>ch)

if(ch==34) //34为双引号的ASCII值

break;

if(!ifstr)

return 0;

}

switch (ch)

{

case '{':

case '[':

case '(':

Push(a,ch); //出现以上三种左括号则进栈

break;

case '}':

if(Peek(a)=='{')

Pop(a); //栈顶的左花括号出栈

else

return 0;

break;

case ']':

if(Peek(a)=='[')

Pop(a); //栈顶的左中括号出栈

else

return 0;

break;

case ')':

if(Peek(a)=='(')

Pop(a); //栈顶的左圆括号出栈

else

return 0;

}

}

if(StackEmpty(a))

return 1;

else

return 0;

}

例3. 把十进制整数转换为二至九之间的任一进制数输出。

由计算机基础知识可知,把一个十进制整数x转换为任一种r进制数得到的是一个r 进制的整数,假定为y,转换方法是逐次除基数r取余法。具体叙述为:首先用十进制整数x除以基数r,得到的整余数是r进制数y的最低位y0,接着以x除以r的整数商作为被除数,用它除以r得到的整余数是y的次最低位y1,依次类推,直到商为0时得到的整余数是y的最高位y m,假定y共有m+1位。这样得到的y与x等值,y的按权展开式为:

y=y0+y1.r+y2.r2+...+y m.r m

例如,若十进制整数为3425,把它转换为八进制数的过程如图4-3所示。

图4-3 十进制整数3425转换为八进制数的过程

最后得到的八进制数为(6541)8,对应的十进制数为6×83+5×82+4×8+1=3425,即为被转换的十进制数,证明转换过程是正确的。

从十进制整数转换为r进制数的过程中,由低到高依次得到r进制数中的每一位数字,而输出时又需要由高到低依次输出每一位。所以此问题适合利用栈来解决,具体算法描述为: void Transform(long num, int r)

//把一个长整型数num转换为一个r进制数输出

{

Stack a; //利用栈a存储转换后得到的每一位数字

InitStack(a); //初始化栈

while(num!=0)

{ //由低到高求出r进制数的每一位并入栈

int k=num % r;

Push(a,k);

num/=r;

}

while(!StackEmpty(a)) //由高到低输出r进制数的每一位

cout<

cout<

}

假定用下面的主程序调用Transform过程。

void main()

{

cout<<"3425的八进制数为:";

Transform(3425,8);

cout<<"3425的六进制数为:";

Transform(3425,6);

cout<<"3425的四进制数为:";

Transform(3425,4);

cout<<"3425的二进制数为:";

Transform(3425,2);

}

则得到的运行结果如下:

3425的八进制数为:6541

3425的六进制数为:23505

3425的四进制数为:311201

3425的二进制数为:110101100001

二、算术表达式的计算

在计算机中进行算术表达式的计算是通过栈来实现的。这一节首先讨论算术表达式的两种表示方法,即中缀表示法和后缀表示法,接着讨论后缀表达式求值的算法,最后讨论中缀表达式转换为后缀表达式的算法。

1. 算术表达式的两种表示

通常书写的算术表达式是由操作数(又叫运算对象或运算量)和运算符以及改变运算次序的圆括号连接而成的式子。操作数可以是常量、变量和函数,同时还可以是表达式。运算符包括单目运算符和双目运算符两类,单目运算符只要求一个操作数,并被放在该操作数的前面,双目运算符要求有两个操作数,并被放在这两个操作数的中间。单目运算符为取正’+’和取负’-’,双目运算符有加’+’,减’-’,乘’*’和除’/’等。为了简便起见,在我们的讨论中只考虑双目运算符。

如对于一个算术表达式2+5*6,乘法运算符’*’的两个操作数是它两边的5和6;对于加法运算符’+’的两个操作数,一个是它前面的2,另一个是它后面的5*6的结果即30。我们把双目运算符出现在两个操作数中间的这种习惯表示叫做算术表达式的中缀表示,这种算术表达式被称为中缀算术表达式或中缀表达式。

中缀表达式的计算比较复杂,它必须遵守以下三条规则:

(1) 先计算括号内,后计算括号外;

(2) 在无括号或同层括号内,先进行乘除运算,后进行加减运算,即乘除运算的优先级高于加减运算的优先级;

(3) 同一优先级运算,从左向右依次进行。

从这三条规则可以看出,在中缀表达式的计算过程中,既要考虑括号的作用,又要考虑运算符的优先级,还要考虑运算符出现的先后次序。因此,各运算符实际的运算次序往往同它们在表达式中出现的先后次序是不一致的,是不可预测的。当然凭直观判别一个中缀表达式中哪个运算符最先算,哪个次之,……,哪个最后算并不困难,但通过计算机处理就比较困难了,因为计算机只能一个字符一个字符地扫描,要想得到哪一个运算符先算,就必须对整个中缀表达式扫描一遍,一个中缀表达式中有多少个运算符,原则上就得扫描多少遍才能计算完毕,这样就太浪费时间了,显然是不可取的。

那么,能否把中缀算术表达式转换成另一种形式的算术表达式,使计算简单化呢? 回答是肯定的。波兰科学家卢卡谢维奇(Lukasiewicz)很早就提出了算术表达式的另一种表示,即后缀表示,又称逆波兰式,其定义是把运算符放在两个运算对象的后面。采用后缀表示的算术表达式被称为后缀算术表达式或后缀表达式。在后缀表达式中,不存在括号,也不存在优先级的差别,计算过程完全按照运算符出现的先后次序进行,整个计算过程仅需一遍扫描便可完成,显然比中缀表达式的计算要简单得多。例如,对于后缀表达式12!4!-!5!/,其中’!’字符表示成分之间的空格,因减法运算符在前,除法运算符在后,所以应先做减法,后做除法;减法的两个操作数是它前面的12和4,其中第一个数12是被减数,第二个数4是减数;除法的两个操作数是它前面的12减4的差(即8)和5,其中8是被除数,5是除数。

中缀算术表达式转换成对应的后缀算术表达式的规则是:把每个运算符都移到它的两个运算对象的后面,然后删除掉所有的括号即可。

例如,对于下列各中缀表达式:

(1) 3/5+6

(2) 16-9*(4+3)

(3) 2*(x+y)/(1-x)

(4) (25+x)*(a*(a+b)+b)

对应的后缀表达式分别为:

(1) 3!5!/!6!+

(2) 16!9!4!3!+!*!-

(3) 2!x!y!+!*!1!x!-!/

(4) 25!x!+!a!a!b!+!*!b!+!*

2. 后缀表达式求值的算法

后缀表达式的求值比较简单,扫描一遍即可完成。它需要使用一个栈,假定用S表示,其元素类型应为操作数的类型,假定为浮点型float,用此栈存储后缀表达式中的操作数、计算过程中的中间结果以及最后结果。假定一个后缀算术表达式以字符’@’作为结束符,并且以一个字符串的方式提供。后缀表达式求值算法的基本思路是:把包含后缀算术表达式的字符串定义为一个输入字符串流对象,每次从中读入一个字符(空格作为数据之间的分隔符,不会被作为字符读入)时,若它是运算符,则表明它的两个操作数已经在栈S中,其中栈顶元素为运算符的后一个操作数,栈顶元素的下一个元素为运算符的前一个操作数,把它们弹出后进行相应运算即可,然后把运算结果再压入栈S中;否则,读入的字符必为操作数的最高位数字,应把它重新送回输入流中,然后把下一个数据作为浮点数输入,并把它压入到栈S中。依次扫描每一个字符(对于浮点数只需扫描它的最高位并一次输入整个浮点数)并进行上述处理,直到遇到结束符’@’为止,表明后缀表达式计算完毕,最终结果保存在栈中,并且栈中仅存这一个值,把它弹出返回即可。具体算法描述为:

float Compute(char* str)

//计算由str字符串所表示的后缀表达式的值,

//表达式要以'@'字符结束。

{

Stack S; //用S栈存储操作数和中间计算结果

InitStack(S); //初始化栈

istrstream ins(str); //把str定义为输入字符串流对象ins

char ch; //用于输入字符

float x; //用于输入浮点数

ins>>ch; //从ins流对象(即str字符串)中顺序读入一个字符

while(ch!='@')

{ //扫描每一个字符并进行相应处理

switch(ch)

{

case '+':

x=Pop(S)+Pop(S);

break;

case '-':

x=Pop(S); // Pop(S)弹出减数

x=Pop(S)-x; //Pop(S)弹出的是被减数

break;

case '*':

x=Pop(S)*Pop(S);

break;

case '/':

x=Pop(S); // Pop(S)弹出除数

if(x!=0.0)

x=Pop(S)/x; //Pop(S)弹出的是被除数

else { //除数为0时终止运行

cerr<<"Divide by 0!"<

exit(1);

}

break;

default: //读入的必为一个浮点数的最高位数字

ins.putback(ch); //把它重新回送到输入流中

ins>>x; //从字符串输入流中读入一个浮点数

}

Push(S,x); //把读入的一个浮点数或进行相应运算

//的结果压入到S栈中

ins>>ch; //输入下一个字符,以便进行下一轮循环处理 }

if(!StackEmpty(S))

{ //若栈中仅有一个元素,则它是后缀表达式的值,否则为出错 x=Pop(S);

if(StackEmpty(S))

return x;

else {

cerr<<"expression error!"<

exit(1);

}

}

else { //若最后栈为空,则终止运行

cerr<<"Stack is empty!"<

exit(1);

}

}

此算法的运行时间主要花在while循环上,它从头到尾扫描后缀表达式中的每一个数据(每个操作数或运算符均为一个数据),若后缀表达式由n个数据组成,则此算法的时间复杂度为O(n)。此算法在运行时所占用的临时空间主要取决于栈S的大小,显然,它的最大深度不会超过表达式中操作数的个数,因为操作数的个数与运算符(假定把’@’也看作为一个特殊运算符,即结束运算符)的个数相等,所以此算法的空间复杂度也同样为O(n)。

假定一个字符串a为:

char a[30]="12 3 20 4 / * 8 - 6 * +@";

对应的中缀算术表达式为12+(3*(20/4)-8)*6@,则使用如下语句调用上述函数得到的输出结果为54。

cout<

在进行这个后缀算术表达式求值的过程中,从第四个操作数入栈开始,每处理一个操作数或运算符后,栈S中保存的操作数和中间结果的情况如图4-4所示。

图4-4 栈S中数据的变化

3. 把中缀表达式转换为后缀表达式的算法

设以’@’字符作为结束符的中缀算术表达式已经保存在s1字符串中,转换后得到的后缀算术表达式拟存于s2字符串中。由中缀表达式转换为后缀表达式的规则可知:转换前后,表达式中的数值项的次序不变,而运算符的次序发生了变化,由处在两个运算对象的中间变为处在两个运算对象的后面,同时去掉了所有的括号。为了使转换正确,必须设定一个运算符栈,并在栈底放入一个特殊算符,假定为’@’字符,让它具有最低的运算符优先级,假定为数值0,此栈用来保存扫描中缀表达式得到的暂不能放入后缀表达式中的运算符,待它的两个运算对象都放入到后缀表达式以后,再令其出栈并写入到后缀表达式中。

把中缀表达式转换为后缀表达式算法的基本思路是从头到尾地扫描中缀表达式中的每个字符,对于不同类型的字符按不情况进行处理。若遇到的是空格则认为是分隔符,不需要进行处理;若遇到的是数字或小数点,则直接写入到s2中,并在每个数值的最后写入一个空格;若遇到的是左括号,则应把它压入到运算符栈中,待以它开始的括号内的表达式转换完毕后再出栈;若遇到的是右括号,则表明括号内的中缀表达式已经扫描完毕,把从栈底直到保存着的对应左括号之间的运算符依次退栈并写入s2串中;若遇到的是运算符,当该运算符的优先级大于栈顶运算符的优先级(加减运算符的优先级设定为1,乘除运算符的优先级设定为2,在栈中保存的特殊运算符’@’和’(’的优先级设定为0)时,表明该运算符的后一个运算对象还没有被扫描并放入到s2串中,应把它暂存于运算符栈中,待它的后一个运算对象从s1串中读出并写入到s2串中后,再另其出栈并写入s2串中;若遇到的运算符的优先级小于等于栈顶运算符的优先级,这表明栈顶运算符的两个运算对象已经被保存到s2串中,应将栈顶运算符退栈并写入到s2串中,对于新的栈顶运算符仍继续进行比较和处理,直到被处理的运算符的优先级大于栈顶运算符的优先级为止,然后另该运算符进栈即可。

按照以上过程扫描到中缀表达式结束符’@’时,把栈中剩余的运算符依次退栈并写入到后缀表达式中,再向s2写入表达式结束符’@’和字符串结束符’\0’,整个转换过程就处理完毕,在s2中就得到了转换成的后缀表达式。

例如,设中缀算术表达式s1为:10+(18+9*3)/15-6@,使用的运算符栈用R表示,则转换过程如下:

(1)开始时存放后缀表达式的字符串s2为空,R中压入有’@’算符,它具有最低的优先级0:

(2)当扫描到s1中的左括号时,s2和R中的数据变化如下:

(3)当扫描到s1中的数值3时,s2和R中的数据变化为:

(4)当扫描到s1中的右括号时,s2和R变为:

(5)当扫描到s1中的数值15时,s2和R又变为:

(6)当扫描到s1中的’@’字符时,s2和R为:

(7)当整个处理过程结束后,R栈为空,s2为:

将中缀算术表达式转换为后缀算术表达式的算法描述如下:

void Change(char* s1, char* s2)

//将字符串s1中的中缀表达式转换为存于字符串s2中的后缀表达式 {

Stack R; //定义用于暂存运算符的栈

InitStack(R); //初始化栈

Push(R,'@'); //给栈底放入'@'字符,它具有最低优先级0

int i,j;

i=0; //用于指示扫描s1串中字符的位置,初值为0

j=0; //用于指示s2串中待存字符的位置,初值为0

char ch=s1[i]; //ch保存s1串中扫描到的字符,初值为第一个字符

while(ch!='@')

{ //顺序处理中缀表达式中的每个字符

if(ch==' ')

//对于空格字符不做任何处理,顺序读取下一个字符

ch=s1[++i];

else if(ch=='(')

{ //对于左括号,直接进栈

Push(R,ch);

ch=s1[++i];

}

else if(ch==')')

{ //对于右括号,使括号内的仍停留在栈中的运算符依次 //出栈并写入到s2中

while(Peek(R)!='(')

s2[j++]=Pop(R);

Pop(R); //删除栈顶的左括号

ch=s1[++i];

}

else if(ch=='+'||ch=='-'||ch=='*'||ch=='/')

{ //对于四则运算符,使暂存在栈中的不低于ch优先级 //的运算符依次出栈并写入到s2中

char w=Peek(R);

while(Precedence(w)>=Precedence(ch))

{ // Precedence(w)函数返回运算符形参的优先级

s2[j++]=w;

Pop(R); w=Peek(R);

}

Push(R,ch); //把ch运算符写入栈中

ch=s1[++i];

}

else

{ //此处必然为数字或小数点字符

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

{ //把一个数值中的每一位依次写入到s2串中

s2[j++]=ch;

ch=s1[++i];

}

s2[j++]=' '; //被转换后的每个数值后放入一个空格 }

}

//把暂存在栈中的运算符依次出栈并写入到s2串中

ch=Pop(R);

while(ch!='@') {

if(ch=='(') {

cerr<<"expression error!"<

exit(1);

}

else {

s2[j++]=ch;

ch=Pop(R);

}

}

//在后缀表达式的末尾放入表达式结束符和字符串结束符

s2[j++]='@';

s2[j++]='\0';

}

求运算符优先级的Precedence函数为:

int Precedence(char op)

//返回运算符op所对应的优先级数值

{

switch(op)

{

case '+':

case '-':

return 1; //定义加减运算的优先级为1

case '*':

case '/':

return 2; //定义乘除运算的优先级为2

case '(':

case '@':

default:

return 0; //定义在栈中的左括号和栈底字符的优先级为0 }

}

在转换算法中,中缀算术表达式中的每个字符均需要扫描一遍,对于扫描到的每个运算符,最多需要进行入栈、出栈和写入后缀表达式这三次操作,对于扫描到的数字或小数点,只需要把它直接写入到后缀表达式即可。所以,此算法的时间复杂度为O(n),n为后缀表达式中字符的个数。该算法需要使用一个运算符栈,需要的深度不会超过中缀表达式中运算符的个数,所以此算法的空间复杂度至多也为O(n)。

利用表达式的后缀表示和堆栈技术只需要两遍扫描就可完成中缀算术表达式的计算,显然比直接进行中缀算术表达式计算的扫描次数要少得多。

在上述讨论的中缀算术表达式求值的两个算法中,把中缀表示转换为后缀表示的算法需要使用一个字符栈,而进行后缀表达式求值的算法又需要使用一个浮点数栈,这两个栈的元素类型不同,所以栈的类型无法作为全局量来定义,栈运算的函数也无法适应这种要求。为了解决这个问题,必须把Stack栈类型定义为模板类,把栈运算的函数定义为该类的公用成员函数,通过调用成员函数来实现栈的运算。这里对此不作深入讨论,留给读者作为练习。

假定采用类模板来定义Stack类和编写计算中缀算术表达式值的程序,若执行下面的主程序:

void main()

{

char a[30];

char b[30];

cout<<"请输入一个以'@'字符结束的中缀算术表达式:"<

cin.getline(a,sizeof(a)); //从键盘上输入一行表示中缀算术表达 //式的字符串存入到字符数组a中

Change(a,b);

cout<<"对应的后缀算术表达式为:"<

cout<

cout<<"求值结果为:"<

}

则得到的显示结果如下:

请输入一个以'@'字符结束的中缀算术表达式:

12+(3*(20/4)-8)*6@

对应的后缀算术表达式为:

12 3 20 4 /*8 -6 *+@

求值结果为:54

实验三 栈和队列的应用

实验三栈和队列的应用 1、实验目的 (1)熟练掌握栈和队列的结构以及这两种数据结构的特点、栈与队列的基本操作。 (2)能够在两种存储结构上实现栈的基本运算,特别注意栈满和栈空的判断条件及描述方法; (3)熟练掌握链队列和循环队列的基本运算,并特别注意队列满和队列空的判断条件和描述方法; (4)掌握栈和队列的应用; 2、实验内容 1)栈和队列基本操作实现 (1)栈的基本操作:采用顺序存储或链式存储结构(数据类型自定义),实现初始化栈、判栈是否为空、入栈、出栈、读取栈顶元素等基本操作,栈的存储结构自定义。 (2)队列的基本操作:实现循环队列或链队列的初始化、入队列、出队列、求队列中元素个数、判队列空等操作,队列的存储结构自定义。 2)栈和队列的应用 (1)利用栈的基本操作将一个十进制的正整数转换成二进制数据,并将其转换结果输出。 提示:利用栈的基本操作实现将任意一个十进制整数转化为R进制整数算法为: 十进制整数X和R作为形参 初始化栈 只要X不为0重复做下列动作 将x%R入栈 X=X/R 只要栈不为空重复做下列动作 栈顶出栈 输出栈顶元素 (2) 利用栈的基本操作对给定的字符串判断其是否是回文,若是则输出“Right”,否则输出“Wrong”。

(3) 假设循环队列中只设rear(队尾)和quelen(元素个数据)来分别表示队尾元素的位置和队中元素的个数,写出相应的入队和出队程序。 (4)选作题:编写程序实现对一个输入表达式的括号配对。 3、实验步骤 (1)理解栈的基本工作原理; (2)仔细分析实验内容,给出其算法和流程图; (3)用C语言实现该算法; (4)给出测试数据,并分析其结果; (5)在实验报告册上写出实验过程。 4、实验帮助 算法为: 1) 定义栈的顺序存取结构 2) 分别定义栈的基本操作(初始化栈、判栈为空、出栈、入栈等) 3) 定义一个函数用来实现上面问题: 十进制整数X和R作为形参 初始化栈 只要X不为0重复做下列动作 将X % R入栈 X=X/R 只要栈不为空重复做下列动作 栈顶出栈 输出栈顶元素 5、算法描述 (1))初始化栈S (创建一个空栈S) void initstack(sqstack *S) { S->base=(ElemType *) malloc(INITSIZE*sizeof(ElemType)); if(!S->base) exit (-1); S->top=0; /*空栈标志*/ S->stacksize = INITSIZE; } (2) 获取栈顶元素 int gettop(sqstack S,ElemType *e) //顺序钱 { if ( S.top==0 ) /* 栈空 */

角(基础)知识讲解

角(基础)知识讲解 撰稿:孙景艳审稿:赵炜 【学习目标】 1.掌握角的概念及角的表示方法,并能进行角度的互换; 2. 借助三角尺画一些特殊角,掌握角大小的比较方法; 3.会利用角平分线的意义进行有关表示或计算; 4. 掌握角的和、差、倍、分关系,并会进行有关计算; 5. 掌握互为余角和互为补角的概念及性质,会用余角、补角及性质进行有关计算; 6.了解方位角的概念,并会用方位角解决简单的实际问题. 【要点梳理】 【高清课堂:角397364 角的概念】 要点一、角的概念 1.角的定义: (1)定义一:有公共端点的两条射线组成的图形叫做角,这个公共端点是角的顶点,这两条射线是角的两条边.如图1所示,角的顶点是点O,边是射线OA、OB. (2 )定义二:一条射线绕着它的端点旋转而形成的图形,射线旋转时经过的平面部分是角的内部.如图2所示,射线OA绕它的端点O旋转到OB的位置时,形成的图形叫做角,起始位置OA是角的始边,终止位置OB是角的终边. 要点诠释: (1)两条射线有公共端点,即角的顶点;角的边是射线;角的大小与角的两边的长短无关.(2)平角与周角:如图1所示射线OA绕点O旋转,当终止位置OB和起始位置OA成一条直线时,所形成的角叫做平角,如图2所示继续旋转,OB和OA重合时,所形成的角叫做周角. 2.角的表示法:角的几何符号用“∠”表示,角的表示法通常有以下四种: 图1 图2

要点诠释: 用数字或小写希腊字母表示角时,要在靠近角的顶点处加上弧线,且注上阿拉伯数字或小写希腊字母. 3.角的画法 (1)用三角板可以画出30°、45°、60°、90°等特殊角. (2)用量角器可以画出任意给定度数的角. (3)利用尺规作图可以画一个角等于已知角. 要点二、角的比较与运算 1.角度制及其换算 角的度量单位是度、分、秒,把一个周角平均分成360等份,每一份就是1°的角,1° 的1 60 为1分,记作“1′”,1′的 1 60 为1秒,记作“1″”.这种以度、分、秒为单位的角 的度量制,叫做角度制. 1周角=360°,1平角=180°,1°=60′,1′=60″. 要点诠释: 在进行有关度分秒的计算时,要按级进行,即分别按度、分、秒计算,不够减,不够除的要借位,从高一位借的单位要化为低位的单位后再进行运算,在相乘或相加时,当低位得数大于等于60时要向高一位进位. 2.角的比较:角的大小比较与线段的大小比较相类似,方法有两种. 方法1:度量比较法.先用量角器量出角的度数,然后比较它们的大小. 方法2:叠合比较法.把其中的一个角移到另一个角上作比较. 如比较∠AOB和∠A′O′B′的大小:如下图,由图(1)可得∠AOB<∠A′O′B′;由图(2)可得∠AOB=∠A′O′B′;由图(3)可得∠AOB>∠A′O′B′.

栈的定义(精)

一、栈 1. 栈的定义 栈(Stack)又称堆栈,它是一种运算受限的线性表,其限制是仅允许在表的一端进行插入和删除运算。人们把此端称为栈顶,栈顶的第一个元素被称为栈顶元素,相对地,把另一端称为栈底。向一个栈插入新元素又称为进栈或入栈,它是把该元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称为出栈或退栈,它是把栈顶元素删除掉,使其下面的相邻元素成为新的栈顶元素。 在日常生活中,有许多类似栈的例子,如刷洗盘子时,依次把每个洗净的盘子放到洗好的盘子上,相当于进栈;取用盘子时,从一摞盘子上一个接一个地向下拿,相当于出栈。又如向枪支弹夹里装子弹时,子弹被一个接一个地压入,则为进栈;射击时子弹总是从顶部一个接一个地被射出,此为子弹出栈。 由于栈的插入和删除运算仅在栈顶一端进行,后进栈的元素必定先出栈,所以又把栈称为后进先出表(Last In First Out, 简称LIFO)。 例如,假定一个栈S为(a,b,c),其中字符c的一端为栈顶,字符c为栈顶元素。若向S压入一个元素d,则S变为(a,b,c,d),此时字符d为栈顶元素;若接着从栈S中依次删除两个元素,则首先删除的是元素d,接着删除的使元素c,栈S变为(a,b),栈顶元素为b。 2. 栈的存储结构 栈既然是一种线性表,所以线性表的顺序存储和链接存储结构同样适用于栈。 (1) 栈的顺序存储结构 栈的顺序存储结构同样需要使用一个数组和一个整型变量来实现,利用数组来顺序存储栈中的所有元素,利用整型变量来存储栈顶元素的下标位置。假定栈数组用stack[StackMaxSize]表示,指示栈顶位置的整型变量用top表示,则元素类型为ElemType 的栈的顺序存储类型可定义为: ElemType stack[StackMaxSize]; int top; 其中,StackMaxSize为一个整型全局常量,需事先通过const语句定义,由它确定顺序栈(即顺序存储的栈)的最大深度,又称为长度,即栈最多能够存储的元素个数;由于top用来指示栈顶元素的位置,所以把它称为栈顶指针。 栈的顺序存储结构同样可以定义在一个记录类型中,假定该记录类型用Stack表示,则定义为: struct Stack { ElemType stack[StackMaxSize]; int top; }; 在顺序存储的栈中,top的值为-1表示栈空,每次向栈中压入一个元素时,首先使top 增1,用以指示新的栈顶位置,然后再把元素赋值到这个位置上,每次从栈中弹出一个元素时,首先取出栈顶元素,然后使top减1,指示前一个元素成为新的栈顶元素。由此可知,对顺序栈的插入和删除运算相当于是在顺序表(即顺序存储的线性表)的表尾进行的,其时间复杂度为O(1)。

三角函数基本概念

三角函数基本概念 1.角的有关概念 (1)从运动的角度看,角可分为正角、负角和零角.(2)从终边位置来看,可分为象限角和轴线角. (3)若α与β是终边相同的角,则β可用α表示为S ={β|β=α+k ·360°,k ∈Z }(或{β|β=α+2k π,k ∈Z }). 2.象限角 3.弧度与角度的互化 (1)1弧度的角:长度等于半径长的弧所对的圆心角叫做1弧度的角,用符号rad 表示. (2)角α的弧度数:如果半径为r 的圆的圆心角α所对弧的长为l ,那么l =rα,角α的弧度数的绝对值是|α| = l r . (3)角度与弧度的换算①1°=π 180rad ;②1 rad =?π 180 (4)弧长、扇形面积的公式:设扇形的弧长为l ,圆心角大小为α(rad),半径为r ,又l =rα,则扇形的面积为 S =12lr =12 |α|·r 2 . 4.任意角的三角函数 三角函数 正弦 余弦 正切 定义 设是一个任意角,它的终边与单位圆交于点P (x ,y ),那么 y 叫做的正弦,记作sin x 叫做的余弦,记作cos x y 叫做的正切,记作tan α 三角函数 正弦 余弦 正切 各象限符号 Ⅰ 正 正 正 Ⅱ 正 负 负 Ⅲ 负 负 正 Ⅳ 负 正 负 各象限符号 口诀 一全正,二正弦,三正切,四余弦 5.三角函数线 设角α的顶点在坐标原点,始边与x 轴非负半轴重合,终边与单位圆相交于点P ,过P 作PM 垂直于x 轴于M ,则点M 是点P 在x 轴上的正射影.由三角函数的定义知,点P 的坐标为(cosα,sinα),即P(cosα,sinα),其中cosα=OM ,sinα=MP ,单位圆与x 轴的正半轴交于点A ,单位圆在A 点的切线与α的终边或其反向延长线相交于点T ,则tanα=AT .我们把有向线段OM 、MP 、AT 叫做α的余弦线、正弦线、正切线.

指针的定义与初始化

int a; float b; a=3; b=5; 3AB0 a 3AB8 b 35

?变量与地址 程序中: int i; float k; 内存中每个字节有一个编号-----地址…... …... 2000200120022005 内存 2003 i k 编译或函数调用时为其分配内存单元 变量是对程序中数据存储空间的抽象

…... …... 2000 20042006 2005整型变量i 10 变量i _pointer 2001 20022003?指针:一个变量的地址 ?指针变量:专门存放变量地址的变量叫~ 2000 指针 指针变量 变量的内容 变量的地址 指针变量变量变量地址(指针) 变量值 指向 地址存入指针变量

指针变量与其所指向的变量之间的关系 ? 一般形式:[存储类型] 数据类型*指针名; 变量i*i_pointer 3 i 2000 i_pointer *i_pointer &i i_pointer i=3;*i_pointer=3 合法标识符指针变量本身的存储类型 指针的目标变量的数据类型 表示定义指针变量 不是‘*’运算符 例int*p1,*p2; float *q ; static char *name; 注意: 1、int *p1, *p2;与int *p1, p2; 2、指针变量名是p1,p2 ,不是*p1,*p2 3、指针变量只能指向定义时所规定类型的变量 4、指针变量定义后,变量值不确定,应用前必须先赋值 指针的定义

?指针是一种变量,在定义的同时可以赋给它初始值,称为指针的初始化。 ?形式如下: 类型标识符*指针名=初始地址值; 例如, int m,n[8];char c; int *pm = &m;int *pn = n;char *pc = &c; 变量地址作初值时, 该变量在此之前已说明过,且类型应一致。 可用已赋初值的指针 去初始化其他指针变量.

栈的类型定义与基本操作

循环队链的出队 bool Dequeue( CSQueue &q, QElemType &e ) { int front; if( q.length == 0 ) return false; front = ( q.rear + 1 - q.length + MAXQSIZE ) % MAXQSIZE; e = q.elem[ front ]; q.length --; return true; } 循环队链的入队 bool Enqueue( CSQueue &q, QElemType e ) { if( q.length == MAXQSIZE ) return false; q.rear = ( q.rear + 1 ) % MAXQSIZE; q.elem[ q.rear ] = e; q.length ++; return true; } 链队的入队 void Enqueue( LQueue &q, QElemType e ) { LQueuePtr p; p = new QNode; p->data = e; p->next = q.rear->next; q.rear->next = p; q.rear = p; } 链队的出队 bool Dequeue( LQueue &q, QElemType &e ) { LQueuePtr p; if( q.rear->next == q.rear ) return false; p = q.rear->next; e = p->next->data; q.rear->next = p->next; delete p; return true; } 顺序栈的类型定义与基本操作:

栈和队列(必备)

栈和队列是操作受限的线性表,好像每本讲数据结构的数都是这么说的。有些书按照这个思路给出了定义和实现;但是很遗憾,这本书没有这样做,所以,原书中的做法是重复建设,这或许可以用不是一个人写的这样的理由来开脱。 顺序表示的栈和队列,必须预先分配空间,并且空间大小受限,使用起来限制比较多。而且,由于限定存取位置,顺序表示的随机存取的优点就没有了,所以,链式结构应该是首选。 栈的定义和实现 #ifndef Stack_H #define Stack_H #include "List.h" template class Stack : List//栈类定义 { public: void Push(Type value) { Insert(value); } Type Pop() { Type p = *GetNext(); RemoveAfter(); return p; }

Type GetTop() { return *GetNext(); } List ::MakeEmpty; List ::IsEmpty; }; #endif 队列的定义和实现 #ifndef Queue_H #define Queue_H #include "List.h" template class Queue : List//队列定义{ public: void EnQueue(const Type &value) { LastInsert(value); } Type DeQueue() {

Type p = *GetNext(); RemoveAfter(); IsEmpty(); return p; } Type GetFront() { return *GetNext(); } List ::MakeEmpty; List ::IsEmpty; }; #endif 测试程序 #ifndef StackTest_H #define StackTest_H #include "Stack.h" void StackTest_int() { cout << endl << "整型栈测试" << endl;

如何透彻理解C语言中指针的概念

如何透彻理解C语言中指针的概念 强大的指针功能是C语言区别于众多高级语言的一个重要特征。C语言指针的功能强大,使用灵活多变,可以有效地表示复杂的数据结构、动态分配内存、高效地使用数组和字符串、使得调用函数时得到多个返回值。而它的应用远不限于此。初学者对于指针的概念总是感到无所适从,有时觉得“自己懂了,为什么编译器就是不懂呢”,常有茫然和无助的感觉。 学好指针的关键在于深入了解内存地址的空间可以理解为一个一维线性空间,内存的编址和寻址方法,以及指针在使用上的一些规定。事实上,指针就是方便我们对内存地址直接进行操作的,是为程序员服务的,我们只要抓住指针想要帮助我们解决什么问题这个核心,就可以轻松地理解它的工作原理。 什么是指针,指针有什么作用 指针就是指向一个特定内存地址的一个变量。简化了的内存空间模型是按照从0到某一个数(比如1048575=1M-1)的一维线性空间,其中的每一个数对应一个存储单元,即1个字节。指针有两个属性:指向性和偏移性。指向性指的是指针一定要有一个确定的指向,偏移性则是体现指针重要应用的方面,即指针可以按程序员的要求向前或向后偏移。 指针的应用往往与数组联系在一起,为了方便说明问题,不妨从数组开始解释指针的偏移。数组就是许多的变量,它的一个重要特征就是在内存空间中连续地存放,而且是按下标顺序存放。比如我们定义一个有100个变量的一维整型数组,它一定从内存的某一个存储单元开始按数组下标顺序存放,连续占用100*4=400字节。当我们定义一个数组时,系统就会自动为它分配一个指针,这个指针指向数组的首地址。(在本文剩余部分的论述中,不加区分地使用“指向数组的首地址”与“指向数组的第一个元素”这两种说法,事实上这两种说法也是一致的。) 为了让系统了解每一次指针偏移的单位,也为了方便程序员进行指针偏移(让程序员记住一个整形变量占用4字节,一个字符型变量占用1字节……等等是很麻烦的),不用每次去计算要偏移多少个字节,C语言引入了指针的基类型的概念。基类型的作用就是让系统了解某个指针每次偏移的字节数。比如,对于一个字符型指针,它每次偏移(比如ptr=ptr+1)所起到的作用就是让指针偏移1字节;而对于一个整型指针,它每次偏移就应该是4字节。这样操作数组时就带来了方便。比如对于一个指向某个整型数组起始存储单元(称为首地址)的指针ptr,ptr=ptr+1就表示将该指针指向这个数组的下一个元素的存储单元,即向后移动4字节,而不仅仅是移动一个存储单元(即移动1字节)。 &()、*()、和[ ]运算符的意义 在本文中,将&()、*()和[ ]都看成是运算符。这样可以方便理解这三个概念。简单地说,&()将某个标识符(比如变量)转化为其在内存空间中的地址,而*()是产生一个对应于某个地址的标识符,[ ]就更复杂一点,ptr[i]表示

实验二_栈、队列地实现与应用

实验二栈、队列的实现及应用 实验课程名:数据结构与算法 专业班级:学号::

/*构造空顺序栈*/ int InitStack(SqStack *S) //InitStack() sub-function { S->base = (SElemType *)malloc(STACK_INIT_SIZE*sizeof(SElemType)); if (!S->base) { printf("分配空间失败!\n"); return (ERROR); } S->top = S->base; S->stacksize = STACK_INIT_SIZE; printf("栈初始化成功!\n"); return (OK); } //InitStack() end /*取顺序栈顶元素*/ int GetTop(SqStack *S, SElemType *e) //GetTop() sub-function { if (S->top == S->base) { printf("栈为空!\n"); //if empty SqStack return (ERROR); } *e = *(S->top - 1); return (OK); } //GetTop() end /*将元素压入顺序栈*/ int Push(SqStack *S) //Push() sub-function { SElemType e; if (S->top - S->base>S->stacksize) { S->base = (SElemType *)realloc(S->base, (S->stacksize + STACKINCREMENT*sizeof(SElemType))); if (!S->base) { printf("存储空间分配失败!\n"); return (ERROR); } S->top = S->base + S->stacksize; S->stacksize += STACKINCREMENT; } fflush(stdin);//清除输入缓冲区,否则原来的输入会默认送给变量x

指针的初始化和定义

每个指针都有一个与之关联的数据类型,该数据类型决定了指针所有指向的对象的类型。例如,一个int 型指针只能指向int 型对象。 1.指针变量的定义 C++语言使用* 号把一个标识符声明为指针: vector *pvec; int *p1, *p2; string *pstring; 提示:理解指针声明语句时,请从右向左阅读。 2.另一种声明指针的风格 在定义指针变量时,可用空格符号* 与气候的标识符分别开来,如下: string* ps; 也就是说,该语句把ps定义为一个指向string 类型对象的指针。 这种指针声明风格容易引起这样的误解:把string * 理解为一种数据类型,认为在同意声明语句中定义的其他变量也指向string 类型对象的指针。 如果使用下面的语句: string* p1, p2;

实际上只把ps1 定义为指针,而ps2并非指针,只是一个普通的string 对象而已。如果需要在一个声明语句中定义了两个指针,必须在每一个变量标识符前再加符号* 声明: string* ps1,*ps2; 3.指针的可能取值 一个有效的指针必然是以下三种状态之一:1.保存一个特定对象的地址;2.指向某个对象后面的另一对象;3.是0值。若指针保存0值,表明它不指向任何对象。未初始化的指针是无效的,直到给该指针赋值后,才可以使用它。下面的定义是合法的: int ival = 1024; int *pi = 0; int *pi2 = &ival; int *pi3; pi = pi2; pi2 = 0; 4.避免使用未初始化的指针 提示:很多运行时错误都源于使用了未初始化的指针。 就像使用其他没有初始化的变量一样,使用未初始化的指针时的行为C++标准中并没有定义,它几乎总会导致运行时崩溃。然而,导致崩溃的这一原因很难发现。

数据结构栈的定义及基本操作介绍

北京理工大学珠海学院实验报告 ZHUHAI CAMPAUS OF BEIJING INSTITUTE OF TECHNOLOGY 班级软件工程3班学号 150202102309姓名郭荣栋 指导教师余俊杰成绩 实验题目栈的实现与应用实验时间 一、实验目的、意义 (1)理解栈的特点,掌握栈的定义和基本操作。 (2)掌握进栈、出栈、清空栈运算的实现方法。 (3)熟练掌握顺序栈的操作及应用。 二、实验内容及要求 1.定义顺序栈,完成栈的基本操作:建空栈、入栈、出栈、取栈顶元素(参见教材45页)。 2. 调用栈的基本操作,将输入的十进制数转换成十六进制数。 3. 调用栈的基本操作,实现表达式求值,如输入3*(7-2)#,得到结果15。 三、实验结果及分析 (所输入的数据及相应的运行结果,运行结果要有提示信息,运行结果采用截图方式给出。)

四、程序清单(包含注释) 1、2. #include #include #include using namespace std; #define OK 1 #define ERROR 0 #define OVERFLOW -2 #define MAXSIZE 100 #define INCREASEMENT 10 #define STACK_INIT_SIZE 100 #define STACKINCREMENT 10

typedef int SElemType; typedef int Status; typedef struct{ SElemType *base; SElemType *top; int stacksize; }Sqstack; void StackTraverse(Sqstack S) { while (S.top != S.base) { cout << *(S.top-1) << endl; S.top--; } } Status InitStack(Sqstack &S){ S.base=(SElemType *)malloc(STACK_INIT_SIZE*sizeof(SElemType)); if(!S.base){ exit(OVERFLOW); }

栈和队列及其应用7

栈和队列及其应用 栈和队列通常用来存储程序执行期间产生的一些临时信息。这两种特殊表结构的共同特点是,只做插入和删除,不做查找,而且所有的插入和删除只在端点进行。 栈是一种特殊的表结构,满足先进后出策略(LIFO:last in first out),栈的插入和删除操作只在同一端点进行。 可以进行插入的端点叫栈顶(top),另一个端点叫栈底(bottom)。 栈的插入操作又叫进栈(push)或压栈,栈删除操作又叫退栈(pop)或出栈。 栈的结构示意图 注意:进栈和退栈可以不定期地、反复交替进行。 生活中类似栈的应用的例子:装药片的小圆桶,军用子弹卡等。 思考:假设有编号为1,2,3的3辆车,如果按照编号为1,2,3的顺序入栈,那么可能的出栈顺序有几种情况??? 栈的存储方式: 1.顺序存储 2.链式存储 栈的常见操作(顺序存储方式实现) 数组s[M]存储一个栈(M代表栈的容量),top变量指示栈顶指针(下标)。 M=6时:

进栈算法: //宏定义 #define M 6 #define EMPTY -1 void pushs(int s[],int &top) { int x,k; cout<<"请输入要进栈的元素值x="; cin>>x; if(top==M-1) { cout<< "栈已经满,进栈失败!"<

小学线和角的基本概念总复习

小学六年级数学总复习(九) 班级______ 姓名_______ 得分__________ 复习内容: ① 线和角的基本概念 ② 平面几何图形的基本概念 一、填空 1. 2. 从一点引出( ),就组成一个角,这个点叫做角的( ),这( ) 叫做角的边。 3. 两条直线相交,有一个角是直角,这两条直线叫做( ),其中一条直线叫做另一 条直线的( ),这两条直线的交点叫做( )。 4. 一个三角形有两条边相等,这个三角形叫做( )。如果这个三角形的顶角是70°, 其余两个底角各是( )度。 5. 直角度数的 31 ,等于平角度数的()(),等于周角度数的()() 。 6. 在直角三角形中,如果一个锐角的度数是另一个锐角度数的一半,那么这两个锐角的度 数分别是( )度和( )度。 7. 一个三角形的每个角都是60°,如果按角分,这个三角形是( )三角形;如果按边 分,这个三角形是( )三角形。 8. 平行四边形的两组对边( ),两组对角( )。 9. 在梯形里,互相平行的一组对边分别叫梯形的( )和( ),不平形的一组对边 叫梯形的( )。 10. 等腰三角形有( )条对称轴,等边三角形有( )条对称轴,长方形有( )条 对称轴,正方形有( )条对称轴,等腰梯形有( )条对称轴,圆有( )条对称轴。 二、判断(对的请在括号内打“√”,错的打“×”。) 1. 一条直线长10厘米。……………………………………………………( ) 2. 角的两条边越长,角就越大。………………………………………… ( ) 3. 通过圆心的线段叫做圆的直径。……………………………………… ( ) 4. 比90°大的角叫做钝角。……………………………………………… ( ) 5. 两个正方形一定可以拼成一个长方形。……………………………… ( ) 6. 四条边相等的四边形不一定是正方形。……………………………… ( ) 7. 经过两点可以作无数条直线。………………………………………… ( ) 8. 两条不平行的直线一定相交。………………………………………… ( ) 9. 平角是一条直线。……………………………………………………… ( ) 10.平行四边形没有对称轴。……………………………………………… ( )

小学线和角的基本概念总复习

小学线和角的基本概念总 复习 The latest revision on November 22, 2020

小学六年级数学总复习(九) 班级______ 姓名_______ 得分__________ 复习内容: ① 线和角的基本概念 ② 平面几何图形的基本概念 一、填空 1. 2. 从一点引出( ),就组成一个角,这个点叫做角的( ),这 ( ) 叫做角的边。 3. 两条直线相交,有一个角是直角,这两条直线叫做( ),其中一条 直线叫做另一条直线的( ),这两条直线的交点叫做( )。 4. 一个三角形有两条边相等,这个三角形叫做( )。如果这个三角形 的顶角是70°,其余两个底角各是( )度。 5. 直角度数的3 1,等于平角度数的()(),等于周角度数的()()。 6. 在直角三角形中,如果一个锐角的度数是另一个锐角度数的一半,那么这 两个锐角的度数分别是( )度和( )度。 7. 一个三角形的每个角都是60°,如果按角分,这个三角形是( )三角 形;如果按边分,这个三角形是( )三角形。 8. 平行四边形的两组对边( ),两组对角( )。 9. 在梯形里,互相平行的一组对边分别叫梯形的( )和( ),不平 形的一组对边叫梯形的( )。 10. 等腰三角形有( )条对称轴,等边三角形有( )条对称轴,长方形 有( )条对称轴,正方形有( )条对称轴,等腰梯形有( )条对 称轴,圆有( )条对称轴。 二、判断(对的请在括号内打“√”,错的打“×”。) 1. 一条直线长10厘米。…………………………………………………… ( ) 2. 角的两条边越长,角就越大。………………………………………… ( ) 3. 通过圆心的线段叫做圆的直径。……………………………………… ( ) 4. 比90°大的角叫做钝角。……………………………………………… ( )

用顺序结构表示栈并实现栈地各种基本操作

栈的顺序表示和实现 2.2基础实验 2.2.1实验目的 (1)掌握栈的顺序表示和实现 (2)掌握栈的链式表示和实现 (3)掌握队列的顺序表示和实现 (4)掌握队列的链式表示和实现 2.2.2实验内容 实验一:栈的顺序表示和实现 【实验内容与要求】 编写一个程序实现顺序栈的各种基本运算,并在此基础上设计一个主程序,完成如下功能: (1)初始化顺序栈 (2 )插入元素 (3)删除栈顶元素 (4)取栈顶元素 (5)遍历顺序栈 (6)置空顺序栈 【知识要点】 栈的顺序存储结构简称为顺序栈,它是运算受限的顺序表。 对于顺序栈,入栈时,首先判断栈是否为满,栈满的条件为:p->top= =MAXNUM-1 ,栈满时,不能入栈;否则岀现空间溢岀,引起错误,这种现象称为上溢。 岀栈和读栈顶元素操作,先判栈是否为空,为空时不能操作,否则产生错误。通常栈空作为一种控制转移的条件。 注意: (1)顺序栈中元素用向量存放 (2)栈底位置是固定不变的,可设置在向量两端的任意一个端点 (3)栈顶位置是随着进栈和退栈操作而变化的,用一个整型量top (通常称top为栈顶指针)来指示当前栈顶位置 【实现提示】 /*定义顺序栈的存储结构*/

typedef struct { ElemType stack[MAXNUM]; int top; }SqStack; /*初始化顺序栈函数*/ void lnitStack(SqStack *p) {q=(SqStack*)malloc(sizeof(SqStack)/* 申请空间*/) /*入栈函数*/ void Push(SqStack *p,ElemType x) {if(p->toptop=p->top+1; /* 栈顶+1*/ p->stack[p->top]=x; } /* 数据入栈*/ } /*岀栈函数*/ ElemType Pop(SqStack *p) {x=p->stack[p->top]; /* 将栈顶元素赋给x*/ p->top=p->top-1; } /* 栈顶-1*/ /*获取栈顶元素函数*/ ElemType GetTop(SqStack *p) { x=p_>stack[p_>top];} /*遍历顺序栈函数*/ void OutStack(SqStack *p) { for(i=p->top;i>=0;i--) printf("第%d 个数据元素是:%6d\n",i,p->stack[i]);} /*置空顺序栈函数*/ void setEmpty(SqStack *p) { p->top= -1;} 【参考程序】 #include #include #define MAXNUM 20 #define ElemType int /*定义顺序栈的存储结构*/ typedef struct { ElemType stack[MAXNUM]; int top; }SqStack; /*初始化顺序栈*/ void InitStack(SqStack *p) { if(!p) printf("Eorror");

数据结构实验二(栈和队列)

实验二栈和队列的基本操作及其应用 一、实验目的 1、掌握栈和队列的顺序存储结构和链式存储结构,以便在实际中灵活应用。 2、掌握栈和队列的特点,即后进先出和先进先出的原则。 3、掌握栈和队列的基本运算,如:入栈与出栈,入队与出队等运算在顺序 存储结构和链式存储结构上的实现。 二、实验内容 本次实验提供4个题目,每个题目都标有难度系数,*越多难度越大,学生 可以根据自己的情况任选一个! 题目一:回文判断(*) [问题描述] 对于一个从键盘输入的字符串,判断其是否为回文。回文即正反序相同。如 “abba”是回文,而“abab”不是回文。 [基本要求] (1)数据从键盘读入; (2)输出要判断的字符串; (3)利用栈的基本操作对给定的字符串判断其是否是回文,若是则输出 “Yes”,否则输出“No”。 [测试数据] 由学生任意指定。 题目二:顺序栈和循环队列基本操作(*) [基本要求] 1、实现栈的基本操作 六项基本操作的机制是:初始化栈:init_stack(S);判断栈空:stack_empty(S);取栈顶元素:stack_top(S,x);入栈:push_stack(S,x);出栈:pop_stack(S);判断栈满:stack_full(S) 2、实现队列的基本操作 六项基本操作的机制是:初始化队列:init_queue(Q);判断队列是否为空:queue_empty(Q);取队头元素:queue_front(Q,x);入队:enqueue(Q,x);出队:outqueue(Q,x);判断队列是否为满:queue_full(Q) [测试数据]

由学生任意指定。 题目三:商品货架管理(**) [问题描述] 商店货架以栈的方式摆放商品。生产日期越近的越靠近栈底,出货时从栈顶取货。一天营业结束,如果货架不满,则需上货。入货直接将商品摆放到货架上,则会使生产日期越近的商品越靠近栈顶。这样就需要倒货架,使生产日期越近的越靠近栈底。 [基本要求] 设计一个算法,保证每一次上货后始终保持生产日期越近的商品越靠近栈底。 [实现提示] 可以用一个队列和一个临时栈作为周转。 [测试数据] 由学生任意指定。 三、实验前的准备工作 1、掌握栈的逻辑结构和存储结构。 2、熟练掌握栈的出栈、入栈等操作。 3、掌握队列的逻辑结构和存储结构。 4、熟练掌握队列的出队、入队等操作 四、实验报告要求 1、实验报告要按照实验报告格式规范书写。 *2、写出算法设计思路。 3、实验上要写出多批测试数据的运行结果。 4、结合运行结果,对程序进行分析。 题目四:Rails(ACM训练题) Description There is a famous railway station in PopPush City. Country there is incredibly hilly. The station was built in last century. Unfortunately, funds were extremely limited that time. It was possible to establish only a surface track. Moreover, it turned out that the

第3章_栈和队列_习题参考答案

第四第3章栈和队列 一、基础知识题 3.1 有五个数依次进栈:1,2,3,4,5。在各种出栈的序列中,以3,4先出的序列有哪几个。(3在4之前出栈)。 【解答】34215 ,34251,34521 3.2 铁路进行列车调度时,常把站台设计成栈式结构,若进站的六辆列车顺序为:1,2,3,4,5,6,那么是否能够得到435612,325641,154623和135426的出站序列,如果不能,说明为什么不能;如果能,说明如何得到(即写出"进栈"或"出栈"的序列)。 【解答】输入序列为123456,不能得出435612和154623。不能得到435612的理由是,输出序列最后两元素是12,前面4个元素(4356)得到后,栈中元素剩12,且2在栈顶,不可能让栈底元素1在栈顶元素2之前出栈。不能得到154623的理由类似,当栈中元素只剩23,且3在栈顶,2不可能先于3出栈。 得到325641的过程如下:1 2 3顺序入栈,32出栈,得到部分输出序列32;然后45入栈,5出栈,部分输出序列变为325;接着6入栈并退栈,部分输出序列变为3256;最后41退栈,得最终结果325641。 得到135426的过程如下:1入栈并出栈,得到部分输出序列1;然后2和3入栈,3出栈,部分输出序列变为13;接着4和5入栈,5,4和2依次出栈,部分输出序列变为13542;最后6入栈并退栈,得最终结果135426。 3.3 若用一个大小为6的数组来实现循环队列,且当前rear和front的值分别为0和3,当从队列中删除一个元素,再加入两个元素后,rear和front的值分别为多少? 【解答】2和 4 3.4 设栈S和队列Q的初始状态为空,元素e1,e2,e3,e4,e5和e6依次通过栈S,一个元素出栈后即进队列Q,若6个元素出队的序列是e3,e5,e4,e6,e2,e1,则栈S的容量至少应该是多少? 【解答】4 3.5 循环队列的优点是什么,如何判断“空”和“满”。 【解答】循环队列解决了常规用0--m-1的数组表示队列时出现的“假溢出”(即队列未满但不能入队)。在循环队列中我们仍用队头指针等于队尾指针表示队空,而用牺牲一个单元的办法表示队满,即当队尾指针加1(求模)等于队头指针时,表示队列满。也有通过设标记以及用一个队头或队尾指针加上队中元素个数来区分队列的“空”和“满”的。 3.6 设长度为n的链队列用单循环链表表示,若只设头指针,则入队和出队的时间如何?若只设尾指针呢? 【解答】若只设头指针,则入队的时间为O(n),出队的时间为O(1)。若只设尾指针,则入队和出队的时间均为O(1)。 3.7 指出下面程序段的功能是什么? (1) void demo1(SeqStack S) {int i,arr[64],n=0; while(!StackEmpty(S)) arr[n++]=Pop(S); for(i=0;i

角的概念的推广教学设计

《角的概念的推广》——教学设计 一、教材分析 1、地位与作用 我校使用的是高等教育出版社由李广全、李尚志编写的基础模块《数学》教材。角的概念的推广来自本教材的第五章的第一节。这节课主要内容是角的概念的推广,首先通过生产、生活的实际例子阐明了推广角的必要性和实际意义,然后又以“动”的观点给出了正、负、零角的概念,最后引入了象限角的概念。本节课的学习具有以下必要性: 1、在实际生活中应用广泛。 2、是前面所学函数类型的延伸。 3、是描述旋转运动和周期性现象的重要特征量。 4、是专业的重要学习工具。 2、课时安排 5.1.1节:任意角的概念的推广,45分钟。 3、教学目标 知识目标:掌握用旋转定义角的概念;理解并掌握“正角”、“负角”、“象限角”的含义,培养学生用运动变化观点审视事物。 能力目标:通过布置课前任务——培养学生的自学能力; 通过让学生讨论、讲解——锻炼学生的语言表达能力; 通过让学生解决生活中与数学相关的问题——提高学生分析问题、解决问题的能力。 情感目标:通过解决生活中的数学问题——让学生感悟数学的实用性; 通过小组活动——培养学生的团队协作意识。 4、教学重点难点 教学重点:理解并掌握正角、负角、零角的定义,掌握象限角的判断方法。 教学难点:旋转方向的观察、象限角的判断。

二、学情分析 学习对象为中职一年级学生,虽然有一定的观察能力,他们普遍对初中数学有恐惧感,数学基础普遍较差;学生重视专业课,忽视基础课的学习;学生对新内容的学习有一定的兴趣和积极性,但缺乏耐心和恒心。 三、教学策略选择与设计 针对职业学校学生、学科特点,更多的学习活动设计将以观察、识别、分析、判断、讨论为主线,以掌握方法、步骤为目标,让学生更能体会到数学的实用性。引入正角、负角、零角的定义,象限角的概念。树立运动变化的观点,理解静是相对的,动是绝对的,并由此深刻理解推广后的角的概念。 教学过程是教师和学生共同参与的过程,启发学生自主性学习,充分调动学生的积极性、主动性;有效地渗透数学思想方法,提高学生素质。根据这样的原则和所要完成的教学目标,并为激发学生的学习兴趣,我采用如下的教学策略:(1)引导发现法。通过已学过角的定义来发现角的概念是可以推广的。 (2)任务驱动法。通过实际问题,使角的推广变得更为必要,如螺丝扳手紧固螺丝、时针与分针、车轮的旋转等等,都能形成角的概念,给学生以直观的印象,形成正角、负角、零角的概念,突出角的概念的理解与掌握。 (3)多媒体法。通过讲解、归纳、概括来介绍角的有关要概念,通过练习来达到巩固知识、突出重点、解决难点。 教给学生方法比教给学生知识更重要,本节课注重调动学生积极思考、主动探索,尽可能地增加学生参与教学活动的时间和空间,我进行了以下学法指导:(1)分类学习法:了解数学知识是有规律可循的,要弄清角的分类及分类的方法。 (2)合作学习法:通过分组合作让学生学会观察、分析和解决问题。

相关文档
最新文档