前言
请跟着上一讲http://blog.youkuaiyun.com/Jurbo/article/details/52593532继续学习
2.2 堆栈
2.2.1 什么是堆栈
计算机如何进行表达式求值?
【例】算术表达式5+6/2-3*4。正确理解:
5+6/2-3*4=5+3-3*4=8-3*4=8-12=-4
- 由两类对象构成的:
- 运算数,如2、3、4
- 运算符号,如+、- 、*、/
- 不同运算符号优先级不一样
后缀表达式
- 中缀表达式:运算符号位于两个运算数之间。如:a+b*c-d/e
- 后缀表达式:运算符号位于两个运算数之后。如:abc*+de/-
【例】62/3-42*+= ?
后缀表达式求值策略:从左向右“扫描”,逐个处理运算数和运算符号
- 遇到运算数怎么办?
- 遇到运算符号怎么办?
【启示】需要有种存储放放,能顺序存储运算数,并在需要时“倒序”输出
堆栈的抽象数据类型描述
堆栈:具有一定操作约束的线性表,只在一端(栈顶,Top)做插入,删除
- 插入数据:入栈(Push)
- 删除数据:出栈(Pop)
- 先进后出:Last in first out(LIFO)
2.2.2 堆栈的顺序存储实现
栈的顺序存储结构通常由一个一维数组和一个记录栈顶元素位置的变量组成
入栈
void Push(Stack PtrS,ElementType item)
{
if(PtrS->Top==MaxSize-1)
{
printf("堆栈满");
return;
]
else
{
PtrS->Data[++(PtrS->Top)]=item;
return;
}
}
入栈的图像
出栈
ElementType Pop(Stack PtrS)
{
if(PtrS->Top==-1)
{
printf("堆栈空");
return ERROR;
}
else
return(PtrS->Data[(PtrS->Top--)]);
}
【例】请用一个数组实现两个堆栈,要求最大地利用数组空间,使数组只要有控件入栈操作就可以成功
【分析】一种比较聪明的方法是使这两个栈分别从数组的两头开始向中间生长;当两个栈的栈顶指针相遇时,表示两个栈都满了
#define MaxSize<存储数据元素的最大个数>
struct DStack
{
ElementType Data[MaxSize];
int Top1; //堆栈1的栈顶指针
int Top2; //堆栈2的栈顶指针
}S
S.Top1=-1;
S.Top2=MaxSize;
void Push(struct DStack *PtrS,ElementType item,int Tag)
{
if(PtrS->Top2-PtrS->Top1==1) //堆栈满
{
printf("堆栈满");
return;
}
if(Tag==1) //对第一个堆栈操作
PtrS->Data[++(PtrS->Top1)]==item;
else //对第二个堆栈操作
PtrS->Data[--(PtrS->Top1)]==item;
}
ElementType Pop(struct DStack *PtrS,int Tag)
{
if(Tag==1)//对第一个堆栈操作
if(PtrS->Top1==-1) //堆栈1空
{
printf("堆栈1空");
return NULL;
}
else
return PtrS->Data[(PtrS->Top1)--];
else
{
if(PtrS->Top2==MaxSize)
{
printf("堆栈2空");
return NULL;
}
else
return PtrS->Data[(PtrS->Top2)++];
}
}
2.2.3 堆栈的链式存储实现
栈的链式存储结构实际上就是一个单链表,叫做链栈。插入和删除操作只能在链栈的栈顶进行。
//链栈的定义
typedef struct SNode *Stack;
struct SNode
{
ElementType Data;
struct SNode *Next;
};
//创建一个空栈
Stack CreateStack()
{
Stack S;
S=(Stack)malloc(sizeof(struct SNode));
S->Next=NULL;
return S;
}
//判断链栈是否为空
int IsEmpty(Stack S)
{
return (S->Next==NULL);
}
//将元素item压入堆栈S
void Push(ElementType item,Stack S)
{
struct SNode,*TmpCell;
TmpCell=(struct SNode *)malloc(sizeof(struct SNode));
TmpCell->Element=item;
TmpCell->Next=S->Next;
S->Next=TmpCell;
}
//删除并返回堆栈的S的栈顶元素(出栈)
ElementType Pop(Stack S)
{
struct SNode *FirstCell;
ElementType TopElem;
if(IsEmpty(S))
{
printf("堆栈空");
return NULL;
}
else
{
FirstCell=S->Next;
S->Next=FirstCell->Next;
TopElem=FirstCell->Element;
free(FirstCell);
return TopElem;
}
}
2.2.4 堆栈应用:表达式求值
回忆:应用堆栈实现后缀表达式求值的基本过程:
从左到右读入后缀表达式的各项(运算符或运算数):
- 运算数:入栈
- 运算符:从堆栈中弹出适当数量的运算数,计算并结果入栈
- 最后,堆栈顶上的元素就是表达式的结果值
中缀表达式求值
基本策略:将中缀表达式转换为后缀表达式,然后求值
如何将中缀表达式转换为后缀表达式?
观察一个简单例子:2+9/3-5 -> 2 9 3 / +5 -
- 运算数相对顺序不变
- 运算符号顺序发生改变
- 需要存储“等待中”的运算符号
- 要将当前运算符号与“等待中”的最后一个运算符号比较
中缀表达式如何转换为后缀表达式
从头到尾读取中缀表达式的每个对象,对不同对象按不同的情况处理。
运算数:直接输出
左括号:压入堆栈
右括号:将栈顶的运算符弹出并输出,直到遇到左括号(出栈,不输出)
运算符
- 若优先级大于栈顶运算符时,则把它压栈
- 若优先级小于等于栈顶运算符时,将栈顶运算符弹出并输出;再比较新的栈顶运算符,直到该运算符大于栈顶运算符优先级为止,然后将该运算符压栈
若各对象处理完毕,则把堆栈中存留的运算符一并输出
【示例】
堆栈的其他应用
- 函数调用及递归实现
- 深度优先搜索
- 回溯算法
- 。。。。
2.3 队列及其实现
2.3.1 队列及顺序存储实现
什么是队列
队列:具有一定操作约束的线性表
- 插入和删除操作:只能在一端插入,而在另一端删除
- 数据插入:入队(AddQ)
- 数据删除:出队列(DeleteQ)
- 先进先出:FIFO
队列的抽象数据类型描述
队列的顺序存储实现
队列的顺序存储结构通常由一个一维数组和一个记录队列头元素位置的变量front以及一个记录队列尾元素位置的变量rear组成。
循环队列
入队
void AddQ(Queue PtrQ,ElementType item)
{
if(PtrQ->rear+1)%MaxSize==PtrQ->front)
{
printf("队列满");
return;
}
PtrQ->rear=(PtrQ->rear+1)%MaxSize;
PtrQ->Data[PtrQ->rear]=item;
}
出队
ElementType DeleteQ(Queue PtrQ)
{
if(PtrQ->front==PtrQ->rear)
{
printf("队列空");
return ERROR;
}
else
{
PtrQ->front==(PtrQ->front+1)%MaxSize;
return PtrQ->Data[PtrQ->front];
}
}
2.3.2 队列的链式存储实现
队列的链式存储结构也可以用一个单链表实现。插入和删除操作分别在链表的两头进行。
不带头结点的链式队列出队操作的一个示例:
ElementType DeleteQ(Queue PtrQ)
{
struct Node *FrontCell;
ElementType FrontElem;
if(PtrQ->front==NULL)
{
printf("队列空");
return ERROR;
}
FrontCell=PtrQ->front;
if(PtrQ->front==PtrQ->rear) //若队列只有一个元素
{
PtrQ->front==PtrQ->rear=NULL; //删除后,队列置为空
}
else
PtrQ->front=PtrQ->front->Next;
FrontElem=FrontCell->Data;
free(FrontCell); //释放被删除结点空间
return FrontElem;
}
2.4 应用实例:多项式加法运算
多项式加法运算
采用不带头结点的单向链表,按照指数递减的顺序排列各项
算法思路:两个指针p1和p2分别指向这两个多项式第一个结点,不断循环: