1.栈(Stack)
1.1栈的定义
栈和队列是两种重要的线性结构。
从数据结构角度看,栈和队列也是线性表,其特殊性在于栈和队列的基本操作是线性表操作的子集,它们是操作受限的线性表。
从数据类型角度看,它们是和线性表大不相同的两种重要的抽象数据类型。
栈(Stack)是限定只能在表的一端进行插入和删除操作的线性表。
栈顶(top):允许插入和删除运算的一端。
栈底(bottom):不允许插入和删除的另一端。
入栈/出栈(push):在栈顶进行的插入操作。
出栈/退栈(pop):在栈顶进行的删除操作。
栈的特点:后进先出LIFO(Last In First Out)
类似生活中搬箱子的动作。
示例:
已知数据元素序列为(a,b,c,d),将数据元素按以上顺序依次入栈,并给出数据元素d和c出栈后栈的状态。
1.2栈的抽象数据类型(ADT Stack)
数据元素集合:具有相同性质数据元素的一个有限序列,且只能在称为栈顶的一端进行插入和删除操作。
基本操作:
初始化栈(InitStack):初始化栈
入栈(Push):在栈顶插入新的数据元素
出栈(Pop):删除栈顶数据元素
取栈顶元素(GetTop):获取栈顶的数据元素
判栈空(StackEmpty):判断栈是否为空
1.3栈的顺序存储结构与基本运算的实现
1.3.1顺序栈的数据类型
#define MaxSize 100
typedef struct
{
Elemtype data[MaxSize];//数组存储空间
int top;
}SqStack;// 顺序栈数据类型
MaxSize为顺序栈的最大容量
top为栈顶元素的下标,0<=top<=MaxSize-1
栈空:top=-1
栈满:top=MaxSize-1
1.3.2入栈操作
1、判断栈是否已满,若满则产生上溢出错误,退出算法,否则执行第二步;
2、栈顶下标增一,指向新的栈顶位置;
3、将新元素置于栈顶。
1.3.3入栈实现
bool Push(SqStack &S,ElemType item)
{
if(S.top==MaxSize-1)//判断栈是否满
{
cout<<"栈满"<<endl;
return false;
}
S.top++;
S.data[S.top]=item;
return true;
}
时间复杂度:O(1)
1.3.4出栈操作
1、判断栈是否为空,若空则产生下溢出错误,退出算法,否则执行第二步;
2、栈顶元素出栈;
3、栈顶下标减一,指向新的栈顶位置。
1.3.5出栈实现
bool Pop(SqStack &S,ElemType &item)
{
if(S.top==-1)
{
cout<<"栈空"<<endl;
return false;
}
item=S.data[S.top];
S.top--;
return ture;
}
时间复杂度:O(1)
1.4栈的链式存储结构与基本运算的实现
1.4.1链栈的数据类型
typedef struct LNode
{
ElemType data;//数据域
struct LNode *next;//后继结点指针
}LinkStNode;//链栈结点类型
S:单链表头指针,指向头结点。
栈顶:单链表第一个元素结点的位置即头结点的后一个位置。
1.4.2入栈实现
bool Push(LinkStNode *S,ElemType item)
{//带头结点单链表的表头插入法
LinkStNode *t=new LinkStNode;//第一处:生成新结点
if(t==NULL)
{
cout<<"内存不足";
return false;
}
t->data=item;
//第二处:在栈顶插入新结点
t->next=S->next;
S->next=t;//第三处
return true;
}
1.4.3出栈实现
bool Pop(LinkStNode *S,ElemType &item)
{//删除单链表的第一个元素结点
if(S->next===NULL)//第一处:判断栈是否为空
{
cout<<"栈空";
return false;
}
//第二处:删除栈顶元素
LinkStNode *t=S->next;
S->next=t->next;
item=t->data;
delete t;
return true;
}
//时间复杂度:O(1)
1.5栈的应用
1.5.1数制转换
数制转换原理:
十进制数N和其他d进制数的转换基于原理:N=(N div d)*d+N mod d(div为整除,mod为求余)
操作步骤(除留余数法):
1、将N除以d,取其商和余数;
2、判断商是否为零:若商不为零,则将商赋值给N,并转向第一步;若商为零,则转换结束。
最先求得的余数是结果的最低位,最后求得的余数是结果的最高位,既运算顺序与结果的各位数字的次序相反,符合栈的特点。
1.5.2数制转换程序实现
void Convert(int num,int d)
{//num为待转换数。d为进制
SqStack S;
ElemType result;
int r;//余数
char ch[]="0123456789ABCDEF";//进制所使用的字符
InitStack(S);
while(num!=0)
{
r=num%d;//取余数r
Push(S,ch[r]);//余数入栈
num=num/d;//利用商进行下一次运算
}
while(!StackEmpty(S))
{
Pop(S,result);
cout<<result;
}
}
1.5.3表达式求值
算术表达式的定义:任何一个表达式都是由操作数、运算符和界限符组成。
算术表达式三种形式:
1、前缀表达式=运算符+操作数1+操作数2
2、中缀表达式=操作数1+运算符+操作数2
3、后缀表达式=操作数1+操作数2+运算符
1.5.3.1中缀表达式的运算规则
1、先计算括弧内后计算括弧外;
2、在无括号或同层括号内时,先乘除后加减;
3、同一优先级运算,先左后右。
1.5.3.2中缀表达式运算特点
中缀表达式的运算不一定能按运算符出现的先后次序进行,它不仅要依赖运算符优先级,而且还要处理括号。因此,中缀表达式不方便计算机程序对表达式进行求值。
1.5.3.3后缀表达式的特点与运算规则
后缀表达式没有括号,也无需考虑优先级,运算符在式中出现的顺序恰为表达式的运算顺序;方便程序进行求值。
1.5.3.4中缀表达式求值
求值方法步骤:
1、中缀表达式转换成后缀表达式;
2、对后缀表达式进行求值。
1.5.3.4.1中缀表达式向后缀表达式的转换
中缀转后缀思路:
1、对原表达式中出现的每一个运算符是否即刻进行运算取决于在它后面出现的运算符;
2、如果它的优先级“高或等于”后面的运算,则它的运算先进行,否则就得得等待在它之后出现的所有优先级高于它的“运算”都完成之后再进行。
用到的数据结构:
1、 char数组str1:存放中缀表达式,以#结尾;
2、char数组str2:存放转换后的后缀表达式;
3、char型栈S:存放运算符,包括*/+-(#
4、其中,*/优先级设为2,+-优先级设为1,为处理方便,将(优先级设为3,#优先级设为0。
5、while(str1[i]!='\0')
(1)如果str1[i]是操作数,则将str1[i]存入str2中;
(2)如果str1[i]是'(',则str1[i]入栈;
(3)如果str1[i]是')',则将栈S中'('之前的运算符依次出栈并存放到str2中,然后将栈S中'('出栈;
(4)如果str1[i]是四则运算符,则还须比较str1[i]与栈顶运算符top的优先级,若str1[i]<=top(除栈顶运算符为"("外),则依次出栈并存入到str2中,然后将str1[i]进栈;
6、str1扫描完毕后,将栈 S中剩余运算符依次出栈并存入str2中。
1.5.3.4.2中缀转后缀算法
依次扫描中缀表达式,读取元素:
操作数(输出到后缀表达式);
运算符(与栈顶运算符比较优先级)->大于栈顶(入栈)或小于栈顶(出栈);
(号:入栈;
)号依次出栈,直到遇到(号;
扫描完毕后,依次把栈内运算符出栈并输出。
1.5.3.4.3后缀表达式求值的过程
1、每个运算符和在它出现之前出现且紧靠它的两个操作数构成一个最小表达式;
2、对后缀式从左向右”扫描“,若读入的是一个操作数,就将它入数值栈;
3、若读入的是一个运算符op,就从数值栈中连续出栈两个元素(两个操作数),假设x2和x1,计算x1 op x2之值,并将计算结果入数值栈;
4、对整个后缀表达式读入结束时,栈顶元素就是计算结果。
1.5.3.4.4后缀求值算法
依次扫描后缀表达式,读取元素:
操作数(入栈);
运算符(出栈操作数,执行相应运算,并将运算结果入栈);
扫描完毕后,栈顶元素就是表达式的值。