一、栈(stack)
1、基本概念
- 栈是一种特殊的线性表,又称为后进先出LIFO (Last In First Out)或先进后出FILO (First In Last Out)线性表
- 插入删除运算仅限定在线性表的某一端进行,不能再表中间和另一端进行
- 栈的插入操作称为进栈(入栈、Push),删除操作称为出栈(退栈、Pop)
- 栈顶:允许进行插入和删除的一端称为栈顶 栈底:另一端,固定不变
- 栈顶元素:处于栈顶位置的数据元素
- 空栈:不含任何数据元素的栈

2、基本运算


3、栈的顺序存储结构(顺序栈)
顺序栈类型声明:
静态分配:
- 顺序栈通常由一个一维数组data和一个记录栈顶元素位置的变量top组成
- 习惯上将栈底放在数组下标小的那端,栈顶元素由栈顶指针top所指向
动态分配:
- 用base表示栈底指针,栈底固定不变
- 用top指示当前栈顶位置,栈顶随着进栈和退栈操作而变化
- 用top==base作为栈空的标记
- 每次top指向栈顶数组中的下一个存储位置
#define STACK_INIT_SIZE 100 //存储空间初始分配量 #define STACK_INCREAMENT 10 //存储空间分配增量 typedef char ElemType; //静态分配 typedef struct{ ElemtType elem[STACK_INIT_SIZE]; //一维数组 int top; //记录栈顶元素位置的变量 }SqStack; //动态分配 typedef struct { ElemType *base; //栈底指针 Elemtype *top; //栈顶指针 int stacksize; //栈空间大小 } SqStack; //顺序栈

插入删除过程中只有top指针变化
对于顺序栈S,可通过动态申请得到其存储空间,它有4个要素:
栈空条件:S.top==S.base
栈满条件:S.top-S.base>=S.stacksize
元素e进栈操作:*(S.top)++=e //先进行*(S.top)运算再进行++运算
元素e出栈操作:e=*- - S.top //先进行- -操作再把- -s.top的值赋给e
基本算法设计
1、初始化栈
主要操作:通过动态申请所需的存储空间,并且初始化相关的参数(top,base,stacksize)。
void InitStack(SqStack &S)
{
if(!(S.base=(Elemtype*)malloc(STACK_INIT_SIZE *sizeof(ElemtType))))
exit(OVERFLOW);//存储分配失败
S.tpo=S.base;//空栈
S.stacksize=STACK_INIT_SIZE; //栈空间大小
}
2、销毁栈
主动释放空间, 栈顶、栈底两个指针置空,栈空间大小置零
void DestroyStack(SqStack &S)
{
free(S.base);
S.base=NULL;
S.top=NULL;
S.stacksize=0;
}
3、进栈
主要操作:判断是否存在“溢出”
void Push(Sqstack &S,Elemtype e)
{
if(S.top-S.base>=S.stack)
{ //栈满,追加存储空间
S.base=(ElemType*)realloc(S.base,(S.stacksize+STACK+INCREMENT)*sizeof(ElemType));
if(S.base)
exit(OVERFLOW); //存储分配失败
S.top=S.base+S.stacksize;
S.stacksize+=STACK_INCREMENT;
}
*(S.top)++=e; //先进行*(S.top)运算再进行++运算
}
4、出栈操作
主要操作:判断是否为空栈。若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR
Status Pop(SqStack &S,ElemType &e)
{
if(S.top==S.base)
return ERROR;
e=*--S.top; //先进行- -操作再把- -s.top的值赋给e
return OK;
}
5、取栈顶元素
主要操作:通过top指针,取出栈顶元素。需要注意的是,top指针没有发生改变(无 - - 操作)。若栈不空,则用e返回S的栈顶元素,并返回OK; 否则返回ERROR
Status GetTop(SqStack S,ElemType &e)
{
if(S.top > S.base)
e=*(S.top - 1);
return OK;
else
return ERROR;
}
6、判断栈空
主要操作:若栈为空(top==base)则返回值1,否则返回值0。
Status StackEmpty(SqStack S)
{
if(S.top == S.base)
return TRUE;
else
return FALSE;
}
4、栈的链式存储结构(链栈)
- 采用不带头节点的单链表作为链栈

