栈和队列是限定插入和删除只能在表的“端点” 进行的线性表
栈和队列是线性表的子集(是插入和删除位置受限的线性表)
一。栈(stack)是一个特殊的线性表,是限定仅在一端进行插入和删除操作的线性表
又称为后进先出(Last In First Out)的线性表,简称LIFO结构。
1.栈的相关概念
栈是仅在表尾进行插入、删除操作的线性表。
表尾(即an端)称为栈顶Top;
表头(即a端)称为栈底Base。
插入元素到栈顶(即表尾)的操作,称为入栈。
从栈顶(即表尾)删除最后一个元素的操作,称为出栈。
[思考]假设有3个元素a, b, C,入栈顺序是a,b,c
则它们的出栈顺序有几种可能?
cba abc acb bac bca
二。队列(queue)只能在表的一端进行插入运算,在表的另一端进行删除运算的线性表
是一种先进先出(Frist In Frist Out ---FIFO)的线性表。
1.逻辑结构
与同线性表相同,仍为一对一关系。
2.存储结构
顺序队或链队,以循环顺序队列更常见。
3.运算规则
只能在队首和队尾运算,且访问结点时依照先进先出(FIFO) 的原则。
4.实现方式
关键是掌握入队和出队操作,具体实现依顺序队或链队的不同而不同。
【案例3】为了实现表达式求值。需要设置两个栈:
一个是算符栈OPTR,用于寄存运算符。
另一个称为操作数栈QPND,用于寄存运算数和运算结果。
求值的处理过程是自左至右扫描表达式的每一个字符
●当扫描到的是运算数,则将其压入栈OPND,
●当扫描到的是运算符时
若这个运算符比OPTR栈顶运算符的优先级高,则入栈OPTR,继续向后处理
若这个运算符比OPTR栈顶运算符优先级低,则从OPND栈中弹出两个运算数,从栈
OPTR中弹出栈顶运算符进行运算,并将运算结果压入栈OPND。
●继续处理当前字符,直到遇到结束符为止。
【案例4】舞伴问题
假设在舞会上,男士和女士各自排成一队。舞会开始时,依次从男队和女队的队头各出一-人配成舞伴。 如果两队初始人数不相同,则较长的那一-队中未配对者等待下一轮舞曲。现要求写一_算法模拟 上述舞伴配对问题。
三。 栈的抽象数据类型的类型定义
ADT[Stack ){
数据对象:
D={ai | ai∈ElemSet, i=1,2...n. n≥0 }
数据关系:
R1={ <ai-1,ai>| ai-1, ai∈D, i=2...n}
约定an端为栈顶,a1 端为栈底。
基本操作:初始化、进栈、出栈、取栈顶元素等
} ADT Stack
InitStack(&S)
初始化操作
操作结果:构造一个空栈S。
DestroyStack(&S)
销毁栈操作
初始条件:栈S已存在。
操作结果:栈S被销毁。
StackEmpty(P)判定S是否为空栈
初始条件:栈S已存在。
操作结果:若栈S为空栈,则返回TRUE,
否则FALSE.
StackLength(Sj
求栈的长度
初始条件:栈S已存在。
操作结果:返回S的元素个数,即栈的长度。
GetTop(S, &e)
取栈顶元素
初始条件:栈S已存在且非空。
操作结果:用e返回S的栈顶元素。
ClearStack(&S) 栈置空操作
初始条件:栈S已存在。
操作结果:将S清为空栈。
Push(&S, e)入栈操作
初始条件:栈S已存在。
操作结果:插入元素e为新的栈顶元素。
Pog(&S, &e)出栈操作
初始条件:栈S已存在且非空。
操作结果:删除S的栈顶元素an,并用e返回
其值。
四。顺序栈的表示和实现
利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。栈底一般在低地址端。
空栈:base == top是栈空标志
栈满top-base= =stacksize
#define MAXSIZE 100
typedef struct{
SElemType *base; //栈底指针
SElemType *top; //栈顶指针
int stacksize; //栈可用最大容量
}SqStack; .
InitStac初始化
void InitStack(SqStack S)
{
S.base = (struct SElemType*)malloc(MAXSIZE*sizeof(struct SElemType));
if (S.base == NULL)
{
printf("%s\n", strerror(errno));
}
S.top = S.base;
S.stacksize =MAXSIZE;
}
StackEmpty是否空栈
int StackEmpty(SqStack S)
{
if (S.base == S.top)
{
return 1;
}
else
{
return 0;
}
}
StackLenght栈元素个数
int StackLenght(SqStack S)
{
return S.top - S.base;
}
ClearStack清空栈
void ClearStack(SqStack S)
{
if (S.base)
{
S.top = S.base;
}
}
DestroyStack销毁栈
void DestroyStack(SqStack S)//销毁
{
S.stacksize = 0;
free(S.base);
S.base = NULL;
S.top = NULL;
}
PushStack入栈
void PushStack(SqStack S, struct SElemType e)
{
if (S.top - S.base==S.stacksize)//是否上溢
{
return;
}
else
{
*S.top = e;
S.top++;
}
}
Pop函数 出栈
struct SElemType Pop(SqStack S)
{
if (S.top - S.base == 0)
{
return;
}
else
{
return *--S.top;
}
}
五。链栈的表示和实现
InitStack初始化
void InitStack(LinkStack S)
{
S = NULL;
}
StackEmpty
int StackEmpty(LinkStack S)
{
if (!S)
return 1;
else
return 0;
}
InsertStack
void InsertStack(LinkStack S, struct SElemType e)
{
StackNode * p = (StackNode*)malloc(sizeof(StackNode));
p->data = e;
p->next = S;
S = p;
}
PopStack出栈 - 出最后一个元素
void PopStack(LinkStack S, struct SElemType e)
{
if (S == NULL)return;
e = S->data;
StackNode * p = S;
S = S->next;
free(p);
}
GetTop获取栈顶元素
struct SElemType GetTop(LinkStack S)
{
if (S!=NULL)
return S->data;
}
六。栈与递归
1.递归的定义
■若一个对象部分地包含它自己,或用它自己给自己定义,则称这个对
象是递归的;
■若一个过程直接地或间接地调用自己,则称这个过程是递归的过程。
2 递归的必备的三个条件
1、能将一一个问题转变成一个新问题,而新问题与原问题的解法相同或类同,不同的仅是处理的对象,且这些处理对象是变化有规律的
2、可以通过上述转化而使问题简化>
3、必须有一个明确的递归出口,或称递归的边界
“递归工作栈” --- 程序运行期间使用的数据存储区
3.递归的优缺点
优点:结构清晰,程序易读
缺点:每次调用要生成工作记录,保存状态信息,入栈;返回时要出栈,
恢复状态信息。时间开销大。
注意:单向递归和尾递归都可以转换为循环结构解决
0