栈与队列
001.栈与队列
定义:
栈是限定仅在表尾进行插入和删除操作的线性表。
队列是只允许在一段进行插入操作、而在另一端进行删除操作的线性表。
002.栈
栈:
- 栈是一种特殊的线性表,栈中的元素具有线性关系,即前驱后继关系。
- 其特殊之处就是限制了它的插入和删除的位置,它始终只在栈顶进行。
- 栈的插入操作,叫做进栈,也称压栈,入栈。
- 栈的删除操作,叫做出栈,也有的叫做弹栈。
003.栈的抽象数据类型
栈的常用操作:
ADT 栈(stack)
Data
同线性表。元素具有相同的类型,相邻的元素具有前驱和后继关系。
Operation
InitStack(*S);//初始化操作,建立一个空栈S。
DestoryStack(*S);//若栈存在,则销毁它。
ClearStack(*S);//将栈清空。
StackEmpty(S);//若栈为空,返回true,否则返回false。
GetTop(S,*e);//若栈存在且非空,用e返回S的栈顶元素
Push(*S,e);//若栈S存在,插入新元素e到栈S中并成为栈顶元素。
Pop(*S,*e);//删除栈S中的栈顶元素,并用e返回其值
StackLength(S);//返回栈S的元素个数
endADT
004.栈的顺序存储结构及其实现
下标为0的一端作为栈底,定义一个top变量来指示栈顶元素在数组中的位置。
实现略;
005.两栈共享空间
简单介绍:
数组有两个断点,两个栈由两个栈底,让一个栈的栈底为数组的始端,即下标为0处,另一个栈的栈底为栈的末端,即下标为数组长度n-1处。
关键思路:
它们是在数组的两端,向中间靠拢。top1和top2时栈1和栈2的栈顶指针。
两栈见面之时,即 top1 + 1 == top2 为栈满。
使用场景:
事实上,使用这样的数据结构,通常都是当两个栈的空间需求有相反关系时,也就是一个栈增长时另一个栈在缩短的情况。
就像买卖股票一样,你买入时,一定是有一个你不知道的人在做卖出操作。有人赚钱就一定是有人赔钱,这样使用两栈共享空间存储方法才有比较大的意义。
006.栈的链式存储结构及其实现
实现:
省略
007.栈的应用-递归
斐波那契数列的实现
斐波那契数列:
1 1 2 3 5 8 13 21 34 55 89 144··· ···
递归实现(打印前四十个):
int Fbi(int i)
{
if(i < 2)
{
return i == 0 ? 0 :1 ;
}
return Fbi(i - 1) + Fbi(i - 2);
}
int main()
{
for(int i = 0; i < 40; i++)
{
printf("%d ", Fbi(i));
}
return 0;
}
递归定义
我们把一个直接调用自己或通过一系列的调用语句间接调用自己的函数,称为递归函数。
每个递归定义必须至少有一个条件满足时递归不再进行,即不再引用自身而是返回值退出。
递归与迭代:
迭代使用的是循环结构,递归使用的是选择结构。
递归能使程序的结构更清晰、更简洁、更容易让人理解,从而减少读懂代码的时间。
但是大量的队规调用会建立函数的副本,会耗费大量的时间和内存。
迭代则不需要反复调用函数和占用额外的内存。(应该视不同情况选择不同的代码实现方式)。
008.栈的应用———四则运算表达式求值
后缀(逆波兰)表示法定义
原表达式(中缀表达式):
9 + (3 - 1) * 3 + 10 / 2
后缀表达式:
9 3 1 - 3 * + 10 2 / +
后缀表达式计算规则:
从左到右遍历表达式的每个数字和符号,遇到是数字就进栈那,遇到是符号,就将处于栈顶两个数字出栈,进行运算,运算结果进栈,一直到最终获得的结果。
中缀表达式转化为后缀表达式规则:
从左至右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;
若是符号,则判断与其栈顶符号的优先级,是右括号或优先级低于栈顶符号,则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。
009.队列
队列定义:
只允许在一端进行插入操作,而在另一端进行删除操作的线性表。(先进先出)
队列的抽象数据类型:
ADT 队列(Queue)
Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
Operation
InitQueue(*Q);//初始化操作,建立一个空队列
DestroyQueue(*Q);//若队列Q存在,则销毁它。
ClearQueue(*Q);//将队列Q清空
QueueEmpty(Q);//若队列Q为空,返回true,否则返回false。
GetHead(Q,*e);//若队列Q存在且非空,用e返回队列Q 的队头元素。
EnQueue(*Q,e);//若队列Q存在,插入新元素e到队列Q中并成为队尾元素。
DeQueue(*Q,*e);//删除队列Q中队头元素,并用e返回其值。
Queue Length(Q);//返回队列Q的元素个数
endADT
循环队列
队列的顺序存储的不足:
假设一个队列有n个元素,则顺序存储的队列需要建立一个大于n的数组,并大队列的所有元素存储在数组的前n个单元,数组下标为0的一端即是队头。
所谓的入队列操作,其实就是在队尾追加一个元素,不需要移动任何元素,因此时间复杂度为O(1)。
但队列的出列是在队头,即下标为0的位置,那也就意味着,队列中的所有元素都得向前移动,时间复杂度为O(n)。
循环队列就解决了这个问题:
引入两个指针,front指针指向队头元素,rear指针指向队尾元素的下一个位置;
这样当front等于rear时,此队列不是还剩一个元素,而是空队列。
如何判断队列已满:
方法一:引入一个flag标志变量,当front == rear,且flag = 0时为队列空,当front == rear,且flag = 1时为队列满。
方法二:队列满时,修改其条件,保留一个元素空间。
队列的链式存储
定义:
队列的链式存储结构,其实就是线性表的单链表,只不过是它只能尾进头出而已,我们把它简称为练队列。
实现:
略