首结点为栈顶,插入删除操作在链表头部进行
链栈的结点声明
typedef char ElemType; typedef struct node { ElemType data; struct node *next; }LinkStack;
- 链栈top初始时top=NULL,其4个要素如下
栈空条件:top==NULL
栈满条件:不考虑
元素x进栈操作:创建存放元素x的结点p,将其插入到栈顶位置上
元素x出栈操作:置x为栈顶结点的data域,并删除该节点
基本算法
1、初始化
主要操作:用top==NULL标识栈为空栈。
void InitStack(LinkStack *top)
{
top=NULL;
}
2、销毁
void DestroyStack(LinkList *&top)
{
LinkStack *pre=top,*p;
if(pre==NULL)return; //考虑空栈情况
p=pre->next;
while(p!=NUll)
{
free(pre); //释放pre结点
pre=p;
p=p->next; //pre、p同步后移
}
free(pre); //释放尾接结点
}
3、进栈
主要操作:先创建一个新结点,其data域值为x;然后将该结点插入到top结点之后作为栈顶结点。
int Push(LinkStack *&top,ElemType x)
{
LinkStack *p;
p=(LinkStack *)malloc(sizeof(LinkStack));
p->data=x;
p->next=top; //插入p结点作为栈顶结点
top=p;
return 1;
}
4、出栈
主要操作:将栈顶结点(即top->next所指结点) 的data域值赋给x,然后删除该栈顶结点。
int Pop(LinkStack *&top,ElemType &x)
{
LinkStack *p;
if(top==NULL) //栈空,下溢出返回0
return 0;
else //栈不空时出栈元素x并返回1
{
p=top; //p指向栈顶结点
x=p->data; //取栈顶元素x
top=p->next; //删除结点p
free(p); //释放p结点
return 1;
}
}
5、取栈顶元素
主要操作:将栈顶结点(即top->next所指结点)的data域值赋给x。
int GetTop(LinkStack *top,ElemType &x)
{
if(top==NULL) //栈空,下溢出时返回0
return 0;
else //栈不空,取栈顶元素x并返回1
{
x=top->data;
return 1;
}
}
6、判断栈空
主要操作:若栈为空(即top->next==NULL)则返回值1,否则返回值0。
int StackEmpty(LinkStack *top)
{
if(top==NULL)
return 1;
else
return 0;
}



5、栈的应用示例
- 在较复杂的数据处理过程中,通常需要保存多个临时产生的数据,如果先产生的数据后进行处理,那么需要用栈来保存这些数据
- 后面示例算法中均采用顺序栈,实际上采用链栈完全相同,一般用顺序栈
eg:

① A、D均合法,而B、C不合法。
② 用一个顺序栈st来判断操作序列是否合法。#include"Sqstack.cpp" //包含前面的顺序栈基本运算函数 int Judge(char str[],int n) { int i; ElemType x; SqStack st; //定义一个顺序栈st InitStack(st); //栈初始化 for(i=0;i<n;i++) //遍历str的所有字符 { if(str[i]=='I') //为'I'时进栈 Push(st,str[i]); else if(str[i]=='o') //为'O'时出栈 if(!Pop(st,x)) //若栈下溢出,则返回0 { DestroyStack(st); return 0; } } if(StackEmpty(st)) //栈为空时返回1;否则返回0 { DestroyStack(st);return 1; } else { DestroyStack(st); return 0; } }

int Palindrome(char str[], int n)
{
SqStack st; //定义一个顺序栈st
InitStack(st); //栈初始化
int i;
char ch;
for (i = 0; i < n; i++) //所有字符依次进栈
Push(st, str[i]);
i = 0; //从头开始遍历str
while (!StackEmpty(st)) //栈不空循环
{
Pop(st, ch); //出栈元素ch
if (ch != str[i++]) //两字符不相同时返回0
return 0;
}
return 1; //所有相应字符都相同时返回1
}

