栈和队列是数据结构中的两种存储结构,它们的相同点是:
栈和队列存储的都是逻辑关系为“一对一”的数据,本质上它们都属于线性存储结构。
栈和队列的区别在于:
栈存储数据,讲究“先进后出”,即最先进栈的数据,最后出栈;队列存储数据,讲究 "先进先出",即最先进队列的数据,也最先出队列。
对于没有系统学过栈和队列的读者,上面两句话很可能读不明白。没关系,接下来我将详细讲解栈和队列这两种存储结构。
数据结构中的栈
对于逻辑关系为“一对一”的数据,除了用顺序表和链表存储外,还可以用栈结构存储。
栈是一种“特殊”的线性存储结构,它的特殊之处体现在以下两个地方:
1、元素进栈和出栈的操作只能从一端完成,另一端是封闭的,如下图所示:
图 1 栈存储结构示意图
通常,我们将元素进栈的过程简称为“入栈”、“进栈”或者“压栈”;将元素出栈的过程简称为“出栈”或者“弹栈”。
2、栈中无论存数据还是取数据,都必须遵循“先进后出”的原则,即最先入栈的元素最先出栈。以图 1 的栈为例,很容易可以看出是元素 1 最先入栈,然后依次是元素 2、3、4 入栈。在此基础上,如果想取出元素 1,根据“先进后出”的原则,必须先依次将元素 4、3、2 出栈,最后才能轮到元素 1 出栈。
我们习惯将栈的开口端称为栈顶,封口端称为栈底。例如在图 1 中,元素 4 一侧为栈顶,元素 1 一侧为栈底,如图 2 所示。
图 2 栈顶和栈底
由此我们可以对栈存储结构下一个定义:栈一种“只能从一端存取元素,且存取过程必须遵循‘先进后出’原则”的线性存储结构。
栈的实际应用
对于刚刚接触栈存储结构的读者来说,可能很难理解为什么会设计出“栈”这种存储结构,它有什么用?栈是一种特殊的线性存储结构,借助它的“特殊性”,可以解决很多实际问题。
1) 实现浏览器的“回退”功能
所谓浏览器的“回退”功能,比如您用浏览器打开 A 页面,然后从 A 页面跳转到 B 页面,然后再从 B 页面跳转到 C 页面。这种情况下,如果想回到 A 页面,有两种方法:
- 重新搜索找到 A 页面;
- 借助浏览器的“回退”功能,先从 C 页面回退到 B 页面,再从 B 页面回退到 A 页面。
很多浏览器的“回退”功能就位于工具栏中,图标是一个类似
←
的箭头。
浏览器的“回退”功能底层就是用栈存储结构实现的,当从 A 页面跳转到 B 页面时,浏览器会执行入栈操作,A 页面信息会存入栈中;同样,从 B 页面跳转到 C 页面时,B 页面信息会存入栈中。当点击浏览器的“回退”按钮时,浏览器会执行“出栈”操作,根据“先进后出”的原则,B 页面先出栈,然后 A 页面出栈,这样就实现了“回退”的功能。
2) 实现 C 语言函数的相互调用
C语言程序中,函数间的相互调用过程也是用栈存储结构实现的。
举个简单的例子:
/**
* 快速入门数据结构 https://xiecoding.cn/ds/
**/
void func(){
printf("Hello,World!");
}
int main(){
func();
return 0;
}
这段 C 语言程序中有两个函数,分别是 main() 函数和 func() 函数,其中 main() 函数内部调用了 func() 函数。
程序执行时,main() 函数会最先入栈,执行到第 5 行代码时,需要执行 func() 函数,此时会将 func() 函数入栈。等待 func() 函数执行完后,func() 函数会出栈,此时 main() 函数中的剩余代码会继续执行,直至 main() 函数执行完毕,做出栈操作,整个程序就执行结束了。
3) 解决一些实际问题
借助栈存储结构,可以快速解决类似“进制转换”、“括号匹配”等问题,具体的解决过程会在后续文章中做详细讲解。
栈的具体实现
和线性表类似,栈存储结构也有两种具体的实现方案:
显然,顺序栈和链栈两种实现方案,本质的区别仍然是顺序表和链表之间的区别,即顺序栈是将所有数据集中存储,而链栈是将数据分散存放,元素之间的逻辑关系靠指针维系。
数据结构中的队列
队列用来存储逻辑关系为“一对一”的数据,是一种“特殊”的线性存储结构。
和顺序表、链表相比,队列的特殊性体现在以下两个方面:
1、元素只能从队列的一端进入,从另一端出去,如下图所示:
图 1 队列存储结构
通常,我们将元素进入队列的一端称为“队尾”,进入队列的过程称为“入队”;将元素从队列中出去的一端称为“队头”,出队列的过程称为“出队”。
2、队列中各个元素的进出必须遵循“先进先出”的原则,即最先入队的元素必须最先出队。
以图 1 所示的队列为例,从各个元素在队列中的存储状态不难判定,元素 1 最先入队,然后是元素 2 入队,最后是元素 3 入队。如果此时想将元素 3 出队,根据“先进先出”原则,必须先将元素 1 和 2 依次出队,最后才能轮到元素 3 出队。
强调:栈和队列不要混淆,栈是一端开口、另一端封口,元素入栈和出栈遵循“先进后出”原则;队列是两端都开口,但元素只能从一端进,从另一端出,且进出队列遵循“先进先出”的原则。
队列的实际应用
队列在操作系统中应用的十分广泛,比如用它解决 CPU 资源的竞争问题。
对于一台计算机来说,CPU 通常只有 1 个,是非常重要的资源。如果在很短的时间内,有多个程序向操作系统申请使用 CPU,就会出现竞争 CPU 资源的现象。不同的操作系统,解决这一问题的方法是不一样的,有一种方法就用到了队列这种存储结构。
假设在某段时间里,有 A、B、C 三个程序向操作系统申请 CPU 资源,操作系统会根据它们的申请次序,将它们排成一个队列。根据“先进先出”原则,最先进队列的程序出队列,并获得 CPU 的使用权。待该程序执行完或者使用 CPU 一段时间后,操作系统会将 CPU 资源分配给下一个出队的程序,以此类推。如果该程序在获得 CPU 资源的时间段内没有执行完,则只能重新入队,等待操作系统再次将 CPU 资源分配给它。
队列还可以用来解决一些实际问题,比如实现一个简单的停车场管理系统、实现一个推小车卡牌游戏等,后续会做详细讲解。
队列的具体实现
和栈的实现方案一样,队列的实现也有两种方式,分别是:
两者的区别仅是顺序表和链表的区别,即顺序队列集中存储数据,而链队列分散存储数据,元素之间的逻辑关系靠指针维系。