一.栈的基本概念
(1)栈是限定仅在表尾进行插入和删除操作的线性表。所谓的表尾是指栈顶,而不是栈底。
(2)栈是后进先出的线性表。
(3)把允许插入和删除的一端称为栈顶,另一端称为栈底。
(4)不含任何元素的栈称为空栈。 判定条件为top等于-1。
(5)栈是一个线性表,栈元素具有线性关系。
很明显不是的。
二.栈的表示与实现(顺序栈)
1.栈的顺序结构定义
说一下我写的代码和课本上是一样的,和图片里的有所不同,本质都是一样的,ppt的代码我写在了后头
typedef struct
{
SElemType *base;//栈底指针
SElemType *top;//栈顶指针
int stacksize;//当前最大容量
} SqStack;
2.栈的初始化(构建空栈)
Status InitStack(SqStack &S)
{
S.base=(SElemType *)malloc(sizeof(SElemType)*STACK_INIT_SIZE);//申请空间
if(!S.base)
exit(OVERFLOW);
S.top=S.base;//头尾指针相等,代表空栈
S.stacksize=STACK_INIT_SIZE;//当前容量为初始最大容量
return OK;
}
3.栈的插入操作
Status Push(SqStack &S,SElemType e)
{
if(S.top-S.base>=S.stacksize)//空间不够的话要考虑扩容
{
S.base=(SElemType*)malloc(sizeof(SElemType)*(S.stacksize+STACKINCREMENT));
if(!S.base)
exit(OVERFLOW);
S.top=S.base+S.stacksize;
S.stacksize+=STACKINCREMENT;
}
//把e插入栈顶
*S.top++=e;//相当于S.top = e;top++;
return OK;
}
4.栈的删除操作
注意插入删除操作均不涉及循环操作,所以时间复杂度均为O(1)
Status Pop(SqStack &S,SElemType &e)
{
if(S.top==S.base)
return ERROR;
e = *S.top--;
return OK;
}
5.返回栈顶元素
Status GetTop(SqStack &S,SElemType &e)
{
if(S.base==S.top)
return ERROR;
e=*(S.top-1);//当栈不空时,栈顶指针总是指在栈顶元素的下一个位置
return OK;
}
6.栈的判空
bool JudgeEmpty(SqStack &S)
{
if(S.top == S.base)
return true;
else
return false;
}
此外给出ppt版的代码实现:
typedef int Position;
struct SNode {
ElementType *Data; /* 存储元素的数组 */
Position Top; /* 栈顶指针 */
int MaxSize; /* 堆栈最大容量 */
};
typedef struct SNode *Stack;
Stack CreateStack( int MaxSize )
{
Stack S = (Stack)malloc(sizeof(struct SNode));
S->Data = (ElementType *)malloc(MaxSize * sizeof(ElementType));
S->Top = -1;
S->MaxSize = MaxSize;
return S;
}
bool IsFull( Stack S )
{
return (S->Top == S->MaxSize-1);
}
bool Push( Stack S, ElementType X )
{
if ( IsFull(S) ) {
printf("堆栈满");
return false;
}
else {
S->Data[++(S->Top)] = X;
return true;
}
}
bool IsEmpty( Stack S )
{
return (S->Top == -1);
}
ElementType Pop( Stack S )
{
if ( IsEmpty(S) ) {
printf("堆栈空");
return ERROR; /* ERROR是ElementType的特殊值,标志错误 */
}
else
return ( S->Data[(S->Top)--] );
}
关于两栈共享空间的问题
黄字表示两个堆栈分别为空的位置
注意,共享栈只针对两个相同类型的栈!!!,如果元素不同,这样做会使问题极其复杂 ,一定要注意这个前提
三.链栈的实现(简单介绍)
由于链栈的操作方法和链表相似,在这里就不详细介绍了,但会给出代码
有几点注意
a:链栈的插入删除操作应该都在最左边即头结点进行
即栈顶指针top只能指向表头,以左边为链表头。
(因为这样方便插入删除,而若top指向表尾,则插入可以,但是删除不行,因为这样就找不到他的直接前驱结点了)
b:链栈中既然有了指向栈顶的top指针,就没有头结点这个概念了哦
c:链栈的插入删除时间复杂度都是O(1)(同顺序栈)
注意这里的插入删除操作
a:插入采用的是头插法,删除就是正常删
b:这里的插入操作不用判满!!!(必须理解!!!)(这是考点哦!!!)
因为这个堆栈是链表实现的,链表是动态分配存储空间的,不存在表满的问题(数组实现的push操作必须判满)
当然删除操作还是要判空的(因为无论是数组还是链表,都有空的情况)
注意push操作是不需要判满的(因为是动态分配的地址空间)
而pop操作是需要判空的
typedef struct SNode *PtrToSNode;
struct SNode {
ElementType Data;
PtrToSNode Next;
};
typedef PtrToSNode Stack;
Stack CreateStack( )
{ /* 构建一个堆栈的头结点,返回该结点指针 */
Stack S;
S = (Stack)malloc(sizeof(struct SNode));
S->Next = NULL;
return S;
}
bool IsEmpty ( Stack S )
{ /* 判断堆栈S是否为空,若是返回true;否则返回false */
return ( S->Next == NULL );
}
bool Push( Stack S, ElementType X )
{ /* 将元素X压入堆栈S */
PtrToSNode TmpCell;
TmpCell = (PtrToSNode)malloc(sizeof(struct SNode));
TmpCell->Data = X;
TmpCell->Next = S->Next;
S->Next = TmpCell;
return true;
}
ElementType Pop( Stack S )
{ /* 删除并返回堆栈S的栈顶元素 */
PtrToSNode FirstCell;
ElementType TopElem;
if( IsEmpty(S) ) {
printf("堆栈空");
return ERROR;
}
else {
FirstCell = S->Next;
TopElem = FirstCell->Data;
S->Next = FirstCell->Next;
free(FirstCell);
return TopElem;
}
}
补充一个问题:
栈的作用是什么:
四.栈的应用举例
1.数值转换
所以这个转换过程符合栈的原理
这里有一道例题,我写的应该算很详细了,看懂的话这块应该就ok了
蓝桥杯 基础练习 十进制转十六进制 (练习栈方法)_markconca的博客的博客-CSDN博客_十进制转十六进制栈
关于其他进制转换的原理,想了解的话大家也可以看这个
https://jingyan.baidu.com/article/495ba84109665338b30ede98.html
2.括号匹配检验
基本原理:
思路:1.在算法中设置一个栈
2.每读入一个括号,若是右括号,则要看是否匹配。当栈不空时,如果匹配,则栈顶元素出栈,如果不匹配(即不合法),说明缺少右括号,输出左符号-?(因为如果是右括号,说明之前已经读入左括号,但是两个括号不匹配)。当栈为空时,说明缺少左括号,输出?-右括号。若是左括号,则压入栈中。
3.对于/*符号,可用“<”代替,读到/时记得先i++;
4.算法的开始于结束,栈都应该为空
举两个例题,会了应该就差不多了
例题一:习题3.8 符号配对 (20 分)
pta原题链接:PTA | 程序设计类实验辅助教学平台
该题答案与解析第三章栈作业题2-栈及其应用-计算机17级 7-2 符号配对 (20 分)_markconca的博客的博客-CSDN博客
例题二:练习题 10:括号画家
该题解析与答案:第三章 栈与队列 练习题 10:括号画家_markconca的博客的博客-CSDN博客
3.行编辑程序
这里还没仔细研究,思路日后补充
4.迷宫求解(回溯算法)
这里还没仔细研究,思路日后补充
具体的例题日后补充
5.表达式转换(非常重要,也比较难)
注意左括号一旦入栈优先级要降为最低 !!!
思路:
从头到尾读取中缀表达式的每个对象,对不同对象按不同的情况处理。
1.运算数:直接输出
2.左括号:压入栈中
3.右括号:将栈顶的运算符弹出并输出,直到遇到左括号(出栈,不输出)
4.运算符:
若优先级大于栈顶运算符时,则把它压栈;
若优先级小于等于栈顶运算符时,将栈顶运算符弹出并输出;再比较新的栈顶运算符,直到该运算符大于栈顶运算符优先级为止,然后将该运算符压栈
5.若各对象处理完毕,则把堆栈中存留的运算符一并输出
这里会这道例题就可以了,不太好做
pta原题链接:习题3.11 表达式转换 (25 分)PTA | 程序设计类实验辅助教学平台
该题超详细答案与解析:(一道老坑爹的题)第三章栈作业题2-栈及其应用-计算机17级 7-1 表达式转换 (25 分)_markconca的博客的博客-CSDN博客
6.其他应用:
a:函数调用及递归实现
b:图的深度优先搜索
补充几道例题哦(详解在下面)
二.链队列的表示与实现
一定注意,不管是顺序实现还是链表实现,队列的插入都是在队尾,删除都是在队头
注意下面这个问题与链栈类似哦
因为front是做出队操作,rear是做入队操作 ,所以只能是开头是front,结尾是rear(),你要是倒过来就尴尬了,因为表尾不能做删除操作!!!考点!!!(与链栈完全类似哦)
1.链队列的结构定义
typedef struct QNode
{
QElemType data;
struct QNode *next;
} QNode,*QueuePtr;
typedef struct
{
QueuePtr front;
QueuePtr rear;
} LinkQueue;
2.链队列的初始化(构建空队列)
Status InitQueue(LinkQueue &Q)
{
Q.front = Q.rear = (QueuePtr)malloc(sizeof(QNode));
if(!Q.front)
exit(OVERFLOW);
Q.front->next = NULL;
return OK;
}
3.链队列的入队操作
Status EnQueue(LinkQueue &Q,QElemType e)
{
QueuePtr p;
p = (QueuePtr)malloc(sizeof(QNode));
if(!p)
exit(OVERFLOW);
p->data = e;
p->next = NULL;
Q.rear->next = p;//尾部插入
Q.rear = p;
return 1;
}
4.链队列的出队操作
Status DeQueue(LinkQueue &Q,QElemType &e)
{
if(Q.front == Q.rear)
return 0;
QueuePtr p = Q.front->next;//指向队头
e = p->data;
Q.front->next = p->next;
//如果队头是队尾,删除后将rear指向头结点
if(Q.rear == p)
Q.rear = Q.front;
free(p);
return 1;
}
5.链队列的判空操作
bool JudgeEmpty(LinkQueue &Q)
{
if(Q.front == Q.rear)
return true;
else
return false;
}
6.链队列的销毁操作
Status DestroyQueue(LinkQueue &Q) {
while(Q.front) {
Q.rear = Q.front->next;
free(Q.front);
Q.front = Q.rear;
}
return true;
}
ppt版队列实现:
typedef struct Node *PtrToNode;
struct Node { /* 队列中的结点 */
ElementType Data;
PtrToNode Next;
};
typedef PtrToNode Position;
struct QNode {
Position Front, Rear; /* 队列的头、尾指针 */
int MaxSize; /* 队列最大容量 */
};
typedef struct QNode *Queue;
bool IsEmpty( Queue Q )
{
return ( Q->Front == NULL);
}
ElementType DeleteQ( Queue Q )
{
Position FrontCell;
ElementType FrontElem;
if ( IsEmpty(Q) ) {
printf("队列空");
return ERROR;
}
else {
FrontCell = Q->Front;
if ( Q->Front == Q->Rear ) /* 若队列只有一个元素 */
Q->Front = Q->Rear = NULL; /* 删除后队列置为空 */
else
Q->Front = Q->Front->Next;
FrontElem = FrontCell->Data;
free( FrontCell ); /* 释放被删除结点空间 */
return FrontElem;
}
}
三.循环队列的表示与实现(顺序队列)
初始front=rear=0
即对顺序存储来说,插入一个元素rear++,删除一个元素front++
此时判空和判满都是front==rear,显然出问题了
根本原因:front与rear的差距最多只有n(6)种情况,而队列元素个数总共有n+1(7)种情况,所以仅靠front和rear肯定是表示不了的(考点!!!)
注意:解决方案2用的多
1.循环队列的结构定义
typedef struct{
QElemType *base;
int front;
int rear;
}SqQueue;
2.循环队列的初始化
SqQueue InitQueue(SqQueue &Q)
{
Q.base=(QElemType *)malloc(MAXQSIZE*sizeof(QElemType));//为队列的数据存储空间分配内存
if(!Q.base)
exit(OVERFLOW);
Q.front = Q.rear = 0;
return OK;
}
3.循环队列的入队操作
Status EnQueue(SqQueue &Q,QElemType e)
{
if((Q.rear + 1)% MAXQSIZE == Q.front)//队列满的判断,就是rear+1和front能碰上
return 0;
Q.base[Q.rear] = e;//将e赋值给队尾
Q.rear = (Q.rear + 1) % MAXQSIZE;//rear指针向后移一位,若到最后则转到数组头部
return 1;
}
4.循环队列的出队操作
Status DeQueue(SqQueue &Q,QElemType &e)
{
if(Q.front == Q.base)//队列空的判断
return 0;
e = Q.base[Q.front];//将队头元素赋值给e
Q.front = (Q.front + 1) % MAXQSIZE;//front指针向后移一位,若到最后则转到数组头部
return 1;
}
5.循环队列返回队列元素个数(求队长)
Status QueueLength(SqQueue Q)//返回队列长度
{
return (Q.rear - Q.front + MAXQSIZE) % MAXQSIZE;
}
ppt版代码实现:
typedef int Position;
struct QNode {
ElementType *Data; /* 存储元素的数组 */
Position Front, Rear; /* 队列的头、尾指针 */
int MaxSize; /* 队列最大容量 */
};
typedef struct QNode *Queue;
Queue CreateQueue( int MaxSize )
{
Queue Q = (Queue)malloc(sizeof(struct QNode));
Q->Data = (ElementType *)malloc(MaxSize * sizeof(ElementType));
Q->Front = Q->Rear = 0;
Q->MaxSize = MaxSize;
return Q;
}
bool IsFull( Queue Q )
{
return ((Q->Rear+1)%Q->MaxSize == Q->Front);
}
bool AddQ( Queue Q, ElementType X )
{
if ( IsFull(Q) ) {
printf("队列满");
return false;
}
else {
Q->Rear = (Q->Rear+1)%Q->MaxSize;
Q->Data[Q->Rear] = X;
return true;
}
}
bool IsEmpty( Queue Q )
{
return (Q->Front == Q->Rear);
}
ElementType DeleteQ( Queue Q )
{
if ( IsEmpty(Q) ) {
printf("队列空");
return ERROR;
}
else {
Q->Front =(Q->Front+1)%Q->MaxSize;
return Q->Data[Q->Front];
}
}
补充几道例题:(带详解哦)