思路:
- 设置一个顺序栈st,定义一个整型flag变量(初始为1)。
- 用 i 扫描表达式exp,当i<n并且flag=1时循环:
- 当遇到左括号“(”、“[”、“{”时,将其进栈;
- 遇到“}”、“]”、“)”时,出栈字符ch,若出栈失败(下溢出)或者ch不匹配,则置flag=0退出循环;
- 否则直到exp扫描完毕为止。
- 若栈空并且flag为1则返回1,否则返回0。
}
#include "SqStack.cpp" //包含前面的顺序栈基本运算函数
int Match(char exp[], int n) //exp存放表达式
{
SqStack st; //定义一个顺序栈st
InitStack(st); //栈初始化
int flag = 1, i = 0;
char ch;
while (i < n && flag == 1) //遍历表达式exp
{
switch (exp[i])
{
case '(': case '[': case '{': //各种左括号进栈
Push(st, exp[i]); break;
case ')': //判断栈顶是否为'('
if (!Pop(st, ch) || ch != '(') //出栈操作失败或不匹配
flag = 0;
break;
case ']': //判断栈顶是否为'['
if (!Pop(st, ch) || ch != '[') //出栈操作失败或不匹配
flag = 0;
break;
case '}': //判断栈顶是否为'{'
if (!Pop(st, ch) || ch != '{') //出栈操作失败或不匹配
flag = 0;
break;
}
i++;
}
if (StackEmpty(st) && flag == 1) //栈空且符号匹配则返回1
return 1;
else
return 0; //否则返回0
}
二、队列
1、基本概念
- 队列(简称队)也是一种运算受限的线性表,插入限定在表的某一端进行,删除限定在表的另一端进行,是一种先进先出(First In First Out ,简称FIFO)的线性表
- 队列的插入操作称为进队,删除操作称为出队
- 允许插入的一段称为队尾,允许删除的一端称为队头(尾插头删)
- 新插入的元素只能添加到队尾,被删除的只能是排在队头的元素

2、基本运算
初始化队列:InitQueue(Q)。建立一个空队Q。销毁队:DestroyQueue(Q)。释放队列Q占用的内存空间。进队:EnQueue(Q, x)。将x插入到队列Q的队尾。出队:DeQueue(Q, x)。将队列Q的队头元素出队并赋给x。取队头元素:GetHead(Q, x)。取出队列Q的队头元素并赋给x,但该元素不出队。判断队空:QueueEmpty(Q)。判断队列Q是否为空。

3、循环队列——队列的顺序表示和实现
- 队列有两种存储结构,顺序存储和链式存储结构
- 队列的顺序存储结构简称为顺序队列,它由一个一维数组(用于存储队列中元素)及两个分别指示队头和队尾的变量组成,分别为“队头指针”和“队尾指针”
- 初始化建空队列时,令front=rear=0.在非空队列中,头指针始终指向队列头元素,而尾指针始终指向rear队尾元素的下一个位置
顺序队列的类型声明
#define MaxSize 20 typedef struct { ElemType data[MaxSize]; //开数组保存队中元素 int front,rear; //设置指示队头和队尾的变量 }SqQueue;

从中可以看到,图 (a)和(e)都是队空的情况,均满足front==rear的条件,所以可以将front==rear作为队空的条件
那么队满的条件如何设置呢?受顺序栈的启发,似乎很容易得到队满的条件为rear==MAXSIZE显然这里有问题,因为图 (d)和(e)都满足这个“队满”的条件,而实际上队列并没有满。
这种因为队满条件设置不合理而导致的“溢出”称为假溢出,也就是说这种“溢出”并不是真正的溢出, 尽管队满条件成立了,但队列中还有多个存放元素的空位置。为了能够充分地使用数组中的存储空间,可以把数组的前端和后端“假想地”连接起来,形成一个环形的 表,即把存储队列元素的表从逻辑上看成一个环。这个环形的表叫做循环队列或环形队列。
循环队列:

