栈(stack)是一个特殊的线性表,是限定仅在一端(通常在表尾)进行插入和删除操作的线性表;又称为后进先出(Last In First Out)的线性表,简称LIFO结构
例如:栈s=(a1,a2,…):a1称为栈底元素,an称为栈顶元素
栈顶:允许插入和删除的一端,表尾(an端)称为栈顶Top
栈底:不允许插入和删除的一端,表头(a1端)称为栈底Base
逻辑结构:一对一关系,与普通线性表相同;存储结构:顺序栈(更常见)和链栈
数据的运算:插入元素——称为入栈进栈压栈 PUSH(x);删除元素——称为出栈弹栈 pop(y)
#define MaxSize 10
typedef struct{
ElemType data[MaxSize] //静态指针存放栈中元素
int top; //栈顶指针(一般指的是数组下表)
}SqStack; //Sq:sequence顺序
void testStack(){
SqStack S; //声明一个顺序栈(分配空间,其大小为MaxSize*sizeof(ElemType))
}
void InitStaack(SqStack &S){
S.top=-1; //初始化栈顶指针
}
//判断栈空
bool StackEmpty(SqStack S){
if(S.top==-1)
return true;
else
return false;
}
bool Push(SqStack &S,EleemType x){
if(S.top==MaxSize-1)
return false;
S.top=S.top+1; //此处两句等价于:S.data[++S.top]=x——先加再运算
S.data[S.top]=x; //先加1指向下一个,再使用top值入栈
return ture;
}
bool Pop(SqStack &S,ElemType &x){
if(S.top==-1)
return false;
x=S.data[S.top]; //等价于:S.data[S.top--]=x——先运算再减
S.top=S.top-1;
return ture;
}
bool GetTop(SqStack &S,EleemType &x){
if(S.top==-1)
return false;
x=S.data[S.top];
return ture;
}
S.top==0;
//进栈
S.data[S.top]=x; //等价于:S.data[S.top++]=x
S.top=S.top+1;
//出栈
S.top=S.top-1; //等价于:S.data[--S.top]=x
x=S.data[S.top];
top==MaxSize
#define MaxSize 10
typedef struct{
ElemType data[MaxSize];
int top0; //0号栈顶指针,栈底-1开始
int top1; //1号栈顶指针,栈顶MaxSize开始
}ShStack;
//共享栈的初始化
void InitStack(ShStack &S){
S.top0=-1;
S.top1=MaxSize;
}
top0+1==top1
typedef struct Linknde{
ElemType data;
struct Linknode *next;
}*LiStack;
队列(Queue)是只允许在一端进行插入,在另一端删除的线性表;又称为先进先出(FIFO)的线性表
队头:允许删除的一端;队尾:允许插入的一端
#define MaxSize 10
typedef struct{
ElemType data[MaxSize]; //用静态数组存放队列元素
int front; //队头指针:指向队头元素
int rear; //队尾指针:指向队尾元素的后一个位置(下一个应该插入的位置)
}SqQueue;
#define MaxSize 10
typedef struct{
ElemType data[MaxSize];
int front;
int rear;
}SqQueue;
void InitQueue(SqQueue &Q){
Q.front=Q.rear=0; //0既是队头位置,也是下一个插入的位置
return OK;
}
//判断队列是否为空
bool QueueEmpty(SqQueue Q){
if(Q.front==Q.rear)
return true;
else
return false;
}
队列元素个数——(rear-front+MaxSize)%MaxSize;
队空——队尾和队头指针指向同一位置front==rear;
队满——队尾指针的下一位置是队头(rear+1)%MAXQSIZE==front;
bool EnQueue(SqQueue &Q,ElemType x){
if((Q.rear+1)%MAXQSIZE==Q.front) //判断队满
return false;
Q.data[Q.rear]=x;
Q.rear=(Q.rear+1)%MaxQSize; //队尾指针后移,指针加1取模
return true;
}
bool DeQueue(SqQueue &Q,ElemType &x){
if(Q.rear==Q.front) //判断队空
return false;
x=Q.data[Q.front];
Q.front=(Q.f+1)%MaxQSize; //队头指针后移
return true;
}
//获得队头元素(只删除后移的代码)
bool GetQueue(SqQueue &Q,ElemType &x){
if(Q.rear==Q.front)
return false;
x=Q.data[Q.front];
return true;
}
方案一:牺牲一个存储空间:
队列元素个数——(rear-front+MaxSize)%MaxSize;
队空——front==rear;
队满——(rear+1)%MAXQSIZE==front;
方案二:不牺牲存储空间(设置size)
#define MaxSize 10
typedef struct{
ElemType data[MaxSize];
int front;
int rear;
int size; //设置一个计数size
}SqQueue;
初始化:rear=front=0; size=0;
插入成功:size++;
删除成功:size--;
队满条件:size==MaxSize;
队空条件size=0;
方案三:不牺牲存储空间(设置tag)
#define MaxSize 10
typedef struct{
ElemType data[MaxSize];
int front;
int rear;
int tag; //设置一个计数tag
}SqQueue;
初始化:rear=front=0; tag=0;
插入成功:tag=1(入队);
删除成功:tag=0(出队);
队满条件:front==rear&&tag==1;
队空条件tag=0
Q.rear=-1;
//入队操作(颠倒顺序)
Q.data[Q.rear]=x;
Q.rear=(Q.rear+1)%MaxQSize;
//出队操作
x=Q.data[Q.front];
Q.front=(Q.f+1)%MaxQSize;
//判空
(Q.rear+1)%MAXQSIZE==Q.front; //头指针在尾指针的后一个位置(类似循环)
//判满(方案一:牺牲一格存储单位)
(Q.rear+2)%MAXQSIZE==Q.front; //头指针在尾指针的后两个位置
//判满方案二:增加辅助变量
typedef struct LinkNode{ //定义链式队列的结点
ElemType data;
struct LinkNode *next;
}LinkNode;
typedef struct{ //定义链式队列
LinkNode *front,*rear;
}LinkQueue;
typedef struct LinkNode{
ElemType data;
struct LinkNode *next;
}LinkNode;
typedef struct{
LinkNode *front,*rear;
}LinkQueue;
//初始化队列(带头结点)
void InitQueue(LinkQueue &Q){
Q.front=Q.rear=(LinkNode*)malloc(sizeof(LinkNode)); //初始时,头尾指针都指向头结点
Q.front->next=NULL;
}
//判断队列是否为空
bool IsEmpty(LinkQueue){
if(Q.front==Q.rear)
return true;
else
return false;
}
void testLinkQueue(){
LinkQueue(); //声明一个队列
InitQueue(Q) //初始化队列
//...后续操作...
}
void InitQueue(LinkQueue &Q){
Q.front=NULL; //初始时,头尾指针都指向NULL
Q.rear=NULL;
}
bool IsEmpty(LinkQueue){
if(Q.front==NULL)
return true;
else
return false;
}
void EnQueue(LinkQueue &Q,ElemType x){
LinkNode *s=(LinkNode*)malloc(sizeof(LinkNode));
s->data=x;
s->next->NULL;
Q.rear->next=s;
Q.rear=s;
}
void EnQueue(LinkQueue &Q,ElemType x){
LinkNode *s=(LinkNode*)malloc(sizeof(LinkNode));
s->data=x;
s->next->NULL;
if(Q.front==NULL){ //在空队列中插入第一个元素
Q.front=s;
Q.rear=s;
}else{
Q.rear->next=s;
Q.rear=s;
}
}
bool DeQueue(LinkQueue &Q,ElemType &x){
if(Q.front==Q.rear)
return false;
LinkNode *p=Q.front->next; //出队为头指针的下一个结点
x=p->data;
Q.front->next=p->next;
if(Q.rear==p) //此次是最后一个结点出队
Q.rear=Q.front; //头尾指针均指向头结点,为空状态
free(p);
return true;
}
bool DeQueue(LinkQueue &Q,ElemType &x){
if(Q.front==NULL)
return false;
LinkNode *p=Q.front; //出队为头指针结点
x=p->data;
Q.front=p->next;
if(Q.rear==p){ //此次是最后一个结点出队
Q.rear=NULL;
Q.front=NULL;
}
free(p);
return true;
}
双端队列:只允许从两端插入、两端删除的线性表
输入受限的双端队列:只允许从一端插入、两端删除的线性表;输出受限的双端队列:只允许从两端插入、一端删除的线性表
考点:判断输出序列的000合法性——考特兰数:C n(上) 2 n(下) /(n+1)
IDE :可视化编程环境
栈的特性——最后出现的左括号最先被匹配
遇到左括号:入栈;遇到右括号:出栈“消耗”一个左括号
#define MaxSize 10
typedef struct{
char data[MaxSize];
int top; //栈顶指针
}
bool bracketCheck(char str[],int length){
SqStack S; //定义一个栈
InitStack(S); //初始化一个栈
for(i=0;i<length;i++){ //下标从0开始
if(str[i]=='('||str[i]=='{'||str[i]=='['){
Push(S,str[i]); //入栈左括号
}else{
if(EmptyStack(S))
return false;
char topElem;
Pop(S,topElem); //出栈左括号;依次判断不匹配的可能
if(str[i]==')'&&topElem!='(')
return false;
if(str[i]=='}'&&topElem!='{')
return false;
if(str[i]==']'&&topElem!='[')
return false;
}
}
return StackEmpty(S); //检索完后,栈空才说明成功
}
算数表达式的三个组成部分:操作数、运算符、界限
例子:a+b-c*d(先算a加b,再算c乘d,再算减法)
中缀表达式:a+b-c*d;后缀表达式:ab+cd *-;前缀表达式:- +ab * cd
后缀表达式(逆波兰表达式)——运算符在两个操作数后面
前缀表达式(波兰表达式)——运算符在两个操作数前面
手算:运算符和两个操作数组合成为一个新的操作数,再参与下次运算
“右优先”原则:只要右边的运算符能先计算,就优先算右边的;让前缀表达式的运算符从右到左依次生效
手算:两个操作数和运算符组合成为一个新的操作数,再参与下次运算
“左优先”原则:只要左边的运算符能先计算,就优先算左边的,可保证运算顺序一致
机算:
初始化一个栈,用于保存还不能确定运算顺序的运算符
从左到右依次扫描各个元素,直到末尾(多种方法)
注意:后缀表达式中没有左右括号
中缀表达式计算:中缀转后缀+后缀表达式求值
初始化两个栈——操作数栈和运算符栈
函数调用的特点:最后被调用的函数最先执行(LIFO)
函数调用时,需要用一个栈(函数调用栈)存储——调用返回地址、实参、局部变量
适合用“递归”算法解决的特点:可以把问题转为属性相同,但规模较小的问题
递归调用时,函数调用栈可称为“递归工作栈”;每进入一层递归,就将递归调用所需信息压入栈顶;每退出一层递归,就从栈顶弹出相应信息;
(进入:return到下一个要计算的函数;退出:return到上一个函数的结果)
int factorial(int i){
if(n==0||n==1)
return 1; //当n减到1时,返回1到factorial(1+1)函数中算(1+1)*fac(1)
else
return n*factorial(n-1); //先递减进入函数,再递加计算每一层函数
}
int main(){
//...其他代码
int x=factorial(10);
}
缺点:太多层递归会导致栈溢出,空间复杂度太高;可能包含很多重复运算
可以自定义栈将递归算法改造成非递归算法
队列应用——树的层次遍历、图的广度优先遍历
队列在操作系统中的应用——CPU:先来先服务(FCFS)
数组的存储结构:一维数组、二维数组
特殊矩阵:对称矩阵、三角矩阵、三对角矩阵、稀疏矩阵
注意:除非特殊说明,否则数组下标默认从0开始