**栈(Stack)**是只允许在一端进行插入或删除操作的线性表
特点:后进先出
**InitStack(&S):初始化栈。**构造一个空栈 S,分配内存空间。
**DestroyStack(&S):销毁栈。**销毁并释放栈 S 所占用的内存空间。
Push(&S,x):进栈,若栈S未满,则将x加入使之成为新栈顶。
Pop(&S,&x):出栈,若栈S非空,则弹出栈顶元素,并用x返回。
**GetTop(S, &x):读栈顶元素。**若栈 S 非空,则用 x 返回栈顶元素
其他常用操作:
StackEmpty(S):判断一个栈 S 是否为空。若S为空,则返回true,否则返回false。
#define MaxSize 10//定义栈中元素的最大个数
typedef struct{
ELemType data[MaxSize];//静态数组存放栈中元素
int top;//栈顶指针
} SqStack;
//初始化栈
void InitStack(SqStack &S){
S.top=-1;//初始化栈顶指针
}
//判断栈空
bool StackEmpty(SqStack S){
if(S.top==-1) //栈空
return true;
else //不空
return false;
}
//新元素入栈
bool Push(SqStack &S,ElemType x){
if(S.top==MaxSize-1) //栈满,报错
return false;
S.top = S.top + 1;//指针先加1
S.data[S.top]=x;//新元素入栈
//以上两步等价于S.data[++S.top]=x;
return true;
}
S.top = S.top + 1;//指针先加1 S.data[S.top]=x;//新元素入栈
等价于S.data[++S.top]=x;
//出栈操作
bool Pop(SqStack &S,ElemType &x){
if(S.top==-1)//栈空,报错
return false;
x=S.data[S.top];//栈顶元素先出栈
S.top= S.top-1;//指针再减1.
//以上两步等价于x=S.data[S.top--];
return true;
}
x=S.data[S.top];//栈顶元素先出栈 S.top= S.top-1;//指针再减1.
等价于x=S.data[S.top--];
//读栈顶元素
bool GetTop(SqStack s,ELemType &x){
if(s.top==-1) //栈空,报错
return false;
x=S.data[S.top];//x记录栈顶元素
return true;
}
栈满的条件:top == MaxSize
利用栈底位置相对不变的特性,可让两个顺序栈共享一个一维数组空间,将两个栈的栈底分
别设置在共享空间的两端,两个栈顶向共享空间的中间延伸,如图3.3所示。
两个栈的栈顶指针都指向栈顶元素,top0=-1 时0号栈为空,top1=MaxSize时1号栈为空;仅当两个栈项指针相邻(top1-top0=1)时,判断为栈满。当0号栈进栈时top0先加1再赋值,1 号栈进栈时top1先减1再赋值;出栈时则刚好相反。
共享栈是为了更有效地利用存储空间,两个栈的空间相互调节,只有在整个存储空间被占满时才发生上溢。其存取数据的时间复杂度均为O(1),所以对存取效率没有什么影响。
#define MaxSize 10//定义栈中元素的最大个数
typedef struct{
ELemType data[MaxSize];//静态数组存放栈中元囊
int top0;//0号栈找顶指针
int top1;//1号枝线顶指针
} ShStack;
//初始化栈
void InitStack(ShStack &S){
S.top0=-1;//初始化栈顶指针
S.top1=MaxSize;
}
栈满条件:top0 + 1 == top1
链栈结点的数据结构:
typedef struct Linknode{
ElemType data; //数据域
struct Linknode *next; //指针域
}LNode,*LiStack;//栈类型定义
单链表结点的数据结构:
typedef struct LNode{ //定义单链表结点类型
ElemType data; //每个节点存放一个数据元素
struct LNode *next; //指针指向下一个节点
}LNode,*LinkList;
差不多。
//自改,不知对错
//带头结点,栈的创建与初始化
LiStack LStackBuild(LiStack &L) {//创建
LNode *s;//声明新结点,此时s没有分配空间
L=(LiStack)malloc(sizeof(LNode));//创建头结点
L->next=NULL;//初始为空链
Push(&L);
return L;
}
//新元素入栈
bool Push(LiStack &L){ //链栈的进栈操作
if(L==NULL)
return false;
int x;//设ElemType为整型
scanf("%d",&x);//输入结点的值
while(x!=9999){//输入9999表示结束建表
s=(LiStack)malloc(sizeof(LNode)); //创建新结点
s->data=x;
s->next=L->next;
L->next=s;//将新结点插入表中,L为头指针
scanf("%d",&x);
}
return true;
}
大差不差,用单链表的写法也行。。
单链表(带头结点)的头插法
//带头结点
LinkList List_HeadInsert(LinkList &L) { //逆向建立单链表
LNode *s;//声明新结点,此时s没有分配空间
int x;//设ElemType为整型
L=(LinkList)malloc(sizeof(LNode)); //创建头结点
L->next=NULL;//初始为空链表
scanf("%d",&x);//输入结点的值
while(x!=9999) {//输入9999表示结束建表
s=(LNode*)malloc(sizeof(LNode)); //创建新结点
s->data=x;
s->next=L->next;
L->next=s;//将新结点插入表中,L为头指针
scanf("%d", &x);
}
return L;
}
//自改,不知对错
//不带头结点时,创建与进栈相同
LiStack LStackBuild(LiStack &L) {
LNode *s;//声明新结点,此时s没有分配空间
int x;//设ElemType为整型
scanf("%d",&x);
while(x!=9999) {//输入9999表示结束建表
s=(LNode*)malloc(sizeof(LNode)); //创建新结点
s->data=x;//数据域赋值
s->next=L;//新结点的next指向NULL
L=s;//将新结点插入表中,L为头指针
scanf("%d",&x);
}
return L;
}
单链表(不带头结点)的头插法
//不带头结点(自己改的,可能有错)
LinkList List_HeadInsert(LinkList &L) { //逆向建立单链表
LNode *s;//声明新结点,此时s没有分配空间
int x;//设ElemType为整型
scanf("%d",&x);
while(x!=9999) {//输入9999表示结束建表
s=(LNode*)malloc(sizeof(LNode)); //创建新结点
s->data=x;//数据域赋值
s->next=L;//新结点的next指向NULL
L=s;//将新结点插入表中,L为头指针
scanf("%d",&x);
}
return L;
}
不能说一摸一样,只能说完全一致。。
typedef struct Linknode{
ElemType data; //数据域
struct Linknode *next; //指针域
}LNode,*LiStack;//栈类型定义
//带头结点
bool Pop(LiStack &L,ElemType &x){
if(L=NULL||L->next=NULL)//栈未创建或为空栈
return false;
LNode *q=L->next;//令q指向被删除结点
x = q->data;//用x返回元素的值
L->next=q->next;//将*q结点从链中“断开”
free(q);//释放结点的存储空间
return true;//删除成功
}
typedef struct Linknode{
ElemType data; //数据域
struct Linknode *next; //指针域
}LNode,*LiStack;//栈类型定义
//不带头结点
bool Pop(LiStack &L,ElemType &x){
if(L=NULL)//栈未创建或为空栈
return false;
LNode *q=L;//令q指向被删除结点
x = q->data;//用x返回元素的值
L=q->next;//将*q结点从链中“断开”
free(q);//释放结点的存储空间
return true;//删除成功
}
//带头结点
bool GetTop(LiStack &L,ElemType &x){
if(L=NULL||L->next=NULL)//栈未创建或为空栈
return false;
x = L->next->data;//用x返回元素的值
return true;//删除成功
}
//不带头结点
bool GetTop(LiStack &L,ElemType &x){
if(L=NULL)//栈未创建或为空栈
return false;
x = L->data;//用x返回元素的值
return true;//删除成功
}
//带头结点
int Length(LinkList L){
int len = 0; //统计表长
LNode *p = L;
while (p->next != NULL){
p = p->next;
len++;
}
return len;
}
//不带头结点
int Length(LinkList L){
int len = 0; //统计表长
LNode *p = L;
while (p!= NULL){
p = p->next;
len++;
}
return len;
}
队列是只允许在一端进行插入,在另一端删除的线性表。
先进先出。
InitQueue(&Q):初始化队列,构造一个空队列Q。
DestroyQueue(&Q):销毁队列。销毁并释放队列Q所占用的内存空间。
EnQueue(&Q,x):入队,若队列Q未满,将x加入,使之成为新的队尾。
DeQueue(&Q,&x):出队,若队列Q非空,删除队头元素,并用x返回。
GetHead(Q,&x):读队头元素,若队列Q非空,则将队头元素赋值给x。
其他常用操作:
QueueEmpty(Q):判队列空,若队列Q为空返回true,否则返回false。
#define MaxSize 10//定义队列中元素的最大个数
typedef struct{
ElemType data [MaxSize];//用静态数组存放队列元素
int front,rear;//队头指和队尾指针
} SqQueue;
队尾指针指向的是队尾元素的后一个位置,做题的时候可能会出队尾指针指向队尾元素的题。
//初始化队列
void InitQueue(SqQueue &Q){
Q.rear=Q.front=0;//初始时队头、队尾指针指向0
}
//判断队列是否为空
bool QueueEmpty(SqQueue Q){
if(Q.rear==Q.front) //队空条件
return true;
else
return false;
}
//判断队列是否已满
bool QueueEmpty(SqQueue Q){
if((Q.rear+1)%MaxSize==Q.front) //队满条件
return true;
else
return false;
}
//入队
bool EnQueue(SqQueue &Q, ElemType x){
if((Q.rear+1)%MaxSize==Q.front)
return false;//队满则报错
Q.data[Q.rear]=x;//将x插入队尾
Q.rear=(Q.rear+1)% MaxSize; //队尾指针+1取模
return true;
}
模运算(取余):两个整数 a,b,a%b 表示 a除以b所得的余数。(也是a MOD b)
用模运算将存储空间在逻辑上变成了==“环状”==;
(Q.rear+1)%MaxSize==Q.front
判断队满
//出队(删除一个队头元素,并用x返回)
bool DeQueue (SqQueue &Q, ElemType &x){
if(Q.rear==Q.front)//判断队空
return false; //队空则报错
x=Q.data[Q.front];
Q.front=(Q.front+1)%MaxSize;//队头指针后移
return true;
}
队列元素个数:( rear + MaxSize - front )%MaxSize
#define MaxSize 10//定义队列中元素的最大个数
typedef struct{
ElemType data [MaxSize];//用静态数组存放队列元素
int front,rear;//队头指和队尾指针
int size;//队列当前长度
} SqQueue;
//插入成功,size++;删除成功,size--;
//初始化
void InitQueue(SqQueue &Q){
Q.rear=Q.front=0;//初始时队头、队尾指针指向0
Q.size=0;
}
//判断队列是否为空
bool QueueEmpty(SqQueue Q){
if(Q.size==0) //队空条件
return true;
else
return false;
}
//判断队列是否已满
bool QueueEmpty(SqQueue Q){
if(Q.size==MaxSize) //队满条件
return true;
else
return false;
}
//入队
bool EnQueue(SqQueue &Q, ElemType x){
if(Q.size==MaxSize)
return false;//队满则报错
Q.data[Q.rear]=x;//将x插入队尾
Q.rear=(Q.rear+1)% MaxSize; //队尾指针+1取模
Q.size++;
return true;
//出队(删除一个队头元素,并用x返回)
bool DeQueue (SqQueue &Q, ElemType &x){
if(Q.size==0)//判断队空
return false; //队空则报错
x=Q.data[Q.front];
Q.front=(Q.front+1)%MaxSize;//队头指针后移
Q.size--;
return true;
}
#define MaxSize 10//定义队列中元素的最大个数
typedef struct{
ElemType data [MaxSize];//用静态数组存放队列元素
int front,rear;//队头指和队尾指针
int tag;//最近进行的是删除/插入
} SqQueue;
//每次删除操作成功时,都令tag=0;
//每次插入操作成功时,都令tag=1;
//初始化
void InitQueue(SqQueue &Q){
Q.rear=Q.front=0;//初始时队头、队尾指针指向0
Q.tag=0;
}
//判断队列是否为空
bool QueueEmpty(SqQueue Q){
if(Q.front==Q.rear&&Q.tag==0) //队空条件
return true;
else
return false;
}
//判断队列是否已满
bool QueueEmpty(SqQueue Q){
if(Q.front==Q.rear&&Q.tag==1) //队满条件
return true;
else
return false;
}
//入队
bool EnQueue(SqQueue &Q, ElemType x){
if(Q.front==Q.rear&&Q.tag==1)
return false;//队满则报错
Q.data[Q.rear]=x;//将x插入队尾
Q.rear=(Q.rear+1)% MaxSize; //队尾指针+1取模
Q.tag=1;
return true;
//出队(删除一个队头元素,并用x返回)
bool DeQueue (SqQueue &Q, ElemType &x){
if(Q.size==0)//判断队空
return false; //队空则报错
x=Q.data[Q.front];
Q.front=(Q.front+1)%MaxSize;//队头指针后移
Q.tag=0;
return true;
}
typedef struct LinkNode{ // 链式队列结点
ElemType data;
struct L inkNode *next;
}LinkNode;
typedef struct{//链式队列
LinkNode *front,*rear; //队列的队头和队尾指针
}LinkQueue;
//初始化队列(带头结点)
void InitQueue(LinkQueue &Q){
//初始时front. rear都指向头结点
Q.front=Q.rear=(LinkNode*)malloc(sizeof(LinkNode));
Q.front->next=NULL;
}
//判断队列是否为空
bool IsEmpty(LinkQueue Q){
if(Q.front= =Q.rear)
return true;
else
return false;
}
//初始化队列(不带头结点)
void InitQueue(LinkQueue &Q) {
//初始时front、rear都指向NULL
Q.front=NULL;
Q.rear=NULL;
}
//判断队列是否为空(不带头结点)
bool IsEmpty(LinkQueue Q){
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;//新结点插入到rear之后
Q.rear=s;//修改表尾指针
}
//新元素入队(不带头结点)
void EnQueue(L inkQueue &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;//新结点插入到rear结点之后
Q.rear=s;//修改rear指针
}
}
//队头元素出队(带头结点)
bool DeQueue(LinkQueue &Q, ElemType &x){
if(Q.front==Q.rear)
return false;//空队
LinkNode *p=Q.front->next;
x=p->data;//用变量x返回队头元素
Q.front->next=p->next;//修改头结点next指针
if(Q.rear==p)//此次是最后一个结点出队
Q.rear=Q.front;//修改rear指针
free(p);//释放结点空间.
return true;
}
//队头元素出队(不带头结点)
bool DeQueue(LinkQueue &Q, ElemType &x){
if(Q.front==NULL)
return false;//空队
LinkNode *p=Q.front;//p指向此次出队的结点
x=p->data;//用变量x返回队头元素
Q.front=p->next;//修改front指针
if(Q.rear==p){//此次是最一-个结点出队
Q.front=NULL;//front 指向NULL
Q.rear=NULL;//rear 指向NULL
free(p);//释放结点空间.
return true;
}
#define MaxSize 10//定义栈中元素的最大个数
typedef struct{
char data[MaxSize];//静态数组存放栈中元素
int top;//栈顶指针
} SqStack;
//担心存满可用链栈
//初始化栈
void InitStack(SqStack &S)
//判断栈是否为空
bool StackEmpty(SqStack S)
//新元素入栈
bool Push(SqStack &S, char x)
//栈顶元素出栈,用x返回
bool Pop(SqStack &S, char &x)
//考试中可直接使用基本操作,建议简要说明接口
//算法如下:
bool bracketCheck(char str[], int length) {
SqStack S;
InitStack(S); //初始化一个栈
for (int i=0;i<length;i++){
if (str[i]=='('|| str[i]=='[' || str[i]=='{'){
Push(S,str[i]); //扫描到左括号,入栈
} else {
if (StackEmpty(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); //检索完全部括号后,栈空说明匹配成功
}
用栈实现括号匹配:
依次扫描所有字符,遇到左括号入栈,遇到右括号则弹出栈顶元素检查是否匹配。
遇到左括号就入栈;遇到右括号,就 “消耗”一个左括号
匹配失败情况:
①左括号单身②右括号单身③左右括号不匹配
中缀转后缀的手算方法:
① 确定中缀表达式中各个运算符的运算顺序
② 选择下一个运算符,按照==「左操作数 右操作数 运算符」==的方式组合成一个新的操作数
③ 如果还有运算符没被处理,就继续 ②
运算顺序不唯一,因此对应的后缀表达式也不唯一:
注:客观来看两种都正确,只是“机算”的结果是前者
“左优先”原则:只要左边的运算符能先计算,就优先算左边的。
后缀表达式的手算方法:
从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应运算,合体为一个操作数。(注意:两个操作数的左右顺序)
用栈实现后缀表达式的计算:
①从左往右扫描下一个元素,直到处理完所有元素
②若扫描到操作数则压入栈,并回到①;否则执行③
③若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①
中缀转前缀的手算方法:
① 确定中缀表达式中各个运算符的运算顺序
② 选择下一个运算符,按照==「运算符 左操作数 右操作数」==的方式组合成一个新的操作数
③ 如果还有运算符没被处理,就继续 ②
“右优先”原则:只要右边的运算符能先计算,就优先算右边的
用栈实现前缀表达式的计算:
①从右往左扫描下一个元素,直到处理完所有元素
②若扫描到操作数则压入栈,并回到①;否则执行③
③若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①
中缀转后缀的手算方法:
① 确定中缀表达式中各个运算符的运算顺序
② 选择下一个运算符,按照「左操作数 右操作数 运算符」的方式组合成一个新的操作数
③ 如果还有运算符没被处理,就继续 ②
“左优先”原则:只要左边的运算符能先计算,就优先算左边的
初始化一个栈,用于保存暂时还不能确定运算顺序的运算符。
从左到右处理各个元素,直到末尾。可能遇到三种情况:
① 遇到操作数。直接加入后缀表达式。
② 遇到界限符。遇到“(”直接入栈;遇到“)”则依次弹出栈内运算符并加入后缀表达式,直到弹出“(”为止。注意:“(”不加入后缀表达式。
③ 遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符(* / 优先级高于 + -),并加入后缀表达式,若碰到“(” 或栈空则停止。之后再把当前运算符入栈。
中缀转后缀+后缀表达式求值 两个算法的结合
用栈实现中缀表达式的计算:
(1)初始化两个栈,操作数栈和运算符栈
(2)若扫描到操作数,压入操作数栈
(3)若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)
递归的精髓在于能否将原始问题转换为属性相同但规模较小的问题。
在递归调用的过程中,系统为每一层的返回点、局部变量、传入实参等开辟了递归工作栈来进行数据存储,递归次数过多容易造成栈溢出等。而其效率不高的原因是递归调用过程中包含很多重复的计算。
递归的效率低下,但优点是代码简单,容易理解。
将递归算法转换为非递归算法,通常需要借助栈来实现这种转换。
递归调用时,函数调用栈可称为“递归工作栈”
每进入一层递归,就将递归调用所需信息压入栈顶
每退出一层递归,就从栈顶弹出相应信息
多个进程争抢着使用有限的系统资源时,FCFS(First Come First Service)是一种常用策略。