循环队列中 模 MAXSIZE是关键;留出一个空间不存放数据
判断队空:
front==rear
判断队满:
(rear+1)MOD MAXSIZE==front。
进队:
Q.data[Q.rear]=x;
Q.rear=(Q.rear+1)%MaxSize;
出队:
x=Q.data[Q.front];
Q.front=(Q.front+1)%MaxSize;
循环队列声明
#define MAXSIZE 100 typedef struct { ElemType *base; //初始化的动态分配存储空间(指向数组的指针) int front; //若队列不空,头指针指向队列头元素 int rear; //若队列不空,指向队列尾元素的下一个位置 }SqQueue;
基本算法
- 初始化
void InitQueue(SqQueue& Q)
{
Q.base = (QElemType*)malloc(MAXSIZE * sizeof(QElemType));//开辟一块内存空间
if (!Q.base) //存储分配失败
exit(OVERFLOW);
Q.front = Q.rear = 0; //指示队头和队尾的变量赋0
}
- 销毁队列
void DestroyQueue(SqQueue& Q)
{
if (Q.base)
free(Q.base);
Q.base = NULL; //指针置空
Q.front = Q.rear = 0; //指示队头和队尾的变量赋0
}
- 入队列
Status EnQueue(WqWueue& Q, QElemType e)
{
if ((Q.rear + 1) % MAXSIZE == Q.front)
return error;
Q.base[Q.rear] = e;
Q.rear = (Q.rear + 1) % MAXSIZE;
return OK;
}
- 出队列
Statuse DeQueue(SqQueue& Q, QElemType& e)
{
if (Q.front == Q.rear)
return ERROR;
e = Q.base[Q.front];
Q.front = (Q.front + 1) % MAXSIZE;
return OK;
}
- 取队列头元素
Status GetJead(SqQueue Q, QElemType& e)
{
if (Q.fornt == Q.rear)
return ERROR;
e = Q.base[Q.front];
return OK;
}
- 判断队空
Status QueueEmpty(SqQueue Q)
{
if (Q.front == Q.rear)
return true;
else
return FALSE;
}
应用举例
对于循环队列,写出求队列中元素个数的公式,并编写相应的算法。
int QueueLength(SqQueue Q)
{
return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}
如果用一个大小为MaxSize的环形数组表示队列,该队列只有一个队头指针front,不设队尾指针rear,而改置一个计数器count,用以记录队列中的元素个数。(1)队列中最多能容纳多少个元素?(2)设计实现队列基本运算的算法。
//设计队列类型
typedef struct
{
ElemType data[MAXSIZE];
int front;
int count
}SqQueue;
//队初始化
void InitQueue(SqQueue &qu)
{
qu.front=qu.count=0;
}
//销毁队
void DestoryQueue(SqQueue sq)
{
}
//入队
int EnQueue(&sq,ElemType)
{
if(sq.count==MAXSIZE)
return 0;
sq.data[(sq.count+sq.front)%MAXSIZE]=x;
sq.count++;
return 1;
}
//出队
int DeQueue(SqQueue &sq,ElemType &x)
{
if(sq.count==0)
return 0;
x=sq.data[sq.front];
sq.front=(sq.front+1)%MAXSIZE;
sq.count--;
return 1;
}
//取队头元素
int GetHead(SqQueue sq,ElemType &x)
{
if(sq.count==0)
return 0;
x=sq.data[sq.front];
return 1;
}
//判断队空
int QueueEmpty(SqQueue sq)
{
return (sq.count==0);
}
本文详细介绍了数据结构中的栈和队列。栈是一种后进先出(LIFO)的线性表,包括顺序栈和链栈的存储结构及其基本运算。队列则是先进先出(FIFO)的线性表,讨论了循环队列的实现和操作。文章提供了相关算法设计和应用实例。
5728

被折叠的 条评论
为什么被折叠?



